// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/renderer_host/render_widget_host_view_views.h" #include <algorithm> #include <string> #include "app/keyboard_code_conversion_gtk.h" #include "app/l10n_util.h" #include "app/x11_util.h" #include "base/command_line.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/metrics/histogram.h" #include "base/string_number_conversions.h" #include "base/task.h" #include "base/time.h" #include "chrome/browser/renderer_host/backing_store_x.h" #include "chrome/browser/renderer_host/render_widget_host.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/native_web_keyboard_event.h" #include "chrome/common/render_messages.h" #include "third_party/WebKit/WebKit/chromium/public/gtk/WebInputEventFactory.h" #include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" #include "views/event.h" #include "views/widget/widget.h" static const int kMaxWindowWidth = 4000; static const int kMaxWindowHeight = 4000; static const char* kRenderWidgetHostViewKey = "__RENDER_WIDGET_HOST_VIEW__"; using WebKit::WebInputEventFactory; using WebKit::WebMouseWheelEvent; namespace { int WebInputEventFlagsFromViewsEvent(const views::Event& event) { int modifiers = 0; if (event.IsShiftDown()) modifiers |= WebKit::WebInputEvent::ShiftKey; if (event.IsControlDown()) modifiers |= WebKit::WebInputEvent::ControlKey; if (event.IsAltDown()) modifiers |= WebKit::WebInputEvent::AltKey; return modifiers; } } // namespace // static RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget( RenderWidgetHost* widget) { return new RenderWidgetHostViewViews(widget); } RenderWidgetHostViewViews::RenderWidgetHostViewViews(RenderWidgetHost* host) : host_(host), about_to_validate_and_paint_(false), is_hidden_(false), is_loading_(false), is_showing_context_menu_(false), visually_deemphasized_(false) { SetFocusable(true); host_->set_view(this); } RenderWidgetHostViewViews::~RenderWidgetHostViewViews() { RenderViewGone(); } void RenderWidgetHostViewViews::InitAsChild() { Show(); } RenderWidgetHost* RenderWidgetHostViewViews::GetRenderWidgetHost() const { return host_; } void RenderWidgetHostViewViews::InitAsPopup( RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) { // TODO(anicolao): figure out cases where popups occur and implement NOTIMPLEMENTED(); } void RenderWidgetHostViewViews::InitAsFullscreen( RenderWidgetHostView* parent_host_view) { NOTIMPLEMENTED(); } void RenderWidgetHostViewViews::DidBecomeSelected() { if (!is_hidden_) return; if (tab_switch_paint_time_.is_null()) tab_switch_paint_time_ = base::TimeTicks::Now(); is_hidden_ = false; host_->WasRestored(); } void RenderWidgetHostViewViews::WasHidden() { if (is_hidden_) return; // If we receive any more paint messages while we are hidden, we want to // ignore them so we don't re-allocate the backing store. We will paint // everything again when we become selected again. is_hidden_ = true; // If we have a renderer, then inform it that we are being hidden so it can // reduce its resource utilization. GetRenderWidgetHost()->WasHidden(); } void RenderWidgetHostViewViews::SetSize(const gfx::Size& size) { // This is called when webkit has sent us a Move message. int width = std::min(size.width(), kMaxWindowWidth); int height = std::min(size.height(), kMaxWindowHeight); if (requested_size_.width() != width || requested_size_.height() != height) { requested_size_ = gfx::Size(width, height); SetBounds(x(), y(), requested_size_.width(), requested_size_.height()); host_->WasResized(); } } void RenderWidgetHostViewViews::MovePluginWindows( const std::vector<webkit_glue::WebPluginGeometry>& moves) { // TODO(anicolao): NIY NOTIMPLEMENTED(); } void RenderWidgetHostViewViews::Focus() { RequestFocus(); } bool RenderWidgetHostViewViews::HasFocus() { return View::HasFocus(); } void RenderWidgetHostViewViews::Show() { SetVisible(true); } void RenderWidgetHostViewViews::Hide() { SetVisible(false); } void RenderWidgetHostViewViews::Blur() { // TODO(estade): We should be clearing native focus as well, but I know of no // way to do that without focusing another widget. host_->Blur(); } bool RenderWidgetHostViewViews::IsShowing() { return IsVisible(); } gfx::Rect RenderWidgetHostViewViews::GetViewBounds() const { return bounds(); } void RenderWidgetHostViewViews::UpdateCursor(const WebCursor& cursor) { // Optimize the common case, where the cursor hasn't changed. // However, we can switch between different pixmaps, so only on the // non-pixmap branch. if (current_cursor_.GetCursorType() != GDK_CURSOR_IS_PIXMAP && current_cursor_.GetCursorType() == cursor.GetCursorType()) { return; } current_cursor_ = cursor; ShowCurrentCursor(); } void RenderWidgetHostViewViews::SetIsLoading(bool is_loading) { is_loading_ = is_loading; // Only call ShowCurrentCursor() when it will actually change the cursor. if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR) ShowCurrentCursor(); } void RenderWidgetHostViewViews::ImeUpdateTextInputState( WebKit::WebTextInputType type, const gfx::Rect& caret_rect) { // TODO(bryeung): im_context_->UpdateInputMethodState(type, caret_rect); NOTIMPLEMENTED(); } void RenderWidgetHostViewViews::ImeCancelComposition() { // TODO(bryeung): im_context_->CancelComposition(); NOTIMPLEMENTED(); } void RenderWidgetHostViewViews::DidUpdateBackingStore( const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, const std::vector<gfx::Rect>& copy_rects) { if (is_hidden_) return; // TODO(darin): Implement the equivalent of Win32's ScrollWindowEX. Can that // be done using XCopyArea? Perhaps similar to // BackingStore::ScrollBackingStore? if (about_to_validate_and_paint_) invalid_rect_ = invalid_rect_.Union(scroll_rect); else SchedulePaint(scroll_rect, false); for (size_t i = 0; i < copy_rects.size(); ++i) { // Avoid double painting. NOTE: This is only relevant given the call to // Paint(scroll_rect) above. gfx::Rect rect = copy_rects[i].Subtract(scroll_rect); if (rect.IsEmpty()) continue; if (about_to_validate_and_paint_) invalid_rect_ = invalid_rect_.Union(rect); else SchedulePaint(rect, false); } } void RenderWidgetHostViewViews::RenderViewGone() { GetRenderWidgetHost()->ViewDestroyed(); Destroy(); } void RenderWidgetHostViewViews::Destroy() { // TODO(anicolao): deal with any special popup cleanup NOTIMPLEMENTED(); } void RenderWidgetHostViewViews::SetTooltipText(const std::wstring& tip) { // TODO(anicolao): decide if we want tooltips for touch (none specified // right now/might want a press-and-hold display) // NOTIMPLEMENTED(); ... too annoying, it triggers for every mousemove } void RenderWidgetHostViewViews::SelectionChanged(const std::string& text) { // TODO(anicolao): deal with the clipboard without GTK NOTIMPLEMENTED(); } void RenderWidgetHostViewViews::ShowingContextMenu(bool showing) { is_showing_context_menu_ = showing; } bool RenderWidgetHostViewViews::NeedsInputGrab() { return popup_type_ == WebKit::WebPopupTypeSelect; } bool RenderWidgetHostViewViews::IsPopup() { return popup_type_ != WebKit::WebPopupTypeNone; } BackingStore* RenderWidgetHostViewViews::AllocBackingStore( const gfx::Size& size) { return new BackingStoreX(host_, size, x11_util::GetVisualFromGtkWidget(native_view()), gtk_widget_get_visual(native_view())->depth); } gfx::NativeView RenderWidgetHostViewViews::native_view() const { return GetWidget()->GetNativeView(); } void RenderWidgetHostViewViews::SetBackground(const SkBitmap& background) { RenderWidgetHostView::SetBackground(background); host_->Send(new ViewMsg_SetBackground(host_->routing_id(), background)); } void RenderWidgetHostViewViews::Paint(gfx::Canvas* canvas) { // Don't do any painting if the GPU process is rendering directly // into the View. RenderWidgetHost* render_widget_host = GetRenderWidgetHost(); if (render_widget_host->is_gpu_rendering_active()) { return; } GdkWindow* window = native_view()->window; DCHECK(!about_to_validate_and_paint_); // TODO(anicolao): get the damage somehow // invalid_rect_ = damage_rect; invalid_rect_ = bounds(); about_to_validate_and_paint_ = true; BackingStoreX* backing_store = static_cast<BackingStoreX*>( host_->GetBackingStore(true)); // Calling GetBackingStore maybe have changed |invalid_rect_|... about_to_validate_and_paint_ = false; gfx::Rect paint_rect = gfx::Rect(0, 0, kMaxWindowWidth, kMaxWindowHeight); paint_rect = paint_rect.Intersect(invalid_rect_); if (backing_store) { // Only render the widget if it is attached to a window; there's a short // period where this object isn't attached to a window but hasn't been // Destroy()ed yet and it receives paint messages... if (window) { if (!visually_deemphasized_) { // In the common case, use XCopyArea. We don't draw more than once, so // we don't need to double buffer. backing_store->XShowRect( paint_rect, x11_util::GetX11WindowFromGtkWidget(native_view())); } else { // If the grey blend is showing, we make two drawing calls. Use double // buffering to prevent flicker. Use CairoShowRect because XShowRect // shortcuts GDK's double buffering. GdkRectangle rect = { paint_rect.x(), paint_rect.y(), paint_rect.width(), paint_rect.height() }; gdk_window_begin_paint_rect(window, &rect); backing_store->CairoShowRect(paint_rect, GDK_DRAWABLE(window)); cairo_t* cr = gdk_cairo_create(window); gdk_cairo_rectangle(cr, &rect); cairo_set_source_rgba(cr, 0, 0, 0, 0.7); cairo_fill(cr); cairo_destroy(cr); gdk_window_end_paint(window); } } if (!whiteout_start_time_.is_null()) { base::TimeDelta whiteout_duration = base::TimeTicks::Now() - whiteout_start_time_; UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration); // Reset the start time to 0 so that we start recording again the next // time the backing store is NULL... whiteout_start_time_ = base::TimeTicks(); } if (!tab_switch_paint_time_.is_null()) { base::TimeDelta tab_switch_paint_duration = base::TimeTicks::Now() - tab_switch_paint_time_; UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration", tab_switch_paint_duration); // Reset tab_switch_paint_time_ to 0 so future tab selections are // recorded. tab_switch_paint_time_ = base::TimeTicks(); } } else { if (window) gdk_window_clear(window); if (whiteout_start_time_.is_null()) whiteout_start_time_ = base::TimeTicks::Now(); } } bool RenderWidgetHostViewViews::OnMousePressed(const views::MouseEvent& event) { RequestFocus(); // TODO(anicolao): validate event generation. WebKit::WebMouseEvent e = WebMouseEventFromViewsEvent(event); // TODO(anicolao): deal with double clicks e.type = WebKit::WebInputEvent::MouseDown; e.clickCount = 1; GetRenderWidgetHost()->ForwardMouseEvent(e); return true; } void RenderWidgetHostViewViews::OnMouseReleased(const views::MouseEvent& event, bool canceled) { WebKit::WebMouseEvent e = WebMouseEventFromViewsEvent(event); e.type = WebKit::WebInputEvent::MouseUp; e.clickCount = 1; GetRenderWidgetHost()->ForwardMouseEvent(e); } bool RenderWidgetHostViewViews::OnMouseDragged(const views::MouseEvent& event) { OnMouseMoved(event); return true; } void RenderWidgetHostViewViews::OnMouseMoved(const views::MouseEvent& event) { WebKit::WebMouseEvent e = WebMouseEventFromViewsEvent(event); e.type = WebKit::WebInputEvent::MouseMove; GetRenderWidgetHost()->ForwardMouseEvent(e); } void RenderWidgetHostViewViews::OnMouseEntered(const views::MouseEvent& event) { // Already generated synthetically by webkit. } void RenderWidgetHostViewViews::OnMouseExited(const views::MouseEvent& event) { // Already generated synthetically by webkit. } bool RenderWidgetHostViewViews::OnMouseWheel(const views::MouseWheelEvent& e) { NOTIMPLEMENTED(); return false; } bool RenderWidgetHostViewViews::OnKeyPressed(const views::KeyEvent &e) { // Send key event to input method. // TODO host_view->im_context_->ProcessKeyEvent(event); NativeWebKeyboardEvent wke; wke.type = WebKit::WebInputEvent::KeyDown; wke.windowsKeyCode = e.GetKeyCode(); wke.setKeyIdentifierFromWindowsKeyCode(); wke.text[0] = wke.unmodifiedText[0] = static_cast<unsigned short>(gdk_keyval_to_unicode( app::GdkKeyCodeForWindowsKeyCode(e.GetKeyCode(), false /*shift*/))); wke.modifiers = WebInputEventFlagsFromViewsEvent(e); ForwardKeyboardEvent(wke); // send the keypress event wke.type = WebKit::WebInputEvent::Char; // TODO(anicolao): fear this comment from GTK land // We return TRUE because we did handle the event. If it turns out webkit // can't handle the event, we'll deal with it in // RenderView::UnhandledKeyboardEvent(). return TRUE; } bool RenderWidgetHostViewViews::OnKeyReleased(const views::KeyEvent &e) { // TODO(bryeung): deal with input methods NativeWebKeyboardEvent wke; wke.type = WebKit::WebInputEvent::KeyUp; wke.windowsKeyCode = e.GetKeyCode(); wke.setKeyIdentifierFromWindowsKeyCode(); ForwardKeyboardEvent(wke); return TRUE; } void RenderWidgetHostViewViews::DidGainFocus() { #if 0 // TODO(anicolao): - is this needed/replicable? // Comes from the GTK equivalent. int x, y; gtk_widget_get_pointer(native_view(), &x, &y); // http://crbug.com/13389 // If the cursor is in the render view, fake a mouse move event so that // webkit updates its state. Otherwise webkit might think the cursor is // somewhere it's not. if (x >= 0 && y >= 0 && x < native_view()->allocation.width && y < native_view()->allocation.height) { WebKit::WebMouseEvent fake_event; fake_event.timeStampSeconds = base::Time::Now().ToDoubleT(); fake_event.modifiers = 0; fake_event.windowX = fake_event.x = x; fake_event.windowY = fake_event.y = y; gdk_window_get_origin(native_view()->window, &x, &y); fake_event.globalX = fake_event.x + x; fake_event.globalY = fake_event.y + y; fake_event.type = WebKit::WebInputEvent::MouseMove; fake_event.button = WebKit::WebMouseEvent::ButtonNone; GetRenderWidgetHost()->ForwardMouseEvent(fake_event); } #endif ShowCurrentCursor(); GetRenderWidgetHost()->GotFocus(); } void RenderWidgetHostViewViews::WillLoseFocus() { // If we are showing a context menu, maintain the illusion that webkit has // focus. if (!is_showing_context_menu_) GetRenderWidgetHost()->Blur(); } void RenderWidgetHostViewViews::ShowCurrentCursor() { // The widget may not have a window. If that's the case, abort mission. This // is the same issue as that explained above in Paint(). if (!native_view()->window) return; // TODO(anicolao): change to set cursors without GTK } void RenderWidgetHostViewViews::CreatePluginContainer( gfx::PluginWindowHandle id) { // TODO(anicolao): plugin_container_manager_.CreatePluginContainer(id); } void RenderWidgetHostViewViews::DestroyPluginContainer( gfx::PluginWindowHandle id) { // TODO(anicolao): plugin_container_manager_.DestroyPluginContainer(id); } void RenderWidgetHostViewViews::SetVisuallyDeemphasized(bool deemphasized) { // TODO(anicolao) } bool RenderWidgetHostViewViews::ContainsNativeView( gfx::NativeView native_view) const { // TODO(port) NOTREACHED() << "RenderWidgetHostViewViews::ContainsNativeView not implemented."; return false; } WebKit::WebMouseEvent RenderWidgetHostViewViews::WebMouseEventFromViewsEvent( const views::MouseEvent& event) { WebKit::WebMouseEvent wmevent; wmevent.timeStampSeconds = base::Time::Now().ToDoubleT(); wmevent.windowX = wmevent.x = event.x(); wmevent.windowY = wmevent.y = event.y(); int x, y; gdk_window_get_origin(GetNativeView()->window, &x, &y); wmevent.globalX = wmevent.x + x; wmevent.globalY = wmevent.y + y; wmevent.modifiers = WebInputEventFlagsFromViewsEvent(event); // Setting |wmevent.button| is not necessary for -move events, but it is // necessary for -clicks and -drags. if (event.IsMiddleMouseButton()) { wmevent.modifiers |= WebKit::WebInputEvent::MiddleButtonDown; wmevent.button = WebKit::WebMouseEvent::ButtonMiddle; } if (event.IsRightMouseButton()) { wmevent.modifiers |= WebKit::WebInputEvent::RightButtonDown; wmevent.button = WebKit::WebMouseEvent::ButtonRight; } if (event.IsLeftMouseButton()) { wmevent.modifiers |= WebKit::WebInputEvent::LeftButtonDown; wmevent.button = WebKit::WebMouseEvent::ButtonLeft; } return wmevent; } void RenderWidgetHostViewViews::ForwardKeyboardEvent( const NativeWebKeyboardEvent& event) { if (!host_) return; EditCommands edit_commands; #if 0 TODO(bryeung): key bindings if (!event.skip_in_browser && key_bindings_handler_->Match(event, &edit_commands)) { host_->ForwardEditCommandsForNextKeyEvent(edit_commands); } #endif host_->ForwardKeyboardEvent(event); } // static RenderWidgetHostView* RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView( gfx::NativeView widget) { gpointer user_data = g_object_get_data(G_OBJECT(widget), kRenderWidgetHostViewKey); return reinterpret_cast<RenderWidgetHostView*>(user_data); }