1// Copyright 2013 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/shell/browser/shell.h" 6 7#include "base/command_line.h" 8#include "base/strings/utf_string_conversions.h" 9#include "content/public/browser/context_factory.h" 10#include "content/public/browser/render_widget_host_view.h" 11#include "content/public/browser/web_contents.h" 12#include "content/public/common/context_menu_params.h" 13#include "content/shell/browser/shell_platform_data_aura.h" 14#include "ui/aura/client/screen_position_client.h" 15#include "ui/aura/env.h" 16#include "ui/aura/window.h" 17#include "ui/aura/window_event_dispatcher.h" 18#include "ui/base/clipboard/clipboard.h" 19#include "ui/base/models/simple_menu_model.h" 20#include "ui/base/resource/resource_bundle.h" 21#include "ui/events/event.h" 22#include "ui/gfx/screen.h" 23#include "ui/views/background.h" 24#include "ui/views/controls/button/label_button.h" 25#include "ui/views/controls/button/menu_button.h" 26#include "ui/views/controls/button/menu_button_listener.h" 27#include "ui/views/controls/menu/menu_runner.h" 28#include "ui/views/controls/textfield/textfield.h" 29#include "ui/views/controls/textfield/textfield_controller.h" 30#include "ui/views/controls/webview/webview.h" 31#include "ui/views/layout/fill_layout.h" 32#include "ui/views/layout/grid_layout.h" 33#include "ui/views/test/desktop_test_views_delegate.h" 34#include "ui/views/view.h" 35#include "ui/views/widget/widget.h" 36#include "ui/views/widget/widget_delegate.h" 37 38#if defined(OS_CHROMEOS) 39#include "chromeos/dbus/dbus_thread_manager.h" 40#include "ui/aura/test/test_screen.h" 41#include "ui/wm/test/wm_test_helper.h" 42#else // !defined(OS_CHROMEOS) 43#include "ui/views/widget/desktop_aura/desktop_screen.h" 44#endif 45 46#if defined(OS_WIN) 47#include <fcntl.h> 48#include <io.h> 49#endif 50 51namespace content { 52 53namespace { 54// ViewDelegate implementation for aura content shell 55class ShellViewsDelegateAura : public views::DesktopTestViewsDelegate { 56 public: 57 ShellViewsDelegateAura() : use_transparent_windows_(false) { 58 } 59 60 virtual ~ShellViewsDelegateAura() { 61 } 62 63 void SetUseTransparentWindows(bool transparent) { 64 use_transparent_windows_ = transparent; 65 } 66 67 private: 68 bool use_transparent_windows_; 69 70 DISALLOW_COPY_AND_ASSIGN(ShellViewsDelegateAura); 71}; 72 73// Model for the "Debug" menu 74class ContextMenuModel : public ui::SimpleMenuModel, 75 public ui::SimpleMenuModel::Delegate { 76 public: 77 explicit ContextMenuModel( 78 Shell* shell, const content::ContextMenuParams& params) 79 : ui::SimpleMenuModel(this), 80 shell_(shell), 81 params_(params) { 82 AddItem(COMMAND_OPEN_DEVTOOLS, base::ASCIIToUTF16("Inspect Element")); 83 } 84 85 // ui::SimpleMenuModel::Delegate: 86 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE { 87 return false; 88 } 89 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE { 90 return true; 91 } 92 virtual bool GetAcceleratorForCommandId( 93 int command_id, 94 ui::Accelerator* accelerator) OVERRIDE { return false; } 95 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE { 96 switch (command_id) { 97 case COMMAND_OPEN_DEVTOOLS: 98 shell_->ShowDevToolsForElementAt(params_.x, params_.y); 99 break; 100 }; 101 } 102 103 private: 104 enum CommandID { 105 COMMAND_OPEN_DEVTOOLS 106 }; 107 108 Shell* shell_; 109 content::ContextMenuParams params_; 110 111 DISALLOW_COPY_AND_ASSIGN(ContextMenuModel); 112}; 113 114// Maintain the UI controls and web view for content shell 115class ShellWindowDelegateView : public views::WidgetDelegateView, 116 public views::TextfieldController, 117 public views::ButtonListener { 118 public: 119 enum UIControl { 120 BACK_BUTTON, 121 FORWARD_BUTTON, 122 STOP_BUTTON 123 }; 124 125 ShellWindowDelegateView(Shell* shell) 126 : shell_(shell), 127 toolbar_view_(new View), 128 contents_view_(new View) { 129 } 130 virtual ~ShellWindowDelegateView() {} 131 132 // Update the state of UI controls 133 void SetAddressBarURL(const GURL& url) { 134 url_entry_->SetText(base::ASCIIToUTF16(url.spec())); 135 } 136 void SetWebContents(WebContents* web_contents, const gfx::Size& size) { 137 contents_view_->SetLayoutManager(new views::FillLayout()); 138 web_view_ = new views::WebView(web_contents->GetBrowserContext()); 139 web_view_->SetWebContents(web_contents); 140 web_view_->SetPreferredSize(size); 141 web_contents->Focus(); 142 contents_view_->AddChildView(web_view_); 143 Layout(); 144 145 // Resize the widget, keeping the same origin. 146 gfx::Rect bounds = GetWidget()->GetWindowBoundsInScreen(); 147 bounds.set_size(GetWidget()->GetRootView()->GetPreferredSize()); 148 GetWidget()->SetBounds(bounds); 149 150 // Resizing a widget on chromeos doesn't automatically resize the root, need 151 // to explicitly do that. 152#if defined(OS_CHROMEOS) 153 GetWidget()->GetNativeWindow()->GetHost()->SetBounds(bounds); 154#endif 155 } 156 157 void SetWindowTitle(const base::string16& title) { title_ = title; } 158 void EnableUIControl(UIControl control, bool is_enabled) { 159 if (control == BACK_BUTTON) { 160 back_button_->SetState(is_enabled ? views::CustomButton::STATE_NORMAL 161 : views::CustomButton::STATE_DISABLED); 162 } else if (control == FORWARD_BUTTON) { 163 forward_button_->SetState(is_enabled ? views::CustomButton::STATE_NORMAL 164 : views::CustomButton::STATE_DISABLED); 165 } else if (control == STOP_BUTTON) { 166 stop_button_->SetState(is_enabled ? views::CustomButton::STATE_NORMAL 167 : views::CustomButton::STATE_DISABLED); 168 } 169 } 170 171 void ShowWebViewContextMenu(const content::ContextMenuParams& params) { 172 gfx::Point screen_point(params.x, params.y); 173 174 // Convert from content coordinates to window coordinates. 175 // This code copied from chrome_web_contents_view_delegate_views.cc 176 aura::Window* web_contents_window = 177 shell_->web_contents()->GetNativeView(); 178 aura::Window* root_window = web_contents_window->GetRootWindow(); 179 aura::client::ScreenPositionClient* screen_position_client = 180 aura::client::GetScreenPositionClient(root_window); 181 if (screen_position_client) { 182 screen_position_client->ConvertPointToScreen(web_contents_window, 183 &screen_point); 184 } 185 186 context_menu_model_.reset(new ContextMenuModel(shell_, params)); 187 context_menu_runner_.reset(new views::MenuRunner( 188 context_menu_model_.get(), views::MenuRunner::CONTEXT_MENU)); 189 190 if (context_menu_runner_->RunMenuAt(web_view_->GetWidget(), 191 NULL, 192 gfx::Rect(screen_point, gfx::Size()), 193 views::MENU_ANCHOR_TOPRIGHT, 194 ui::MENU_SOURCE_NONE) == 195 views::MenuRunner::MENU_DELETED) { 196 return; 197 } 198 } 199 200 void OnWebContentsFocused(content::WebContents* web_contents) { 201 if (web_view_->GetWebContents() == web_contents) 202 web_view_->OnWebContentsFocused(web_contents); 203 } 204 205 private: 206 // Initialize the UI control contained in shell window 207 void InitShellWindow() { 208 set_background(views::Background::CreateStandardPanelBackground()); 209 210 views::GridLayout* layout = new views::GridLayout(this); 211 SetLayoutManager(layout); 212 213 views::ColumnSet* column_set = layout->AddColumnSet(0); 214 column_set->AddPaddingColumn(0, 2); 215 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, 216 views::GridLayout::USE_PREF, 0, 0); 217 column_set->AddPaddingColumn(0, 2); 218 219 layout->AddPaddingRow(0, 2); 220 221 // Add toolbar buttons and URL text field 222 { 223 layout->StartRow(0, 0); 224 views::GridLayout* toolbar_layout = new views::GridLayout(toolbar_view_); 225 toolbar_view_->SetLayoutManager(toolbar_layout); 226 227 views::ColumnSet* toolbar_column_set = 228 toolbar_layout->AddColumnSet(0); 229 // Back button 230 back_button_ = new views::LabelButton(this, base::ASCIIToUTF16("Back")); 231 back_button_->SetStyle(views::Button::STYLE_BUTTON); 232 gfx::Size back_button_size = back_button_->GetPreferredSize(); 233 toolbar_column_set->AddColumn(views::GridLayout::CENTER, 234 views::GridLayout::CENTER, 0, 235 views::GridLayout::FIXED, 236 back_button_size.width(), 237 back_button_size.width() / 2); 238 // Forward button 239 forward_button_ = 240 new views::LabelButton(this, base::ASCIIToUTF16("Forward")); 241 forward_button_->SetStyle(views::Button::STYLE_BUTTON); 242 gfx::Size forward_button_size = forward_button_->GetPreferredSize(); 243 toolbar_column_set->AddColumn(views::GridLayout::CENTER, 244 views::GridLayout::CENTER, 0, 245 views::GridLayout::FIXED, 246 forward_button_size.width(), 247 forward_button_size.width() / 2); 248 // Refresh button 249 refresh_button_ = 250 new views::LabelButton(this, base::ASCIIToUTF16("Refresh")); 251 refresh_button_->SetStyle(views::Button::STYLE_BUTTON); 252 gfx::Size refresh_button_size = refresh_button_->GetPreferredSize(); 253 toolbar_column_set->AddColumn(views::GridLayout::CENTER, 254 views::GridLayout::CENTER, 0, 255 views::GridLayout::FIXED, 256 refresh_button_size.width(), 257 refresh_button_size.width() / 2); 258 // Stop button 259 stop_button_ = new views::LabelButton(this, base::ASCIIToUTF16("Stop")); 260 stop_button_->SetStyle(views::Button::STYLE_BUTTON); 261 gfx::Size stop_button_size = stop_button_->GetPreferredSize(); 262 toolbar_column_set->AddColumn(views::GridLayout::CENTER, 263 views::GridLayout::CENTER, 0, 264 views::GridLayout::FIXED, 265 stop_button_size.width(), 266 stop_button_size.width() / 2); 267 toolbar_column_set->AddPaddingColumn(0, 2); 268 // URL entry 269 url_entry_ = new views::Textfield(); 270 url_entry_->set_controller(this); 271 toolbar_column_set->AddColumn(views::GridLayout::FILL, 272 views::GridLayout::FILL, 1, 273 views::GridLayout::USE_PREF, 0, 0); 274 toolbar_column_set->AddPaddingColumn(0, 2); 275 276 // Fill up the first row 277 toolbar_layout->StartRow(0, 0); 278 toolbar_layout->AddView(back_button_); 279 toolbar_layout->AddView(forward_button_); 280 toolbar_layout->AddView(refresh_button_); 281 toolbar_layout->AddView(stop_button_); 282 toolbar_layout->AddView(url_entry_); 283 284 layout->AddView(toolbar_view_); 285 } 286 287 layout->AddPaddingRow(0, 5); 288 289 // Add web contents view as the second row 290 { 291 layout->StartRow(1, 0); 292 layout->AddView(contents_view_); 293 } 294 295 layout->AddPaddingRow(0, 5); 296 297 InitAccelerators(); 298 } 299 void InitAccelerators() { 300 static const ui::KeyboardCode keys[] = { ui::VKEY_F5, 301 ui::VKEY_BROWSER_BACK, 302 ui::VKEY_BROWSER_FORWARD }; 303 for (size_t i = 0; i < arraysize(keys); ++i) { 304 GetFocusManager()->RegisterAccelerator( 305 ui::Accelerator(keys[i], ui::EF_NONE), 306 ui::AcceleratorManager::kNormalPriority, 307 this); 308 } 309 } 310 // Overridden from TextfieldController 311 virtual void ContentsChanged(views::Textfield* sender, 312 const base::string16& new_contents) OVERRIDE { 313 } 314 virtual bool HandleKeyEvent(views::Textfield* sender, 315 const ui::KeyEvent& key_event) OVERRIDE { 316 if (sender == url_entry_ && key_event.key_code() == ui::VKEY_RETURN) { 317 std::string text = base::UTF16ToUTF8(url_entry_->text()); 318 GURL url(text); 319 if (!url.has_scheme()) { 320 url = GURL(std::string("http://") + std::string(text)); 321 url_entry_->SetText(base::ASCIIToUTF16(url.spec())); 322 } 323 shell_->LoadURL(url); 324 return true; 325 } 326 return false; 327 } 328 329 // Overridden from ButtonListener 330 virtual void ButtonPressed(views::Button* sender, 331 const ui::Event& event) OVERRIDE { 332 if (sender == back_button_) 333 shell_->GoBackOrForward(-1); 334 else if (sender == forward_button_) 335 shell_->GoBackOrForward(1); 336 else if (sender == refresh_button_) 337 shell_->Reload(); 338 else if (sender == stop_button_) 339 shell_->Stop(); 340 } 341 342 // Overridden from WidgetDelegateView 343 virtual bool CanResize() const OVERRIDE { return true; } 344 virtual bool CanMaximize() const OVERRIDE { return true; } 345 virtual bool CanMinimize() const OVERRIDE { return true; } 346 virtual base::string16 GetWindowTitle() const OVERRIDE { 347 return title_; 348 } 349 virtual void WindowClosing() OVERRIDE { 350 if (shell_) { 351 delete shell_; 352 shell_ = NULL; 353 } 354 } 355 virtual View* GetContentsView() OVERRIDE { return this; } 356 357 // Overridden from View 358 virtual gfx::Size GetMinimumSize() const OVERRIDE { 359 // We want to be able to make the window smaller than its initial 360 // (preferred) size. 361 return gfx::Size(); 362 } 363 virtual void ViewHierarchyChanged( 364 const ViewHierarchyChangedDetails& details) OVERRIDE { 365 if (details.is_add && details.child == this) { 366 InitShellWindow(); 367 } 368 } 369 370 // Overridden from AcceleratorTarget: 371 virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) OVERRIDE { 372 switch (accelerator.key_code()) { 373 case ui::VKEY_F5: 374 shell_->Reload(); 375 return true; 376 case ui::VKEY_BROWSER_BACK: 377 shell_->GoBackOrForward(-1); 378 return true; 379 case ui::VKEY_BROWSER_FORWARD: 380 shell_->GoBackOrForward(1); 381 return true; 382 default: 383 return views::WidgetDelegateView::AcceleratorPressed(accelerator); 384 } 385 } 386 387 private: 388 // Hold a reference of Shell for deleting it when the window is closing 389 Shell* shell_; 390 391 // Window title 392 base::string16 title_; 393 394 // Toolbar view contains forward/backward/reload button and URL entry 395 View* toolbar_view_; 396 views::LabelButton* back_button_; 397 views::LabelButton* forward_button_; 398 views::LabelButton* refresh_button_; 399 views::LabelButton* stop_button_; 400 views::Textfield* url_entry_; 401 scoped_ptr<ContextMenuModel> context_menu_model_; 402 scoped_ptr<views::MenuRunner> context_menu_runner_; 403 404 // Contents view contains the web contents view 405 View* contents_view_; 406 views::WebView* web_view_; 407 408 DISALLOW_COPY_AND_ASSIGN(ShellWindowDelegateView); 409}; 410 411} // namespace 412 413#if defined(OS_CHROMEOS) 414wm::WMTestHelper* Shell::wm_test_helper_ = NULL; 415gfx::Screen* Shell::test_screen_ = NULL; 416#endif 417views::ViewsDelegate* Shell::views_delegate_ = NULL; 418 419// static 420void Shell::PlatformInitialize(const gfx::Size& default_window_size) { 421#if defined(OS_WIN) 422 _setmode(_fileno(stdout), _O_BINARY); 423 _setmode(_fileno(stderr), _O_BINARY); 424#endif 425#if defined(OS_CHROMEOS) 426 chromeos::DBusThreadManager::Initialize(); 427 test_screen_ = aura::TestScreen::Create(gfx::Size()); 428 gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, test_screen_); 429 wm_test_helper_ = new wm::WMTestHelper(default_window_size, 430 GetContextFactory()); 431#else 432 gfx::Screen::SetScreenInstance( 433 gfx::SCREEN_TYPE_NATIVE, views::CreateDesktopScreen()); 434#endif 435 views_delegate_ = new ShellViewsDelegateAura(); 436} 437 438void Shell::PlatformExit() { 439#if defined(OS_CHROMEOS) 440 delete wm_test_helper_; 441 wm_test_helper_ = NULL; 442 443 delete test_screen_; 444 test_screen_ = NULL; 445#endif 446 delete views_delegate_; 447 views_delegate_ = NULL; 448 delete platform_; 449 platform_ = NULL; 450#if defined(OS_CHROMEOS) 451 chromeos::DBusThreadManager::Shutdown(); 452#endif 453 aura::Env::DeleteInstance(); 454} 455 456void Shell::PlatformCleanUp() { 457} 458 459void Shell::PlatformEnableUIControl(UIControl control, bool is_enabled) { 460 if (headless_) 461 return; 462 ShellWindowDelegateView* delegate_view = 463 static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate()); 464 if (control == BACK_BUTTON) { 465 delegate_view->EnableUIControl(ShellWindowDelegateView::BACK_BUTTON, 466 is_enabled); 467 } else if (control == FORWARD_BUTTON) { 468 delegate_view->EnableUIControl(ShellWindowDelegateView::FORWARD_BUTTON, 469 is_enabled); 470 } else if (control == STOP_BUTTON) { 471 delegate_view->EnableUIControl(ShellWindowDelegateView::STOP_BUTTON, 472 is_enabled); 473 } 474} 475 476void Shell::PlatformSetAddressBarURL(const GURL& url) { 477 if (headless_) 478 return; 479 ShellWindowDelegateView* delegate_view = 480 static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate()); 481 delegate_view->SetAddressBarURL(url); 482} 483 484void Shell::PlatformSetIsLoading(bool loading) { 485} 486 487void Shell::PlatformCreateWindow(int width, int height) { 488 if (headless_) { 489 content_size_ = gfx::Size(width, height); 490 if (!platform_) 491 platform_ = new ShellPlatformDataAura(content_size_); 492 else 493 platform_->ResizeWindow(content_size_); 494 return; 495 } 496#if defined(OS_CHROMEOS) 497 window_widget_ = views::Widget::CreateWindowWithContextAndBounds( 498 new ShellWindowDelegateView(this), 499 wm_test_helper_->GetDefaultParent(NULL, NULL, gfx::Rect()), 500 gfx::Rect(0, 0, width, height)); 501#else 502 window_widget_ = new views::Widget; 503 views::Widget::InitParams params; 504 params.bounds = gfx::Rect(0, 0, width, height); 505 params.delegate = new ShellWindowDelegateView(this); 506 window_widget_->Init(params); 507#endif 508 509 content_size_ = gfx::Size(width, height); 510 511 window_ = window_widget_->GetNativeWindow(); 512 // Call ShowRootWindow on RootWindow created by WMTestHelper without 513 // which XWindow owned by RootWindow doesn't get mapped. 514 window_->GetHost()->Show(); 515 window_widget_->Show(); 516} 517 518void Shell::PlatformSetContents() { 519 if (headless_) { 520 CHECK(platform_); 521 aura::Window* content = web_contents_->GetNativeView(); 522 aura::Window* parent = platform_->host()->window(); 523 if (!parent->Contains(content)) { 524 parent->AddChild(content); 525 content->Show(); 526 } 527 content->SetBounds(gfx::Rect(content_size_)); 528 RenderWidgetHostView* host_view = web_contents_->GetRenderWidgetHostView(); 529 if (host_view) 530 host_view->SetSize(content_size_); 531 } else { 532 views::WidgetDelegate* widget_delegate = window_widget_->widget_delegate(); 533 ShellWindowDelegateView* delegate_view = 534 static_cast<ShellWindowDelegateView*>(widget_delegate); 535 delegate_view->SetWebContents(web_contents_.get(), content_size_); 536 } 537} 538 539void Shell::PlatformResizeSubViews() { 540} 541 542void Shell::Close() { 543 if (headless_) 544 delete this; 545 else 546 window_widget_->CloseNow(); 547} 548 549void Shell::PlatformSetTitle(const base::string16& title) { 550 if (headless_) 551 return; 552 ShellWindowDelegateView* delegate_view = 553 static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate()); 554 delegate_view->SetWindowTitle(title); 555 window_widget_->UpdateWindowTitle(); 556} 557 558bool Shell::PlatformHandleContextMenu( 559 const content::ContextMenuParams& params) { 560 if (headless_) 561 return true; 562 ShellWindowDelegateView* delegate_view = 563 static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate()); 564 delegate_view->ShowWebViewContextMenu(params); 565 return true; 566} 567 568void Shell::PlatformWebContentsFocused(WebContents* contents) { 569 if (headless_) 570 return; 571 ShellWindowDelegateView* delegate_view = 572 static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate()); 573 delegate_view->OnWebContentsFocused(contents); 574} 575 576} // namespace content 577