| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/ash/ash_web_view_impl.h" |
| |
| #include "ash/public/cpp/window_properties.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "content/public/browser/focused_node_details.h" |
| #include "content/public/browser/host_zoom_map.h" |
| #include "content/public/browser/page.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h" |
| #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h" |
| #include "ui/aura/window.h" |
| #include "ui/views/controls/webview/web_contents_set_background_color.h" |
| #include "ui/views/controls/webview/webview.h" |
| #include "ui/views/focus/focus_manager.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace { |
| void FixZoomLevelToOne(content::RenderFrameHost* render_frame_host) { |
| content::HostZoomMap* zoom_map = |
| content::HostZoomMap::Get(render_frame_host->GetSiteInstance()); |
| zoom_map->SetTemporaryZoomLevel(render_frame_host->GetGlobalId(), 1.0); |
| } |
| } // namespace |
| |
| AshWebViewImpl::AshWebViewImpl(const InitParams& params) : params_(params) { |
| Profile* profile = ProfileManager::GetActiveUserProfile(); |
| InitWebContents(profile); |
| InitLayout(profile); |
| } |
| |
| AshWebViewImpl::~AshWebViewImpl() { |
| Observe(nullptr); |
| web_contents_->SetDelegate(nullptr); |
| } |
| |
| const char* AshWebViewImpl::GetClassName() const { |
| return "AshWebViewImpl"; |
| } |
| |
| gfx::NativeView AshWebViewImpl::GetNativeView() { |
| return web_contents_->GetNativeView(); |
| } |
| |
| void AshWebViewImpl::ChildPreferredSizeChanged(views::View* child) { |
| DCHECK_EQ(web_view_, child); |
| SetPreferredSize(web_view_->GetPreferredSize()); |
| } |
| |
| void AshWebViewImpl::Layout() { |
| web_view_->SetBoundsRect(GetContentsBounds()); |
| } |
| |
| void AshWebViewImpl::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void AshWebViewImpl::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| bool AshWebViewImpl::GoBack() { |
| if (web_contents_->GetController().CanGoBack()) { |
| web_contents_->GetController().GoBack(); |
| return true; |
| } |
| return false; |
| } |
| |
| void AshWebViewImpl::Navigate(const GURL& url) { |
| content::NavigationController::LoadURLParams params(url); |
| web_contents_->GetController().LoadURLWithParams(params); |
| } |
| |
| views::View* AshWebViewImpl::GetInitiallyFocusedView() { |
| return web_view_; |
| } |
| |
| void AshWebViewImpl::AddedToWidget() { |
| UpdateMinimizeOnBackProperty(); |
| AshWebView::AddedToWidget(); |
| } |
| |
| bool AshWebViewImpl::IsWebContentsCreationOverridden( |
| content::SiteInstance* source_site_instance, |
| content::mojom::WindowContainerType window_container_type, |
| const GURL& opener_url, |
| const std::string& frame_name, |
| const GURL& target_url) { |
| if (params_.suppress_navigation) { |
| NotifyDidSuppressNavigation(target_url, |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| /*from_user_gesture=*/true); |
| return true; |
| } |
| return content::WebContentsDelegate::IsWebContentsCreationOverridden( |
| source_site_instance, window_container_type, opener_url, frame_name, |
| target_url); |
| } |
| |
| content::WebContents* AshWebViewImpl::OpenURLFromTab( |
| content::WebContents* source, |
| const content::OpenURLParams& params) { |
| if (params_.suppress_navigation) { |
| NotifyDidSuppressNavigation(params.url, params.disposition, |
| params.user_gesture); |
| return nullptr; |
| } |
| return content::WebContentsDelegate::OpenURLFromTab(source, params); |
| } |
| |
| void AshWebViewImpl::ResizeDueToAutoResize(content::WebContents* web_contents, |
| const gfx::Size& new_size) { |
| DCHECK_EQ(web_contents_.get(), web_contents); |
| web_view_->SetPreferredSize(new_size); |
| } |
| |
| bool AshWebViewImpl::TakeFocus(content::WebContents* web_contents, |
| bool reverse) { |
| DCHECK_EQ(web_contents_.get(), web_contents); |
| auto* focus_manager = GetFocusManager(); |
| if (focus_manager) |
| focus_manager->ClearNativeFocus(); |
| return false; |
| } |
| |
| void AshWebViewImpl::NavigationStateChanged( |
| content::WebContents* web_contents, |
| content::InvalidateTypes changed_flags) { |
| DCHECK_EQ(web_contents_.get(), web_contents); |
| UpdateCanGoBack(); |
| } |
| |
| void AshWebViewImpl::RequestMediaAccessPermission( |
| content::WebContents* web_contents, |
| const content::MediaStreamRequest& request, |
| content::MediaResponseCallback callback) { |
| if (!params_.can_record_media) { |
| std::move(callback).Run( |
| blink::mojom::StreamDevicesSet(), |
| blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, |
| std::unique_ptr<content::MediaStreamUI>()); |
| return; |
| } |
| MediaCaptureDevicesDispatcher::GetInstance()->ProcessMediaAccessRequest( |
| web_contents, request, std::move(callback), /*extension=*/nullptr); |
| } |
| |
| bool AshWebViewImpl::CheckMediaAccessPermission( |
| content::RenderFrameHost* render_frame_host, |
| const GURL& security_origin, |
| blink::mojom::MediaStreamType type) { |
| if (!params_.can_record_media) |
| return false; |
| return MediaCaptureDevicesDispatcher::GetInstance() |
| ->CheckMediaAccessPermission(render_frame_host, security_origin, type); |
| } |
| |
| void AshWebViewImpl::DidStopLoading() { |
| for (auto& observer : observers_) |
| observer.DidStopLoading(); |
| } |
| |
| void AshWebViewImpl::OnFocusChangedInPage( |
| content::FocusedNodeDetails* details) { |
| // When navigating to the |web_contents_|, it may not focus it. Request focus |
| // as needed. This is a workaround to get a non-empty rect of the focused |
| // node. See details in b/177047240. |
| auto* native_view = web_contents_->GetContentNativeView(); |
| if (native_view && !native_view->HasFocus()) |
| web_contents_->Focus(); |
| |
| for (auto& observer : observers_) |
| observer.DidChangeFocusedNode(details->node_bounds_in_screen); |
| } |
| |
| void AshWebViewImpl::RenderFrameHostChanged( |
| content::RenderFrameHost* old_host, |
| content::RenderFrameHost* new_host) { |
| if (new_host != new_host->GetOutermostMainFrame()) |
| return; |
| if (params_.fix_zoom_level_to_one) |
| FixZoomLevelToOne(new_host); |
| } |
| |
| void AshWebViewImpl::PrimaryPageChanged(content::Page& page) { |
| DCHECK_EQ(&page.GetMainDocument(), web_contents_->GetPrimaryMainFrame()); |
| if (!web_contents_->GetRenderWidgetHostView()) |
| return; |
| |
| if (!params_.enable_auto_resize) |
| return; |
| |
| gfx::Size min_size(1, 1); |
| if (params_.min_size) |
| min_size.SetToMax(params_.min_size.value()); |
| |
| gfx::Size max_size(INT_MAX, INT_MAX); |
| if (params_.max_size) |
| max_size.SetToMin(params_.max_size.value()); |
| |
| web_contents_->GetRenderWidgetHostView()->EnableAutoResize(min_size, |
| max_size); |
| } |
| |
| void AshWebViewImpl::NavigationEntriesDeleted() { |
| UpdateCanGoBack(); |
| } |
| |
| void AshWebViewImpl::InitWebContents(Profile* profile) { |
| web_contents_ = |
| content::WebContents::Create(content::WebContents::CreateParams( |
| profile, content::SiteInstance::Create(profile))); |
| |
| web_contents_->SetDelegate(this); |
| Observe(web_contents_.get()); |
| |
| // Use a transparent background. |
| views::WebContentsSetBackgroundColor::CreateForWebContentsWithColor( |
| web_contents_.get(), SK_ColorTRANSPARENT); |
| |
| // If requested, suppress navigation. |
| if (params_.suppress_navigation) { |
| web_contents_->GetMutableRendererPrefs() |
| ->browser_handles_all_top_level_requests = true; |
| web_contents_->SyncRendererPrefs(); |
| } |
| |
| if (params_.fix_zoom_level_to_one) |
| FixZoomLevelToOne(web_contents_->GetPrimaryMainFrame()); |
| } |
| |
| void AshWebViewImpl::InitLayout(Profile* profile) { |
| web_view_ = AddChildView(std::make_unique<views::WebView>(profile)); |
| web_view_->SetWebContents(web_contents_.get()); |
| } |
| |
| void AshWebViewImpl::NotifyDidSuppressNavigation( |
| const GURL& url, |
| WindowOpenDisposition disposition, |
| bool from_user_gesture) { |
| // Note that we post notification to |observers_| as an observer may cause |
| // |this| to be deleted during handling of the event which is unsafe to do |
| // until the original navigation sequence has been completed. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](const base::WeakPtr<AshWebViewImpl>& self, GURL url, |
| WindowOpenDisposition disposition, bool from_user_gesture) { |
| if (self) { |
| for (auto& observer : self->observers_) { |
| observer.DidSuppressNavigation(url, disposition, |
| from_user_gesture); |
| |
| // We need to check |self| to confirm that |observer| did not |
| // delete |this|. If |this| is deleted, we quit. |
| if (!self) |
| return; |
| } |
| } |
| }, |
| weak_factory_.GetWeakPtr(), url, disposition, from_user_gesture)); |
| } |
| |
| void AshWebViewImpl::UpdateCanGoBack() { |
| const bool can_go_back = web_contents_->GetController().CanGoBack(); |
| if (can_go_back_ == can_go_back) |
| return; |
| |
| can_go_back_ = can_go_back; |
| |
| UpdateMinimizeOnBackProperty(); |
| |
| for (auto& observer : observers_) |
| observer.DidChangeCanGoBack(can_go_back_); |
| } |
| |
| void AshWebViewImpl::UpdateMinimizeOnBackProperty() { |
| const bool minimize_on_back = params_.minimize_on_back_key && !can_go_back_; |
| views::Widget* widget = GetWidget(); |
| if (widget) { |
| widget->GetNativeWindow()->SetProperty(ash::kMinimizeOnBackKey, |
| minimize_on_back); |
| } |
| } |