| // Copyright 2013 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 "content/shell/browser/shell.h" | 
 |  | 
 | #include <stddef.h> | 
 |  | 
 | #include "base/auto_reset.h" | 
 | #include "base/command_line.h" | 
 | #include "base/location.h" | 
 | #include "base/macros.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/single_thread_task_runner.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "build/build_config.h" | 
 | #include "content/public/browser/devtools_agent_host.h" | 
 | #include "content/public/browser/navigation_controller.h" | 
 | #include "content/public/browser/navigation_entry.h" | 
 | #include "content/public/browser/render_view_host.h" | 
 | #include "content/public/browser/render_widget_host.h" | 
 | #include "content/public/browser/web_contents.h" | 
 | #include "content/public/browser/web_contents_observer.h" | 
 | #include "content/public/common/content_switches.h" | 
 | #include "content/public/common/renderer_preferences.h" | 
 | #include "content/public/common/webrtc_ip_handling_policy.h" | 
 | #include "content/shell/browser/layout_test/blink_test_controller.h" | 
 | #include "content/shell/browser/layout_test/layout_test_bluetooth_chooser_factory.h" | 
 | #include "content/shell/browser/layout_test/layout_test_devtools_frontend.h" | 
 | #include "content/shell/browser/layout_test/layout_test_javascript_dialog_manager.h" | 
 | #include "content/shell/browser/layout_test/secondary_test_window_observer.h" | 
 | #include "content/shell/browser/shell_browser_main_parts.h" | 
 | #include "content/shell/browser/shell_content_browser_client.h" | 
 | #include "content/shell/browser/shell_devtools_frontend.h" | 
 | #include "content/shell/browser/shell_javascript_dialog_manager.h" | 
 | #include "content/shell/common/shell_messages.h" | 
 | #include "content/shell/common/shell_switches.h" | 
 | #include "media/media_features.h" | 
 |  | 
 | namespace content { | 
 |  | 
 | const int kDefaultTestWindowWidthDip = 800; | 
 | const int kDefaultTestWindowHeightDip = 600; | 
 |  | 
 | std::vector<Shell*> Shell::windows_; | 
 | base::Callback<void(Shell*)> Shell::shell_created_callback_; | 
 |  | 
 | bool Shell::quit_message_loop_ = true; | 
 |  | 
 | class Shell::DevToolsWebContentsObserver : public WebContentsObserver { | 
 |  public: | 
 |   DevToolsWebContentsObserver(Shell* shell, WebContents* web_contents) | 
 |       : WebContentsObserver(web_contents), | 
 |         shell_(shell) { | 
 |   } | 
 |  | 
 |   // WebContentsObserver | 
 |   void WebContentsDestroyed() override { | 
 |     shell_->OnDevToolsWebContentsDestroyed(); | 
 |   } | 
 |  | 
 |  private: | 
 |   Shell* shell_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(DevToolsWebContentsObserver); | 
 | }; | 
 |  | 
 | Shell::Shell(WebContents* web_contents) | 
 |     : WebContentsObserver(web_contents), | 
 |       devtools_frontend_(NULL), | 
 |       is_fullscreen_(false), | 
 |       window_(NULL), | 
 | #if defined(OS_MACOSX) | 
 |       url_edit_view_(NULL), | 
 | #endif | 
 |       headless_(false) { | 
 |   if (switches::IsRunLayoutTestSwitchPresent()) | 
 |     headless_ = true; | 
 |   windows_.push_back(this); | 
 |  | 
 |   if (!shell_created_callback_.is_null()) { | 
 |     shell_created_callback_.Run(this); | 
 |     shell_created_callback_.Reset(); | 
 |   } | 
 | } | 
 |  | 
 | Shell::~Shell() { | 
 |   PlatformCleanUp(); | 
 |  | 
 |   for (size_t i = 0; i < windows_.size(); ++i) { | 
 |     if (windows_[i] == this) { | 
 |       windows_.erase(windows_.begin() + i); | 
 |       break; | 
 |     } | 
 |   } | 
 |  | 
 |   if (windows_.empty() && quit_message_loop_) { | 
 |     if (headless_) | 
 |       PlatformExit(); | 
 |     base::ThreadTaskRunnerHandle::Get()->PostTask( | 
 |         FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); | 
 |   } | 
 | } | 
 |  | 
 | Shell* Shell::CreateShell(WebContents* web_contents, | 
 |                           const gfx::Size& initial_size) { | 
 |   Shell* shell = new Shell(web_contents); | 
 |   shell->PlatformCreateWindow(initial_size.width(), initial_size.height()); | 
 |  | 
 |   shell->web_contents_.reset(web_contents); | 
 |   web_contents->SetDelegate(shell); | 
 |  | 
 |   shell->PlatformSetContents(); | 
 |  | 
 |   shell->PlatformResizeSubViews(); | 
 |  | 
 |   // Note: Do not make RenderFrameHost or RenderViewHost specific state changes | 
 |   // here, because they will be forgotten after a cross-process navigation. Use | 
 |   // RenderFrameCreated or RenderViewCreated instead. | 
 |   if (switches::IsRunLayoutTestSwitchPresent()) { | 
 |     web_contents->GetMutableRendererPrefs()->use_custom_colors = false; | 
 |     web_contents->GetRenderViewHost()->SyncRendererPrefs(); | 
 |   } | 
 |  | 
 | #if BUILDFLAG(ENABLE_WEBRTC) | 
 |   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | 
 |   if (command_line->HasSwitch(switches::kForceWebRtcIPHandlingPolicy)) { | 
 |     web_contents->GetMutableRendererPrefs()->webrtc_ip_handling_policy = | 
 |         command_line->GetSwitchValueASCII( | 
 |             switches::kForceWebRtcIPHandlingPolicy); | 
 |   } | 
 | #endif | 
 |  | 
 |   return shell; | 
 | } | 
 |  | 
 | void Shell::CloseAllWindows() { | 
 |   base::AutoReset<bool> auto_reset(&quit_message_loop_, false); | 
 |   DevToolsAgentHost::DetachAllClients(); | 
 |   std::vector<Shell*> open_windows(windows_); | 
 |   for (size_t i = 0; i < open_windows.size(); ++i) | 
 |     open_windows[i]->Close(); | 
 |   base::RunLoop().RunUntilIdle(); | 
 |   PlatformExit(); | 
 | } | 
 |  | 
 | void Shell::SetShellCreatedCallback( | 
 |     base::Callback<void(Shell*)> shell_created_callback) { | 
 |   DCHECK(shell_created_callback_.is_null()); | 
 |   shell_created_callback_ = shell_created_callback; | 
 | } | 
 |  | 
 | Shell* Shell::FromRenderViewHost(RenderViewHost* rvh) { | 
 |   for (size_t i = 0; i < windows_.size(); ++i) { | 
 |     if (windows_[i]->web_contents() && | 
 |         windows_[i]->web_contents()->GetRenderViewHost() == rvh) { | 
 |       return windows_[i]; | 
 |     } | 
 |   } | 
 |   return NULL; | 
 | } | 
 |  | 
 | // static | 
 | void Shell::Initialize() { | 
 |   PlatformInitialize(GetShellDefaultSize()); | 
 | } | 
 |  | 
 | gfx::Size Shell::AdjustWindowSize(const gfx::Size& initial_size) { | 
 |   if (!initial_size.IsEmpty()) | 
 |     return initial_size; | 
 |   return GetShellDefaultSize(); | 
 | } | 
 |  | 
 | Shell* Shell::CreateNewWindow(BrowserContext* browser_context, | 
 |                               const GURL& url, | 
 |                               const scoped_refptr<SiteInstance>& site_instance, | 
 |                               const gfx::Size& initial_size) { | 
 |   WebContents::CreateParams create_params(browser_context, site_instance); | 
 |   create_params.initial_size = AdjustWindowSize(initial_size); | 
 |   WebContents* web_contents = WebContents::Create(create_params); | 
 |   Shell* shell = CreateShell(web_contents, create_params.initial_size); | 
 |   if (!url.is_empty()) | 
 |     shell->LoadURL(url); | 
 |   return shell; | 
 | } | 
 |  | 
 | void Shell::LoadURL(const GURL& url) { | 
 |   LoadURLForFrame(url, std::string()); | 
 | } | 
 |  | 
 | void Shell::LoadURLForFrame(const GURL& url, const std::string& frame_name) { | 
 |   NavigationController::LoadURLParams params(url); | 
 |   params.transition_type = ui::PageTransitionFromInt( | 
 |       ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); | 
 |   params.frame_name = frame_name; | 
 |   web_contents_->GetController().LoadURLWithParams(params); | 
 |   web_contents_->Focus(); | 
 | } | 
 |  | 
 | void Shell::LoadDataWithBaseURL(const GURL& url, const std::string& data, | 
 |     const GURL& base_url) { | 
 |   bool load_as_string = false; | 
 |   LoadDataWithBaseURLInternal(url, data, base_url, load_as_string); | 
 | } | 
 |  | 
 | #if defined(OS_ANDROID) | 
 | void Shell::LoadDataAsStringWithBaseURL(const GURL& url, | 
 |                                         const std::string& data, | 
 |                                         const GURL& base_url) { | 
 |   bool load_as_string = true; | 
 |   LoadDataWithBaseURLInternal(url, data, base_url, load_as_string); | 
 | } | 
 | #endif | 
 |  | 
 | void Shell::LoadDataWithBaseURLInternal(const GURL& url, | 
 |                                         const std::string& data, | 
 |                                         const GURL& base_url, | 
 |                                         bool load_as_string) { | 
 | #if !defined(OS_ANDROID) | 
 |   DCHECK(!load_as_string);  // Only supported on Android. | 
 | #endif | 
 |  | 
 |   NavigationController::LoadURLParams params(GURL::EmptyGURL()); | 
 |   const std::string data_url_header = "data:text/html;charset=utf-8,"; | 
 |   if (load_as_string) { | 
 |     params.url = GURL(data_url_header); | 
 |     std::string data_url_as_string = data_url_header + data; | 
 | #if defined(OS_ANDROID) | 
 |     params.data_url_as_string = | 
 |         base::RefCountedString::TakeString(&data_url_as_string); | 
 | #endif | 
 |   } else { | 
 |     params.url = GURL(data_url_header + data); | 
 |   } | 
 |  | 
 |   params.load_type = NavigationController::LOAD_TYPE_DATA; | 
 |   params.base_url_for_data_url = base_url; | 
 |   params.virtual_url_for_data_url = url; | 
 |   params.override_user_agent = NavigationController::UA_OVERRIDE_FALSE; | 
 |   web_contents_->GetController().LoadURLWithParams(params); | 
 |   web_contents_->Focus(); | 
 | } | 
 |  | 
 | void Shell::AddNewContents(WebContents* source, | 
 |                            WebContents* new_contents, | 
 |                            WindowOpenDisposition disposition, | 
 |                            const gfx::Rect& initial_rect, | 
 |                            bool user_gesture, | 
 |                            bool* was_blocked) { | 
 |   CreateShell(new_contents, AdjustWindowSize(initial_rect.size())); | 
 |   if (switches::IsRunLayoutTestSwitchPresent()) | 
 |     SecondaryTestWindowObserver::CreateForWebContents(new_contents); | 
 | } | 
 |  | 
 | void Shell::GoBackOrForward(int offset) { | 
 |   web_contents_->GetController().GoToOffset(offset); | 
 |   web_contents_->Focus(); | 
 | } | 
 |  | 
 | void Shell::Reload() { | 
 |   web_contents_->GetController().Reload(false); | 
 |   web_contents_->Focus(); | 
 | } | 
 |  | 
 | void Shell::ReloadBypassingCache() { | 
 |   web_contents_->GetController().ReloadBypassingCache(false); | 
 |   web_contents_->Focus(); | 
 | } | 
 |  | 
 | void Shell::Stop() { | 
 |   web_contents_->Stop(); | 
 |   web_contents_->Focus(); | 
 | } | 
 |  | 
 | void Shell::UpdateNavigationControls(bool to_different_document) { | 
 |   int current_index = web_contents_->GetController().GetCurrentEntryIndex(); | 
 |   int max_index = web_contents_->GetController().GetEntryCount() - 1; | 
 |  | 
 |   PlatformEnableUIControl(BACK_BUTTON, current_index > 0); | 
 |   PlatformEnableUIControl(FORWARD_BUTTON, current_index < max_index); | 
 |   PlatformEnableUIControl(STOP_BUTTON, | 
 |       to_different_document && web_contents_->IsLoading()); | 
 | } | 
 |  | 
 | void Shell::ShowDevTools() { | 
 |   if (!devtools_frontend_) { | 
 |     devtools_frontend_ = ShellDevToolsFrontend::Show(web_contents()); | 
 |     devtools_observer_.reset(new DevToolsWebContentsObserver( | 
 |         this, devtools_frontend_->frontend_shell()->web_contents())); | 
 |   } | 
 |  | 
 |   devtools_frontend_->Activate(); | 
 |   devtools_frontend_->Focus(); | 
 | } | 
 |  | 
 | void Shell::CloseDevTools() { | 
 |   if (!devtools_frontend_) | 
 |     return; | 
 |   devtools_observer_.reset(); | 
 |   devtools_frontend_->Close(); | 
 |   devtools_frontend_ = NULL; | 
 | } | 
 |  | 
 | gfx::NativeView Shell::GetContentView() { | 
 |   if (!web_contents_) | 
 |     return NULL; | 
 |   return web_contents_->GetNativeView(); | 
 | } | 
 |  | 
 | WebContents* Shell::OpenURLFromTab(WebContents* source, | 
 |                                    const OpenURLParams& params) { | 
 |   WebContents* target = nullptr; | 
 |   switch (params.disposition) { | 
 |     case WindowOpenDisposition::CURRENT_TAB: | 
 |       target = source; | 
 |       break; | 
 |  | 
 |     // Normally, the difference between NEW_POPUP and NEW_WINDOW is that a popup | 
 |     // should have no toolbar, no status bar, no menu bar, no scrollbars and be | 
 |     // not resizable.  For simplicity and to enable new testing scenarios in | 
 |     // content shell and layout tests, popups don't get special treatment below | 
 |     // (i.e. they will have a toolbar and other things described here). | 
 |     case WindowOpenDisposition::NEW_POPUP: | 
 |     case WindowOpenDisposition::NEW_WINDOW: { | 
 |       Shell* new_window = | 
 |           Shell::CreateNewWindow(source->GetBrowserContext(), | 
 |                                  GURL(),  // Don't load anything just yet. | 
 |                                  params.source_site_instance, | 
 |                                  gfx::Size());  // Use default size. | 
 |       target = new_window->web_contents(); | 
 |       if (switches::IsRunLayoutTestSwitchPresent()) | 
 |         SecondaryTestWindowObserver::CreateForWebContents(target); | 
 |       break; | 
 |     } | 
 |  | 
 |     // No tabs in content_shell: | 
 |     case WindowOpenDisposition::SINGLETON_TAB: | 
 |     case WindowOpenDisposition::NEW_FOREGROUND_TAB: | 
 |     case WindowOpenDisposition::NEW_BACKGROUND_TAB: | 
 |     // No incognito mode in content_shell: | 
 |     case WindowOpenDisposition::OFF_THE_RECORD: | 
 |     // TODO(lukasza): Investigate if some layout tests might need support for | 
 |     // SAVE_TO_DISK disposition.  This would probably require that | 
 |     // BlinkTestController always sets up and cleans up a temporary directory | 
 |     // as the default downloads destinations for the duration of a test. | 
 |     case WindowOpenDisposition::SAVE_TO_DISK: | 
 |     // Ignoring requests with disposition == IGNORE_ACTION... | 
 |     case WindowOpenDisposition::IGNORE_ACTION: | 
 |     default: | 
 |       return nullptr; | 
 |   } | 
 |  | 
 |   NavigationController::LoadURLParams load_url_params(params.url); | 
 |   load_url_params.source_site_instance = params.source_site_instance; | 
 |   load_url_params.transition_type = params.transition; | 
 |   load_url_params.frame_tree_node_id = params.frame_tree_node_id; | 
 |   load_url_params.referrer = params.referrer; | 
 |   load_url_params.redirect_chain = params.redirect_chain; | 
 |   load_url_params.extra_headers = params.extra_headers; | 
 |   load_url_params.is_renderer_initiated = params.is_renderer_initiated; | 
 |   load_url_params.should_replace_current_entry = | 
 |       params.should_replace_current_entry; | 
 |  | 
 |   if (params.uses_post) { | 
 |     load_url_params.load_type = NavigationController::LOAD_TYPE_HTTP_POST; | 
 |     load_url_params.post_data = params.post_data; | 
 |   } | 
 |  | 
 |   target->GetController().LoadURLWithParams(load_url_params); | 
 |   return target; | 
 | } | 
 |  | 
 | void Shell::LoadingStateChanged(WebContents* source, | 
 |     bool to_different_document) { | 
 |   UpdateNavigationControls(to_different_document); | 
 |   PlatformSetIsLoading(source->IsLoading()); | 
 | } | 
 |  | 
 | void Shell::EnterFullscreenModeForTab(WebContents* web_contents, | 
 |                                       const GURL& origin) { | 
 |   ToggleFullscreenModeForTab(web_contents, true); | 
 | } | 
 |  | 
 | void Shell::ExitFullscreenModeForTab(WebContents* web_contents) { | 
 |   ToggleFullscreenModeForTab(web_contents, false); | 
 | } | 
 |  | 
 | void Shell::ToggleFullscreenModeForTab(WebContents* web_contents, | 
 |                                        bool enter_fullscreen) { | 
 | #if defined(OS_ANDROID) | 
 |   PlatformToggleFullscreenModeForTab(web_contents, enter_fullscreen); | 
 | #endif | 
 |   if (!switches::IsRunLayoutTestSwitchPresent()) | 
 |     return; | 
 |   if (is_fullscreen_ != enter_fullscreen) { | 
 |     is_fullscreen_ = enter_fullscreen; | 
 |     web_contents->GetRenderViewHost()->GetWidget()->WasResized(); | 
 |   } | 
 | } | 
 |  | 
 | bool Shell::IsFullscreenForTabOrPending(const WebContents* web_contents) const { | 
 | #if defined(OS_ANDROID) | 
 |   return PlatformIsFullscreenForTabOrPending(web_contents); | 
 | #else | 
 |   return is_fullscreen_; | 
 | #endif | 
 | } | 
 |  | 
 | blink::WebDisplayMode Shell::GetDisplayMode( | 
 |     const WebContents* web_contents) const { | 
 |  // TODO : should return blink::WebDisplayModeFullscreen wherever user puts | 
 |  // a browser window into fullscreen (not only in case of renderer-initiated | 
 |  // fullscreen mode): crbug.com/476874. | 
 |  return IsFullscreenForTabOrPending(web_contents) ? | 
 |      blink::WebDisplayModeFullscreen : blink::WebDisplayModeBrowser; | 
 | } | 
 |  | 
 | void Shell::RequestToLockMouse(WebContents* web_contents, | 
 |                                bool user_gesture, | 
 |                                bool last_unlocked_by_target) { | 
 |   web_contents->GotResponseToLockMouseRequest(true); | 
 | } | 
 |  | 
 | void Shell::CloseContents(WebContents* source) { | 
 |   Close(); | 
 | } | 
 |  | 
 | bool Shell::CanOverscrollContent() const { | 
 | #if defined(USE_AURA) | 
 |   return true; | 
 | #else | 
 |   return false; | 
 | #endif | 
 | } | 
 |  | 
 | void Shell::DidNavigateMainFramePostCommit(WebContents* web_contents) { | 
 |   PlatformSetAddressBarURL(web_contents->GetLastCommittedURL()); | 
 | } | 
 |  | 
 | JavaScriptDialogManager* Shell::GetJavaScriptDialogManager( | 
 |     WebContents* source) { | 
 |   if (!dialog_manager_) { | 
 |     dialog_manager_.reset(switches::IsRunLayoutTestSwitchPresent() | 
 |                               ? new LayoutTestJavaScriptDialogManager | 
 |                               : new ShellJavaScriptDialogManager); | 
 |   } | 
 |   return dialog_manager_.get(); | 
 | } | 
 |  | 
 | std::unique_ptr<BluetoothChooser> Shell::RunBluetoothChooser( | 
 |     RenderFrameHost* frame, | 
 |     const BluetoothChooser::EventHandler& event_handler) { | 
 |   if (switches::IsRunLayoutTestSwitchPresent()) { | 
 |     return BlinkTestController::Get()->RunBluetoothChooser(frame, | 
 |                                                            event_handler); | 
 |   } | 
 |   return nullptr; | 
 | } | 
 |  | 
 | bool Shell::DidAddMessageToConsole(WebContents* source, | 
 |                                    int32_t level, | 
 |                                    const base::string16& message, | 
 |                                    int32_t line_no, | 
 |                                    const base::string16& source_id) { | 
 |   return switches::IsRunLayoutTestSwitchPresent(); | 
 | } | 
 |  | 
 | void Shell::RendererUnresponsive( | 
 |     WebContents* source, | 
 |     const WebContentsUnresponsiveState& unresponsive_state) { | 
 |   if (switches::IsRunLayoutTestSwitchPresent()) | 
 |     BlinkTestController::Get()->RendererUnresponsive(); | 
 | } | 
 |  | 
 | void Shell::ActivateContents(WebContents* contents) { | 
 |   contents->GetRenderViewHost()->GetWidget()->Focus(); | 
 | } | 
 |  | 
 | gfx::Size Shell::GetShellDefaultSize() { | 
 |   static gfx::Size default_shell_size; | 
 |   if (!default_shell_size.IsEmpty()) | 
 |     return default_shell_size; | 
 |   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | 
 |   if (command_line->HasSwitch(switches::kContentShellHostWindowSize)) { | 
 |     const std::string size_str = command_line->GetSwitchValueASCII( | 
 |                   switches::kContentShellHostWindowSize); | 
 |     int width, height; | 
 |     CHECK_EQ(2, sscanf(size_str.c_str(), "%dx%d", &width, &height)); | 
 |     default_shell_size = gfx::Size(width, height); | 
 |   } else { | 
 |     default_shell_size = gfx::Size( | 
 |       kDefaultTestWindowWidthDip, kDefaultTestWindowHeightDip); | 
 |   } | 
 |   return default_shell_size; | 
 | } | 
 |  | 
 | void Shell::TitleWasSet(NavigationEntry* entry, bool explicit_set) { | 
 |   if (entry) | 
 |     PlatformSetTitle(entry->GetTitle()); | 
 | } | 
 |  | 
 | void Shell::OnDevToolsWebContentsDestroyed() { | 
 |   devtools_observer_.reset(); | 
 |   devtools_frontend_ = NULL; | 
 | } | 
 |  | 
 | }  // namespace content |