| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/compute_pressure/pressure_service_base.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/renderer_host/render_widget_host_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/device_service.h" |
| #include "content/public/browser/video_picture_in_picture_window_controller.h" |
| #include "mojo/public/cpp/bindings/message.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "services/device/public/cpp/device_features.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "base/check.h" |
| #endif |
| |
| namespace { |
| |
| constexpr char kVirtualPressureSourceStartConsoleMessage[] = |
| "Non-virtual observers are running while a virtual source is being added. " |
| "The virtual source will have no effect on any observer as long as " |
| "observers are still running. Stop all the observers and restart them if " |
| "they want to use the virtual source."; |
| |
| constexpr char kVirtualPressureSourceStopConsoleMessage[] = |
| "Virtual observers are still running after the virtual source was " |
| "disconnected. The non-virtual source will have no effect on any observer " |
| "as long as observers are still running. Stop all the observers and " |
| "restart them if they want to use the non-virtual source."; |
| |
| } // namespace |
| |
| namespace content { |
| |
| PressureServiceBase::PressureServiceBase() |
| : source_to_client_{PressureClientImpl(this)} { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (base::FeatureList::IsEnabled( |
| features::kComputePressureBreakCalibrationMitigation)) { |
| converter_.EnableStateRandomizationMitigation(); |
| } |
| } |
| |
| PressureServiceBase::~PressureServiceBase() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Manually remove the observer here instead of using |
| // base::ScopedObserver. In general, this class will be destroyed before |
| // the observable (WebContentsPressureManagerProxy) but in some cases |
| // (e.g. active PressureObserver instances in both a shared worker and a |
| // dedicated worker may cause the PressureServiceForDedicatedWorker to be |
| // destroyed only when its DedicatedWorkerHost's RenderProcessHost is |
| // destroyed, which happens after WebContentsPressureManagerProxy object is |
| // destroyed) this is not true. The condition above can be reproduced by |
| // ComputePressureBrowserTest when SupportsSharedWorker() is true and shared |
| // workers are used. |
| auto* pressure_manager_proxy = GetWebContentsPressureManagerProxy(); |
| if (pressure_manager_proxy) { |
| pressure_manager_proxy->RemoveObserver(this); |
| } |
| } |
| |
| // static |
| // https://www.w3.org/TR/compute-pressure/#dfn-document-has-implicit-focus |
| bool PressureServiceBase::HasImplicitFocus(RenderFrameHost* render_frame_host) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| // TODO: http://crbug.com/407801065 |
| // On ChromeOS, in rare occasions render_frame_host may be nullptr. The |
| // following DUMP_WILL_BE_CHECK() is used to provide additional information |
| // for diagnosis. |
| DUMP_WILL_BE_CHECK(render_frame_host); |
| #endif |
| if (!render_frame_host) { |
| return false; |
| } |
| |
| // 1. If document is not fully active, return false. |
| if (!render_frame_host->IsActive()) { |
| return false; |
| } |
| |
| WebContentsImpl* web_contents = static_cast<WebContentsImpl*>( |
| WebContents::FromRenderFrameHost(render_frame_host)); |
| const auto& current_origin = render_frame_host->GetLastCommittedOrigin(); |
| |
| // 3. If associated document is same origin with initiators of active |
| // Picture-in-Picture sessions, return true. |
| if (std::ranges::any_of( |
| WebContentsImpl::GetAllWebContents(), [&](WebContentsImpl* wc) { |
| if (!wc->HasPictureInPictureVideo()) { |
| return false; |
| } |
| |
| auto origin = PictureInPictureWindowController:: |
| GetOrCreateVideoPictureInPictureController(wc) |
| ->GetOrigin(); |
| return current_origin == origin.value() && |
| wc->GetBrowserContext() == web_contents->GetBrowserContext(); |
| })) { |
| return true; |
| } |
| |
| // 4. If browsing context is capturing, return true. |
| // TODO(crbug.com/40945930): Take muted state into account. |
| if (static_cast<RenderFrameHostImpl*>(render_frame_host) |
| ->HasMediaStreams( |
| RenderFrameHostImpl::MediaStreamType::kCapturingMediaStream)) { |
| return true; |
| } |
| |
| // 6. If top-level browsing context does not have system focus, return false. |
| RenderWidgetHostImpl* rwh = static_cast<RenderWidgetHostImpl*>( |
| render_frame_host->GetRenderWidgetHost()); |
| if (!rwh->is_focused()) { |
| return false; |
| } |
| |
| // 7. Let focused document be the currently focused area's node document. |
| auto* focused_frame = web_contents->GetFocusedFrame(); |
| if (!focused_frame) { |
| return false; |
| } |
| |
| // 8. If origin is same origin with focused document, return true. |
| // 9. Otherwise, return false. |
| return current_origin.IsSameOriginWith( |
| focused_frame->GetLastCommittedOrigin()); |
| } |
| |
| device::mojom::PressureState PressureServiceBase::CalculateState( |
| double pressure_value) { |
| return converter_.CalculateState(pressure_value); |
| } |
| |
| bool PressureServiceBase::CanCallAddClient() const { |
| return true; |
| } |
| |
| void PressureServiceBase::BindReceiver( |
| mojo::PendingReceiver<blink::mojom::WebPressureManager> receiver) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (manager_receiver_.is_bound()) { |
| mojo::ReportBadMessage("PressureService is already connected."); |
| return; |
| } |
| |
| manager_receiver_.Bind(std::move(receiver)); |
| // base::Unretained is safe because Mojo guarantees the callback will not |
| // be called after `associated_manager_receiver_` is deallocated, |
| // and `associated_manager_receiver_` is owned by this class. |
| manager_receiver_.set_disconnect_handler( |
| base::BindRepeating(&PressureServiceBase::OnPressureManagerDisconnected, |
| base::Unretained(this))); |
| |
| auto* pressure_manager_proxy = |
| GetWebContentsPressureManagerProxy(/*allow_creation*/true); |
| if (pressure_manager_proxy) { |
| pressure_manager_proxy->AddObserver(this); |
| } |
| } |
| |
| void PressureServiceBase::AddClient( |
| device::mojom::PressureSource source, |
| mojo::PendingAssociatedRemote<blink::mojom::WebPressureClient> client, |
| AddClientCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!CanCallAddClient()) { |
| std::move(callback).Run( |
| device::mojom::PressureManagerAddClientResult::kNotSupported); |
| return; |
| } |
| |
| auto& pressure_client = source_to_client_[static_cast<size_t>(source)]; |
| if (pressure_client.is_client_associated_remote_bound()) { |
| manager_receiver_.ReportBadMessage( |
| "PressureClientImpl is already connected."); |
| // manager_receiver_.ReportBadMessage() will reset `manager_receiver_` and |
| // so clean up as if the pipe had been disconnected. |
| OnPressureManagerDisconnected(); |
| return; |
| } |
| |
| pressure_client.BindPendingAssociatedRemote(std::move(client)); |
| |
| if (!manager_remote_.is_bound()) { |
| auto manager_receiver = manager_remote_.BindNewPipeAndPassReceiver(); |
| manager_remote_.set_disconnect_handler( |
| base::BindRepeating(&PressureServiceBase::OnPressureManagerDisconnected, |
| base::Unretained(this))); |
| |
| GetDeviceService().BindPressureManager(std::move(manager_receiver)); |
| } |
| |
| if (pressure_client.is_client_receiver_bound()) { |
| std::move(callback).Run(device::mojom::PressureManagerAddClientResult::kOk); |
| } else { |
| const std::optional<base::UnguessableToken>& token = GetTokenFor(source); |
| manager_remote_->AddClient( |
| source, token, pressure_client.BindNewEndpointAndPassRemote(), |
| base::BindOnce(&PressureServiceBase::DidAddClient, |
| weak_ptr_factory_.GetWeakPtr(), source, token, |
| std::move(callback))); |
| } |
| } |
| |
| void PressureServiceBase::DidAddVirtualPressureSource( |
| device::mojom::PressureSource source) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| const auto& pressure_client = source_to_client_[static_cast<size_t>(source)]; |
| |
| if (pressure_client.pressure_source_type() == |
| PressureClientImpl::PressureSourceType::kNonVirtual) { |
| AddMessageToConsole(kVirtualPressureSourceStartConsoleMessage); |
| } |
| } |
| |
| void PressureServiceBase::DidRemoveVirtualPressureSource( |
| device::mojom::PressureSource source) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| const auto& pressure_client = source_to_client_[static_cast<size_t>(source)]; |
| if (pressure_client.pressure_source_type() == |
| PressureClientImpl::PressureSourceType::kVirtual) { |
| AddMessageToConsole(kVirtualPressureSourceStopConsoleMessage); |
| } |
| } |
| |
| WebContentsPressureManagerProxy* |
| PressureServiceBase::GetWebContentsPressureManagerProxy( |
| bool allow_creation) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| RenderFrameHost* rfh = GetRenderFrameHost(); |
| auto* web_contents = WebContents::FromRenderFrameHost(rfh); |
| // Checking the validity of RenderFrameHost* because in some cases as |
| // explained in ~PressureServiceBase(), the order of destruction might not be |
| // as expected. |
| if (rfh && web_contents) { |
| return allow_creation |
| ? WebContentsPressureManagerProxy::GetOrCreate(web_contents) |
| : WebContentsPressureManagerProxy::FromWebContents(web_contents); |
| } else { |
| return nullptr; |
| } |
| } |
| |
| RenderFrameHost* PressureServiceBase::GetRenderFrameHost() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return nullptr; |
| } |
| |
| void PressureServiceBase::AddMessageToConsole( |
| const std::string& message) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| RenderFrameHost* rfh = GetRenderFrameHost(); |
| CHECK(rfh); |
| rfh->AddMessageToConsole(blink::mojom::ConsoleMessageLevel::kInfo, message); |
| } |
| |
| // Disconnection handler for |associated_manager_receiver_| and |
| // |associated_manager_remote_|. If either of the connections breaks, we should |
| // disconnect all connections and let //services know we do not need more |
| // updates. |
| void PressureServiceBase::OnPressureManagerDisconnected() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| manager_receiver_.reset(); |
| manager_remote_.reset(); |
| |
| // In case the client was removed from Blink before service binding |
| // has occurred. Reset is needed. |
| for (auto& client : source_to_client_) { |
| client.Reset(); |
| } |
| |
| auto* pressure_manager_proxy = GetWebContentsPressureManagerProxy(); |
| if (pressure_manager_proxy) { |
| pressure_manager_proxy->RemoveObserver(this); |
| } |
| } |
| |
| void PressureServiceBase::DidAddClient( |
| device::mojom::PressureSource source, |
| const std::optional<base::UnguessableToken>& token, |
| AddClientCallback client_callback, |
| device::mojom::PressureManagerAddClientResult result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto& pressure_client = source_to_client_[static_cast<size_t>(source)]; |
| |
| if (result == device::mojom::PressureManagerAddClientResult::kNotSupported) { |
| std::move(client_callback) |
| .Run(device::mojom::PressureManagerAddClientResult::kNotSupported); |
| pressure_client.Reset(); |
| return; |
| } |
| |
| pressure_client.SetPressureSourceType(token.has_value()); |
| |
| std::move(client_callback) |
| .Run(device::mojom::PressureManagerAddClientResult::kOk); |
| } |
| |
| } // namespace content |