| // Copyright 2020 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 "third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/check.h" |
| #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/common/tokens/tokens.h" |
| #include "third_party/blink/public/mojom/frame/frame_owner_element_type.mojom-blink.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/frame/frame.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/remote_frame.h" |
| #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h" |
| #include "third_party/blink/renderer/platform/heap/thread_state.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread.h" |
| #include "third_party/blink/renderer/platform/wtf/casting.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_copier.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" |
| |
| using performance_manager::mojom::blink::IframeAttributionData; |
| using performance_manager::mojom::blink::IframeAttributionDataPtr; |
| using performance_manager::mojom::blink::ProcessCoordinationUnit; |
| using performance_manager::mojom::blink::V8ContextDescription; |
| using performance_manager::mojom::blink::V8ContextDescriptionPtr; |
| using performance_manager::mojom::blink::V8ContextWorldType; |
| |
| namespace WTF { |
| |
| // Copies the data by move. |
| template <> |
| struct CrossThreadCopier<V8ContextDescriptionPtr> |
| : public WTF::CrossThreadCopierByValuePassThrough<V8ContextDescriptionPtr> { |
| }; |
| |
| // Copies the data by move. |
| template <> |
| struct CrossThreadCopier<IframeAttributionDataPtr> |
| : public WTF::CrossThreadCopierByValuePassThrough< |
| IframeAttributionDataPtr> {}; |
| |
| // Copies the data using the copy constructor. |
| template <> |
| struct CrossThreadCopier<blink::V8ContextToken> |
| : public WTF::CrossThreadCopierPassThrough<blink::V8ContextToken> {}; |
| |
| } // namespace WTF |
| |
| namespace blink { |
| |
| using mojom::blink::FrameOwnerElementType; |
| |
| namespace { |
| |
| // Determines if the given stable world ID is an extension world ID. |
| // Extensions IDs are 32-character strings containing characters in the range of |
| // 'a' to 'p', inclusive. |
| // TODO(chrisha): Lift this somewhere public and common in components/extensions |
| // and reuse it from there. |
| bool IsExtensionStableWorldId(const String& stable_world_id) { |
| if (stable_world_id.IsNull() || stable_world_id.IsEmpty()) |
| return false; |
| if (stable_world_id.length() != 32) |
| return false; |
| for (size_t i = 0; i < stable_world_id.length(); ++i) { |
| if (stable_world_id[i] < 'a' || stable_world_id[i] > 'p') |
| return false; |
| } |
| return true; |
| } |
| |
| // Returns true if |owner| is an iframe, false otherwise. |
| // This will also return true for custom elements built on iframe, like |
| // <webview> and <guestview>. Since the renderer has no knowledge of these they |
| // must be filtered out on the browser side. |
| bool ShouldSendIframeNotificationsFor(const HTMLFrameOwnerElement& owner) { |
| return owner.OwnerType() == FrameOwnerElementType::kIframe; |
| } |
| |
| // If |frame| is a RemoteFrame with a local parent, returns the parent. |
| // Otherwise returns nullptr. |
| LocalFrame* GetLocalParentOfRemoteFrame(const Frame& frame) { |
| if (IsA<RemoteFrame>(frame)) { |
| if (Frame* parent = frame.Tree().Parent()) { |
| return DynamicTo<LocalFrame>(parent); |
| } |
| } |
| return nullptr; |
| } |
| |
| IframeAttributionDataPtr AttributionDataForOwner( |
| const HTMLFrameOwnerElement& owner) { |
| auto attribution_data = IframeAttributionData::New(); |
| attribution_data->id = owner.FastGetAttribute(html_names::kIdAttr); |
| attribution_data->src = owner.FastGetAttribute(html_names::kSrcAttr); |
| return attribution_data; |
| } |
| |
| } // namespace |
| |
| RendererResourceCoordinatorImpl::~RendererResourceCoordinatorImpl() = default; |
| |
| // static |
| void RendererResourceCoordinatorImpl::MaybeInitialize() { |
| if (!RuntimeEnabledFeatures::PerformanceManagerInstrumentationEnabled()) |
| return; |
| |
| blink::Platform* platform = Platform::Current(); |
| DCHECK(IsMainThread()); |
| DCHECK(platform); |
| |
| mojo::PendingRemote<ProcessCoordinationUnit> remote; |
| platform->GetBrowserInterfaceBroker()->GetInterface( |
| remote.InitWithNewPipeAndPassReceiver()); |
| RendererResourceCoordinator::Set( |
| new RendererResourceCoordinatorImpl(std::move(remote))); |
| } |
| |
| void RendererResourceCoordinatorImpl::SetMainThreadTaskLoadIsLow( |
| bool main_thread_task_load_is_low) { |
| DCHECK(service_); |
| service_->SetMainThreadTaskLoadIsLow(main_thread_task_load_is_low); |
| } |
| |
| void RendererResourceCoordinatorImpl::OnScriptStateCreated( |
| ScriptState* script_state, |
| ExecutionContext* execution_context) { |
| DCHECK(script_state); |
| DCHECK(service_); |
| |
| auto v8_desc = V8ContextDescription::New(); |
| v8_desc->token = script_state->GetToken(); |
| |
| IframeAttributionDataPtr iframe_attribution_data; |
| |
| // Default the world name to being empty. |
| |
| auto& dom_wrapper = script_state->World(); |
| switch (dom_wrapper.GetWorldType()) { |
| case DOMWrapperWorld::WorldType::kMain: { |
| v8_desc->world_type = V8ContextWorldType::kMain; |
| } break; |
| case DOMWrapperWorld::WorldType::kIsolated: { |
| auto stable_world_id = dom_wrapper.NonMainWorldStableId(); |
| if (IsExtensionStableWorldId(stable_world_id)) { |
| v8_desc->world_type = V8ContextWorldType::kExtension; |
| v8_desc->world_name = stable_world_id; |
| } else { |
| v8_desc->world_type = V8ContextWorldType::kIsolated; |
| v8_desc->world_name = dom_wrapper.NonMainWorldHumanReadableName(); |
| } |
| } break; |
| case DOMWrapperWorld::WorldType::kInspectorIsolated: { |
| v8_desc->world_type = V8ContextWorldType::kInspector; |
| } break; |
| case DOMWrapperWorld::WorldType::kRegExp: { |
| v8_desc->world_type = V8ContextWorldType::kRegExp; |
| } break; |
| case DOMWrapperWorld::WorldType::kForV8ContextSnapshotNonMain: { |
| // This should not happen in the production browser. |
| NOTREACHED(); |
| } break; |
| case DOMWrapperWorld::WorldType::kWorker: { |
| v8_desc->world_type = V8ContextWorldType::kWorkerOrWorklet; |
| } break; |
| } |
| |
| if (execution_context) { |
| // This should never happen for a regexp world. |
| DCHECK_NE(DOMWrapperWorld::WorldType::kRegExp, dom_wrapper.GetWorldType()); |
| |
| v8_desc->execution_context_token = |
| execution_context->GetExecutionContextToken(); |
| |
| // Only report the iframe data alongside the main world. |
| // If this is the main world (so also a LocalDOMWindow) ... |
| if (v8_desc->world_type == V8ContextWorldType::kMain) { |
| auto* local_dom_window = To<LocalDOMWindow>(execution_context); |
| // ... with a parent ... |
| auto* local_frame = local_dom_window->GetFrame(); |
| DCHECK(local_frame); |
| if (auto* parent_frame = local_frame->Parent()) { |
| // ... that is also local ... |
| if (IsA<LocalFrame>(parent_frame)) { |
| // ... then we want to grab the iframe data associated with this |
| // frame. |
| auto* owner = To<HTMLFrameOwnerElement>(local_frame->Owner()); |
| DCHECK(owner); |
| iframe_attribution_data = AttributionDataForOwner(*owner); |
| } |
| } |
| } |
| } |
| |
| DispatchOnV8ContextCreated(std::move(v8_desc), |
| std::move(iframe_attribution_data)); |
| } |
| |
| void RendererResourceCoordinatorImpl::OnScriptStateDetached( |
| ScriptState* script_state) { |
| DCHECK(script_state); |
| DispatchOnV8ContextDetached(script_state->GetToken()); |
| } |
| |
| void RendererResourceCoordinatorImpl::OnScriptStateDestroyed( |
| ScriptState* script_state) { |
| DCHECK(script_state); |
| DispatchOnV8ContextDestroyed(script_state->GetToken()); |
| } |
| |
| void RendererResourceCoordinatorImpl::OnBeforeContentFrameAttached( |
| const Frame& frame, |
| const HTMLFrameOwnerElement& owner) { |
| DCHECK(service_); |
| if (!ShouldSendIframeNotificationsFor(owner)) |
| return; |
| LocalFrame* parent = GetLocalParentOfRemoteFrame(frame); |
| if (!parent) |
| return; |
| service_->OnRemoteIframeAttached( |
| parent->GetLocalFrameToken(), |
| frame.GetFrameToken().GetAs<RemoteFrameToken>(), |
| AttributionDataForOwner(owner)); |
| } |
| |
| void RendererResourceCoordinatorImpl::OnBeforeContentFrameDetached( |
| const Frame& frame, |
| const HTMLFrameOwnerElement& owner) { |
| DCHECK(service_); |
| if (!ShouldSendIframeNotificationsFor(owner)) |
| return; |
| LocalFrame* parent = GetLocalParentOfRemoteFrame(frame); |
| if (!parent) |
| return; |
| service_->OnRemoteIframeDetached( |
| parent->GetLocalFrameToken(), |
| frame.GetFrameToken().GetAs<RemoteFrameToken>()); |
| } |
| |
| RendererResourceCoordinatorImpl::RendererResourceCoordinatorImpl( |
| mojo::PendingRemote<ProcessCoordinationUnit> remote) { |
| service_.Bind(std::move(remote)); |
| } |
| |
| void RendererResourceCoordinatorImpl::DispatchOnV8ContextCreated( |
| V8ContextDescriptionPtr v8_desc, |
| IframeAttributionDataPtr iframe_attribution_data) { |
| DCHECK(service_); |
| // Calls to this can arrive on any thread (due to workers, etc), but the |
| // interface itself is bound to the main thread. In this case, once we've |
| // collated the necessary data we bounce over to the main thread. Note that |
| // posting "this" unretained is safe because the renderer resource coordinator |
| // is a singleton that leaks at process shutdown. |
| if (!IsMainThread()) { |
| blink::PostCrossThreadTask( |
| *Thread::MainThread()->GetTaskRunner(), FROM_HERE, |
| WTF::CrossThreadBindOnce( |
| &RendererResourceCoordinatorImpl::DispatchOnV8ContextCreated, |
| WTF::CrossThreadUnretained(this), std::move(v8_desc), |
| std::move(iframe_attribution_data))); |
| } else { |
| service_->OnV8ContextCreated(std::move(v8_desc), |
| std::move(iframe_attribution_data)); |
| } |
| } |
| |
| void RendererResourceCoordinatorImpl::DispatchOnV8ContextDetached( |
| const blink::V8ContextToken& token) { |
| DCHECK(service_); |
| // See DispatchOnV8ContextCreated for why this is both needed and safe. |
| if (!IsMainThread()) { |
| blink::PostCrossThreadTask( |
| *Thread::MainThread()->GetTaskRunner(), FROM_HERE, |
| WTF::CrossThreadBindOnce( |
| &RendererResourceCoordinatorImpl::DispatchOnV8ContextDetached, |
| WTF::CrossThreadUnretained(this), token)); |
| } else { |
| service_->OnV8ContextDetached(token); |
| } |
| } |
| void RendererResourceCoordinatorImpl::DispatchOnV8ContextDestroyed( |
| const blink::V8ContextToken& token) { |
| DCHECK(service_); |
| // See DispatchOnV8ContextCreated for why this is both needed and safe. |
| if (!IsMainThread()) { |
| blink::PostCrossThreadTask( |
| *Thread::MainThread()->GetTaskRunner(), FROM_HERE, |
| WTF::CrossThreadBindOnce( |
| &RendererResourceCoordinatorImpl::DispatchOnV8ContextDestroyed, |
| WTF::CrossThreadUnretained(this), token)); |
| } else { |
| service_->OnV8ContextDestroyed(token); |
| } |
| } |
| |
| } // namespace blink |