|  | // 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/message_loop/message_loop.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_bindings.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/layout_test/layout_test_switches.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), | 
|  | web_contents_(web_contents), | 
|  | devtools_frontend_(NULL), | 
|  | is_fullscreen_(false), | 
|  | window_(NULL), | 
|  | #if defined(OS_MACOSX) | 
|  | url_edit_view_(NULL), | 
|  | #endif | 
|  | headless_(false) { | 
|  | web_contents_->SetDelegate(this); | 
|  |  | 
|  | 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()); | 
|  | } | 
|  |  | 
|  | web_contents_->SetDelegate(nullptr); | 
|  | } | 
|  |  | 
|  | 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->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(ReloadType::NORMAL, false); | 
|  | web_contents_->Focus(); | 
|  | } | 
|  |  | 
|  | void Shell::ReloadBypassingCache() { | 
|  | web_contents_->GetController().Reload(ReloadType::BYPASSING_CACHE, 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: | 
|  | // content_shell doesn't really support tabs, but some layout tests use | 
|  | // middle click (which translates into kNavigationPolicyNewBackgroundTab), | 
|  | // so we treat the cases below just like a NEW_WINDOW disposition. | 
|  | case WindowOpenDisposition::NEW_BACKGROUND_TAB: | 
|  | case WindowOpenDisposition::NEW_FOREGROUND_TAB: { | 
|  | 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: | 
|  | // 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::kWebDisplayModeFullscreen | 
|  | : blink::kWebDisplayModeBrowser; | 
|  | } | 
|  |  | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | bool Shell::ShouldAllowRunningInsecureContent( | 
|  | content::WebContents* web_contents, | 
|  | bool allowed_per_prefs, | 
|  | const url::Origin& origin, | 
|  | const GURL& resource_url) { | 
|  | bool allowed_by_test = false; | 
|  | if (base::CommandLine::ForCurrentProcess()->HasSwitch( | 
|  | switches::kRunLayoutTest)) { | 
|  | const base::DictionaryValue& test_flags = | 
|  | BlinkTestController::Get() | 
|  | ->accumulated_layout_test_runtime_flags_changes(); | 
|  | test_flags.GetBoolean("running_insecure_content_allowed", &allowed_by_test); | 
|  | } | 
|  |  | 
|  | return allowed_per_prefs || allowed_by_test; | 
|  | } | 
|  |  | 
|  | 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 |