| // Copyright 2015 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 "blimp/engine/session/blimp_engine_session.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/trace_event/trace_event.h" |
| #include "blimp/common/blob_cache/in_memory_blob_cache.h" |
| #include "blimp/common/create_blimp_message.h" |
| #include "blimp/common/proto/tab_control.pb.h" |
| #include "blimp/engine/app/blimp_engine_config.h" |
| #include "blimp/engine/app/settings_manager.h" |
| #include "blimp/engine/app/switches.h" |
| #include "blimp/engine/app/ui/blimp_layout_manager.h" |
| #include "blimp/engine/app/ui/blimp_screen.h" |
| #include "blimp/engine/app/ui/blimp_window_tree_client.h" |
| #include "blimp/engine/app/ui/blimp_window_tree_host.h" |
| #include "blimp/engine/common/blimp_browser_context.h" |
| #include "blimp/engine/common/blimp_user_agent.h" |
| #include "blimp/engine/mojo/blob_channel_service.h" |
| #include "blimp/engine/session/tab.h" |
| #include "blimp/net/blimp_connection.h" |
| #include "blimp/net/blimp_message_multiplexer.h" |
| #include "blimp/net/blimp_message_thread_pipe.h" |
| #include "blimp/net/blimp_stats.h" |
| #include "blimp/net/blob_channel/blob_channel_sender_impl.h" |
| #include "blimp/net/blob_channel/helium_blob_sender_delegate.h" |
| #include "blimp/net/browser_connection_handler.h" |
| #include "blimp/net/common.h" |
| #include "blimp/net/engine_authentication_handler.h" |
| #include "blimp/net/engine_connection_manager.h" |
| #include "blimp/net/null_blimp_message_processor.h" |
| #include "blimp/net/tcp_engine_transport.h" |
| #include "blimp/net/thread_pipe_manager.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.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/render_widget_host_view.h" |
| #include "content/public/browser/web_contents.h" |
| #include "device/geolocation/geolocation_delegate.h" |
| #include "device/geolocation/geolocation_provider.h" |
| #include "net/base/ip_address.h" |
| #include "net/base/net_errors.h" |
| #include "ui/aura/client/default_capture_client.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/ime/input_method.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/wm/core/base_focus_rules.h" |
| #include "ui/wm/core/default_activation_client.h" |
| #include "ui/wm/core/focus_controller.h" |
| |
| namespace blimp { |
| namespace engine { |
| namespace { |
| |
| const int kDummyTabId = 0; |
| const float kDefaultScaleFactor = 1.f; |
| const int kDefaultDisplayWidth = 800; |
| const int kDefaultDisplayHeight = 600; |
| const uint16_t kDefaultPort = 25467; |
| |
| // Focus rules that support activating an child window. |
| class FocusRulesImpl : public wm::BaseFocusRules { |
| public: |
| FocusRulesImpl() {} |
| ~FocusRulesImpl() override {} |
| |
| bool SupportsChildActivation(aura::Window* window) const override { |
| return true; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(FocusRulesImpl); |
| }; |
| |
| // Proxies calls to TaskRunner::PostTask while stripping the return value, |
| // which provides a suitable function prototype for binding a base::Closure. |
| void PostTask(const scoped_refptr<base::TaskRunner>& task_runner, |
| const base::Closure& closure) { |
| task_runner->PostTask(FROM_HERE, closure); |
| } |
| |
| // Returns a closure that quits the current (bind-time) MessageLoop. |
| base::Closure QuitCurrentMessageLoopClosure() { |
| return base::Bind(&PostTask, base::ThreadTaskRunnerHandle::Get(), |
| base::MessageLoop::QuitWhenIdleClosure()); |
| } |
| |
| net::IPAddress GetListeningAddress() { |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch(kAllowNonLocalhost)) { |
| return net::IPAddress::IPv4AllZeros(); |
| } |
| return net::IPAddress::IPv4Localhost(); |
| } |
| |
| uint16_t GetListeningPort() { |
| unsigned port_parsed = 0; |
| if (!base::StringToUint( |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| kEnginePort), |
| &port_parsed)) { |
| return kDefaultPort; |
| } |
| if (port_parsed > 65535) { |
| LOG(FATAL) << "--engine-port must be a value between 0 and 65535."; |
| return kDefaultPort; |
| } |
| return port_parsed; |
| } |
| |
| } // namespace |
| |
| // EngineNetworkComponents is created by the BlimpEngineSession on the UI |
| // thread, and then used and destroyed on the IO thread. |
| class EngineNetworkComponents : public ConnectionHandler, |
| public ConnectionErrorObserver { |
| public: |
| // |net_log|: The log to use for network-related events. |
| explicit EngineNetworkComponents(net::NetLog* net_log); |
| ~EngineNetworkComponents() override; |
| |
| // Sets up network components and starts listening for incoming connection. |
| // This should be called after all features have been registered so that |
| // received messages can be properly handled. |
| void Initialize(scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, |
| base::WeakPtr<BlobChannelSender> blob_channel_sender, |
| const std::string& client_token); |
| |
| uint16_t GetPortForTesting() { return port_; } |
| |
| BrowserConnectionHandler* connection_handler() { |
| return &connection_handler_; |
| } |
| |
| BlobChannelService* blob_channel_service() { |
| return blob_channel_service_.get(); |
| } |
| |
| private: |
| // ConnectionHandler implementation. |
| void HandleConnection(std::unique_ptr<BlimpConnection> connection) override; |
| |
| // ConnectionErrorObserver implementation. |
| // Signals the engine session that an authenticated connection was |
| // terminated. |
| void OnConnectionError(int error) override; |
| |
| net::NetLog* net_log_; |
| uint16_t port_ = 0; |
| |
| BrowserConnectionHandler connection_handler_; |
| std::unique_ptr<EngineAuthenticationHandler> authentication_handler_; |
| std::unique_ptr<EngineConnectionManager> connection_manager_; |
| std::unique_ptr<BlobChannelService> blob_channel_service_; |
| base::Closure quit_closure_; |
| |
| DISALLOW_COPY_AND_ASSIGN(EngineNetworkComponents); |
| }; |
| |
| EngineNetworkComponents::EngineNetworkComponents(net::NetLog* net_log) |
| : net_log_(net_log), quit_closure_(QuitCurrentMessageLoopClosure()) {} |
| |
| EngineNetworkComponents::~EngineNetworkComponents() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| } |
| |
| void EngineNetworkComponents::Initialize( |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, |
| base::WeakPtr<BlobChannelSender> blob_channel_sender, |
| const std::string& client_token) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| DCHECK(!connection_manager_); |
| // Plumb authenticated connections from the authentication handler |
| // to |this| (which will then pass it to |connection_handler_|. |
| authentication_handler_ = |
| base::MakeUnique<EngineAuthenticationHandler>(this, client_token); |
| |
| // Plumb unauthenticated connections to |authentication_handler_|. |
| connection_manager_ = |
| base::MakeUnique<EngineConnectionManager>(authentication_handler_.get()); |
| |
| blob_channel_service_ = |
| base::MakeUnique<BlobChannelService>(blob_channel_sender, ui_task_runner); |
| |
| // Adds BlimpTransports to connection_manager_. |
| net::IPEndPoint address(GetListeningAddress(), GetListeningPort()); |
| TCPEngineTransport* transport = new TCPEngineTransport(address, net_log_); |
| connection_manager_->AddTransport(base::WrapUnique(transport)); |
| |
| transport->GetLocalAddress(&address); |
| port_ = address.port(); |
| DVLOG(1) << "Engine port #: " << port_; |
| } |
| |
| void EngineNetworkComponents::HandleConnection( |
| std::unique_ptr<BlimpConnection> connection) { |
| // Observe |connection| for disconnection events. |
| connection->AddConnectionErrorObserver(this); |
| connection_handler_.HandleConnection(std::move(connection)); |
| } |
| |
| void EngineNetworkComponents::OnConnectionError(int error) { |
| DVLOG(1) << "EngineNetworkComponents::OnConnectionError(" << error << ")"; |
| quit_closure_.Run(); |
| } |
| |
| BlimpEngineSession::BlimpEngineSession( |
| std::unique_ptr<BlimpBrowserContext> browser_context, |
| net::NetLog* net_log, |
| BlimpEngineConfig* engine_config, |
| SettingsManager* settings_manager) |
| : screen_(new BlimpScreen), |
| browser_context_(std::move(browser_context)), |
| engine_config_(engine_config), |
| settings_manager_(settings_manager), |
| settings_feature_(settings_manager_), |
| render_widget_feature_(settings_manager_), |
| net_components_(new EngineNetworkComponents(net_log)) { |
| DCHECK(engine_config_); |
| DCHECK(settings_manager_); |
| |
| screen_->UpdateDisplayScaleAndSize( |
| kDefaultScaleFactor, |
| gfx::Size(kDefaultDisplayWidth, kDefaultDisplayHeight)); |
| render_widget_feature_.SetDelegate(kDummyTabId, this); |
| |
| std::unique_ptr<HeliumBlobSenderDelegate> helium_blob_delegate( |
| new HeliumBlobSenderDelegate); |
| blob_delegate_ = helium_blob_delegate.get(); |
| blob_channel_sender_ = base::MakeUnique<BlobChannelSenderImpl>( |
| base::MakeUnique<InMemoryBlobCache>(), std::move(helium_blob_delegate)); |
| blob_channel_sender_weak_factory_ = |
| base::MakeUnique<base::WeakPtrFactory<BlobChannelSenderImpl>>( |
| blob_channel_sender_.get()); |
| |
| device::GeolocationProvider::SetGeolocationDelegate( |
| geolocation_feature_.CreateGeolocationDelegate()); |
| } |
| |
| BlimpEngineSession::~BlimpEngineSession() { |
| render_widget_feature_.RemoveDelegate(kDummyTabId); |
| |
| window_tree_host_->GetInputMethod()->RemoveObserver(this); |
| |
| // Ensure that all tabs are torn down first, since teardown will |
| // trigger RenderViewDeleted callbacks to their observers, which will in turn |
| // send messages to net_components_, which is already deleted due to the line |
| // below. |
| tab_.reset(); |
| |
| // Safely delete network components on the IO thread. |
| content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE, |
| net_components_.release()); |
| } |
| |
| void BlimpEngineSession::Initialize() { |
| DCHECK(!display::Screen::GetScreen()); |
| display::Screen::SetScreenInstance(screen_.get()); |
| |
| window_tree_host_.reset(new BlimpWindowTreeHost()); |
| |
| screen_->set_window_tree_host(window_tree_host_.get()); |
| window_tree_host_->InitHost(); |
| window_tree_host_->window()->SetLayoutManager( |
| new BlimpLayoutManager(window_tree_host_->window())); |
| focus_client_.reset(new wm::FocusController(new FocusRulesImpl)); |
| aura::client::SetFocusClient(window_tree_host_->window(), |
| focus_client_.get()); |
| aura::client::SetActivationClient(window_tree_host_->window(), |
| focus_client_.get()); |
| capture_client_.reset( |
| new aura::client::DefaultCaptureClient(window_tree_host_->window())); |
| |
| window_tree_client_.reset( |
| new BlimpWindowTreeClient(window_tree_host_->window())); |
| |
| window_tree_host_->GetInputMethod()->AddObserver(this); |
| |
| window_tree_host_->SetBounds(gfx::Rect(screen_->GetPrimaryDisplay().size())); |
| |
| RegisterFeatures(); |
| |
| // Initialize must only be posted after the RegisterFeature calls have |
| // completed. |
| content::BrowserThread::PostTask( |
| content::BrowserThread::IO, FROM_HERE, |
| base::Bind(&EngineNetworkComponents::Initialize, |
| base::Unretained(net_components_.get()), |
| base::ThreadTaskRunnerHandle::Get(), |
| blob_channel_sender_weak_factory_->GetWeakPtr(), |
| engine_config_->client_auth_token())); |
| } |
| |
| BlobChannelService* BlimpEngineSession::GetBlobChannelService() { |
| return net_components_->blob_channel_service(); |
| } |
| |
| void BlimpEngineSession::GetEnginePortForTesting( |
| const GetPortCallback& callback) { |
| content::BrowserThread::PostTaskAndReplyWithResult( |
| content::BrowserThread::IO, FROM_HERE, |
| base::Bind(&EngineNetworkComponents::GetPortForTesting, |
| base::Unretained(net_components_.get())), |
| callback); |
| } |
| |
| void BlimpEngineSession::RegisterFeatures() { |
| thread_pipe_manager_.reset( |
| new ThreadPipeManager(content::BrowserThread::GetTaskRunnerForThread( |
| content::BrowserThread::IO), |
| net_components_->connection_handler())); |
| |
| // Register features' message senders and receivers. |
| tab_control_message_sender_ = |
| thread_pipe_manager_->RegisterFeature(BlimpMessage::kTabControl, this); |
| navigation_message_sender_ = |
| thread_pipe_manager_->RegisterFeature(BlimpMessage::kNavigation, this); |
| render_widget_feature_.set_render_widget_message_sender( |
| thread_pipe_manager_->RegisterFeature(BlimpMessage::kRenderWidget, |
| &render_widget_feature_)); |
| render_widget_feature_.set_input_message_sender( |
| thread_pipe_manager_->RegisterFeature(BlimpMessage::kInput, |
| &render_widget_feature_)); |
| render_widget_feature_.set_compositor_message_sender( |
| thread_pipe_manager_->RegisterFeature(BlimpMessage::kCompositor, |
| &render_widget_feature_)); |
| render_widget_feature_.set_ime_message_sender( |
| thread_pipe_manager_->RegisterFeature(BlimpMessage::kIme, |
| &render_widget_feature_)); |
| geolocation_feature_.set_outgoing_message_processor( |
| thread_pipe_manager_->RegisterFeature(BlimpMessage::kGeolocation, |
| &geolocation_feature_)); |
| blob_delegate_->set_outgoing_message_processor( |
| thread_pipe_manager_->RegisterFeature(BlimpMessage::kBlobChannel, |
| blob_delegate_)); |
| |
| // The Settings feature does not need an outgoing message processor, since we |
| // don't send any messages to the client right now. |
| thread_pipe_manager_->RegisterFeature(BlimpMessage::kSettings, |
| &settings_feature_); |
| } |
| |
| bool BlimpEngineSession::CreateTab(const int target_tab_id) { |
| DVLOG(1) << "Create tab " << target_tab_id; |
| if (tab_) { |
| return false; |
| } |
| |
| content::WebContents::CreateParams create_params(browser_context_.get(), |
| nullptr); |
| std::unique_ptr<content::WebContents> new_contents = |
| base::WrapUnique(content::WebContents::Create(create_params)); |
| PlatformSetContents(std::move(new_contents), target_tab_id); |
| |
| return true; |
| } |
| |
| void BlimpEngineSession::CloseTab(const int target_tab_id) { |
| DVLOG(1) << "Close tab " << target_tab_id; |
| tab_.reset(); |
| } |
| |
| void BlimpEngineSession::HandleResize(float device_pixel_ratio, |
| const gfx::Size& size) { |
| DVLOG(1) << "Resize to " << size.ToString() << ", " << device_pixel_ratio; |
| screen_->UpdateDisplayScaleAndSize(device_pixel_ratio, size); |
| window_tree_host_->SetBounds(gfx::Rect(size)); |
| if (tab_) { |
| tab_->Resize(device_pixel_ratio, |
| screen_->GetPrimaryDisplay().bounds().size()); |
| } |
| } |
| |
| void BlimpEngineSession::OnWebGestureEvent( |
| content::RenderWidgetHost* render_widget_host, |
| std::unique_ptr<blink::WebGestureEvent> event) { |
| TRACE_EVENT1("blimp", "BlimpEngineSession::OnWebGestureEvent", "type", |
| event->type); |
| render_widget_host->ForwardGestureEvent(*event); |
| } |
| |
| void BlimpEngineSession::OnCompositorMessageReceived( |
| content::RenderWidgetHost* render_widget_host, |
| const std::vector<uint8_t>& message) { |
| TRACE_EVENT0("blimp", "BlimpEngineSession::OnCompositorMessageReceived"); |
| |
| render_widget_host->HandleCompositorProto(message); |
| } |
| |
| void BlimpEngineSession::OnTextInputTypeChanged( |
| const ui::TextInputClient* client) {} |
| |
| void BlimpEngineSession::OnFocus() {} |
| |
| void BlimpEngineSession::OnBlur() {} |
| |
| void BlimpEngineSession::OnCaretBoundsChanged( |
| const ui::TextInputClient* client) {} |
| |
| // Called when either: |
| // - the TextInputClient is changed (e.g. by a change of focus) |
| // - the TextInputType of the TextInputClient changes |
| void BlimpEngineSession::OnTextInputStateChanged( |
| const ui::TextInputClient* client) { |
| if (!tab_ || !tab_->web_contents()->GetRenderWidgetHostView()) |
| return; |
| |
| ui::TextInputType type = |
| client ? client->GetTextInputType() : ui::TEXT_INPUT_TYPE_NONE; |
| |
| // TODO(shaktisahu): Propagate the new type to the client. |
| // Hide IME, when text input is out of focus, i.e. if the text input type |
| // changes to ui::TEXT_INPUT_TYPE_NONE. For other text input types, |
| // OnShowImeIfNeeded is used instead to send show IME request to client. |
| if (type == ui::TEXT_INPUT_TYPE_NONE) |
| render_widget_feature_.SendHideImeRequest( |
| kDummyTabId, |
| tab_->web_contents()->GetRenderWidgetHostView()->GetRenderWidgetHost()); |
| } |
| |
| void BlimpEngineSession::OnInputMethodDestroyed( |
| const ui::InputMethod* input_method) {} |
| |
| // Called when a user input should trigger showing the IME. |
| void BlimpEngineSession::OnShowImeIfNeeded() { |
| TRACE_EVENT0("blimp", "BlimpEngineSession::OnShowImeIfNeeded"); |
| if (!tab_ || !tab_->web_contents()->GetRenderWidgetHostView() || |
| !window_tree_host_->GetInputMethod()->GetTextInputClient()) |
| return; |
| |
| render_widget_feature_.SendShowImeRequest( |
| kDummyTabId, |
| tab_->web_contents()->GetRenderWidgetHostView()->GetRenderWidgetHost(), |
| window_tree_host_->GetInputMethod()->GetTextInputClient()); |
| } |
| |
| void BlimpEngineSession::ProcessMessage( |
| std::unique_ptr<BlimpMessage> message, |
| const net::CompletionCallback& callback) { |
| TRACE_EVENT1("blimp", "BlimpEngineSession::ProcessMessage", "TabId", |
| message->target_tab_id()); |
| DCHECK(!callback.is_null()); |
| DCHECK(BlimpMessage::kTabControl == message->feature_case() || |
| BlimpMessage::kNavigation == message->feature_case()); |
| |
| net::Error result = net::OK; |
| if (message->has_tab_control()) { |
| switch (message->tab_control().tab_control_case()) { |
| case TabControlMessage::kCreateTab: |
| if (!CreateTab(message->target_tab_id())) |
| result = net::ERR_FAILED; |
| break; |
| case TabControlMessage::kCloseTab: |
| CloseTab(message->target_tab_id()); |
| case TabControlMessage::kSize: |
| HandleResize(message->tab_control().size().device_pixel_ratio(), |
| gfx::Size(message->tab_control().size().width(), |
| message->tab_control().size().height())); |
| break; |
| default: |
| NOTIMPLEMENTED(); |
| result = net::ERR_NOT_IMPLEMENTED; |
| } |
| } else if (message->has_navigation()) { |
| if (tab_) { |
| switch (message->navigation().type()) { |
| case NavigationMessage::LOAD_URL: |
| tab_->LoadUrl(GURL(message->navigation().load_url().url())); |
| break; |
| case NavigationMessage::GO_BACK: |
| tab_->GoBack(); |
| break; |
| case NavigationMessage::GO_FORWARD: |
| tab_->GoForward(); |
| break; |
| case NavigationMessage::RELOAD: |
| tab_->Reload(); |
| break; |
| default: |
| NOTIMPLEMENTED(); |
| result = net::ERR_NOT_IMPLEMENTED; |
| } |
| } else { |
| VLOG(1) << "Tab " << message->target_tab_id() << " does not exist"; |
| } |
| } else { |
| NOTREACHED(); |
| result = net::ERR_FAILED; |
| } |
| |
| callback.Run(result); |
| } |
| |
| void BlimpEngineSession::AddNewContents(content::WebContents* source, |
| content::WebContents* new_contents, |
| WindowOpenDisposition disposition, |
| const gfx::Rect& initial_rect, |
| bool user_gesture, |
| bool* was_blocked) { |
| NOTIMPLEMENTED(); |
| } |
| |
| content::WebContents* BlimpEngineSession::OpenURLFromTab( |
| content::WebContents* source, |
| const content::OpenURLParams& params) { |
| // CURRENT_TAB is the only one we implement for now. |
| if (params.disposition != WindowOpenDisposition::CURRENT_TAB) { |
| NOTIMPLEMENTED(); |
| return nullptr; |
| } |
| |
| // TODO(haibinlu): Add helper method to get LoadURLParams from OpenURLParams. |
| content::NavigationController::LoadURLParams load_url_params(params.url); |
| load_url_params.source_site_instance = params.source_site_instance; |
| load_url_params.referrer = params.referrer; |
| load_url_params.frame_tree_node_id = params.frame_tree_node_id; |
| load_url_params.transition_type = params.transition; |
| load_url_params.extra_headers = params.extra_headers; |
| load_url_params.should_replace_current_entry = |
| params.should_replace_current_entry; |
| load_url_params.is_renderer_initiated = params.is_renderer_initiated; |
| load_url_params.override_user_agent = |
| content::NavigationController::UA_OVERRIDE_TRUE; |
| |
| source->GetController().LoadURLWithParams(load_url_params); |
| return source; |
| } |
| |
| void BlimpEngineSession::RequestToLockMouse(content::WebContents* web_contents, |
| bool user_gesture, |
| bool last_unlocked_by_target) { |
| web_contents->GotResponseToLockMouseRequest(true); |
| } |
| |
| void BlimpEngineSession::CloseContents(content::WebContents* source) { |
| if (source == tab_->web_contents()) { |
| tab_.reset(); |
| } |
| } |
| |
| void BlimpEngineSession::ActivateContents(content::WebContents* contents) { |
| contents->GetRenderViewHost()->GetWidget()->Focus(); |
| } |
| |
| void BlimpEngineSession::ForwardCompositorProto( |
| content::RenderWidgetHost* render_widget_host, |
| const std::vector<uint8_t>& proto) { |
| TRACE_EVENT0("blimp", "BlimpEngineSession::ForwardCompositorProto"); |
| render_widget_feature_.SendCompositorMessage(kDummyTabId, render_widget_host, |
| proto); |
| } |
| |
| void BlimpEngineSession::NavigationStateChanged( |
| content::WebContents* source, |
| content::InvalidateTypes changed_flags) { |
| TRACE_EVENT0("blimp", "BlimpEngineSession::NavigationStateChanged"); |
| if (source == tab_->web_contents()) |
| tab_->NavigationStateChanged(changed_flags); |
| } |
| |
| void BlimpEngineSession::PlatformSetContents( |
| std::unique_ptr<content::WebContents> new_contents, |
| const int target_tab_id) { |
| new_contents->SetDelegate(this); |
| |
| aura::Window* parent = window_tree_host_->window(); |
| aura::Window* content = new_contents->GetNativeView(); |
| if (!parent->Contains(content)) |
| parent->AddChild(content); |
| content->Show(); |
| |
| tab_ = base::MakeUnique<Tab>(std::move(new_contents), target_tab_id, |
| &render_widget_feature_, |
| navigation_message_sender_.get()); |
| } |
| |
| } // namespace engine |
| } // namespace blimp |