blob: c8fbabbac8a336ee7d010a23893abe472e718734 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/extension_api_frame_id_map.h"
#include <string_view>
#include <tuple>
#include <utility>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "base/uuid.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/common/constants.h"
namespace extensions {
namespace {
// The map is accessed on the IO and UI thread, so construct it once and never
// delete it.
base::LazyInstance<ExtensionApiFrameIdMap>::Leaky g_map_instance =
LAZY_INSTANCE_INITIALIZER;
} // namespace
const int ExtensionApiFrameIdMap::kInvalidFrameId = -1;
const int ExtensionApiFrameIdMap::kTopFrameId = 0;
ExtensionApiFrameIdMap::FrameData::FrameData()
: frame_id(kInvalidFrameId),
parent_frame_id(kInvalidFrameId),
tab_id(extension_misc::kUnknownTabId),
window_id(extension_misc::kUnknownWindowId),
frame_type(api::extension_types::FrameType::kOutermostFrame),
document_lifecycle(api::extension_types::DocumentLifecycle::kActive) {}
ExtensionApiFrameIdMap::FrameData::FrameData(
int frame_id,
int parent_frame_id,
int tab_id,
int window_id,
const DocumentId& document_id,
const DocumentId& parent_document_id,
api::extension_types::FrameType frame_type,
api::extension_types::DocumentLifecycle document_lifecycle)
: frame_id(frame_id),
parent_frame_id(parent_frame_id),
tab_id(tab_id),
window_id(window_id),
document_id(document_id),
parent_document_id(parent_document_id),
frame_type(frame_type),
document_lifecycle(document_lifecycle) {}
ExtensionApiFrameIdMap::FrameData::~FrameData() = default;
ExtensionApiFrameIdMap::FrameData::FrameData(
const ExtensionApiFrameIdMap::FrameData& other) = default;
ExtensionApiFrameIdMap::FrameData& ExtensionApiFrameIdMap::FrameData::operator=(
const ExtensionApiFrameIdMap::FrameData& other) = default;
ExtensionApiFrameIdMap::ExtensionApiFrameIdMap() = default;
ExtensionApiFrameIdMap::~ExtensionApiFrameIdMap() = default;
// static
ExtensionApiFrameIdMap* ExtensionApiFrameIdMap::Get() {
return g_map_instance.Pointer();
}
// static
int ExtensionApiFrameIdMap::GetFrameId(
content::RenderFrameHost* render_frame_host) {
if (!render_frame_host) {
return kInvalidFrameId;
}
if (!render_frame_host->IsInPrimaryMainFrame()) {
return render_frame_host->GetFrameTreeNodeId().value();
}
return kTopFrameId;
}
// static
int ExtensionApiFrameIdMap::GetFrameId(
content::NavigationHandle* navigation_handle) {
return navigation_handle->IsInPrimaryMainFrame()
? kTopFrameId
: navigation_handle->GetFrameTreeNodeId().value();
}
// static
int ExtensionApiFrameIdMap::GetParentFrameId(
content::RenderFrameHost* render_frame_host) {
return render_frame_host
? GetFrameId(render_frame_host->GetParentOrOuterDocument())
: kInvalidFrameId;
}
// static
int ExtensionApiFrameIdMap::GetParentFrameId(
content::NavigationHandle* navigation_handle) {
return GetFrameId(navigation_handle->GetParentFrameOrOuterDocument());
}
// static
content::RenderFrameHost* ExtensionApiFrameIdMap::GetRenderFrameHostById(
content::WebContents* web_contents,
int frame_id) {
// Although it is technically possible to map |frame_id| to a RenderFrameHost
// without WebContents, we choose to not do that because in the extension API
// frameIds are only guaranteed to be meaningful in combination with a tabId.
if (!web_contents) {
return nullptr;
}
if (frame_id == kInvalidFrameId) {
return nullptr;
}
if (frame_id == kTopFrameId) {
return web_contents->GetPrimaryMainFrame();
}
DCHECK_GE(frame_id, 1);
// Unfortunately, extension APIs do not know which process to expect for a
// given frame ID, so we must use an unsafe API here that could return a
// different RenderFrameHost than the caller may have expected (e.g., one that
// changed after a cross-process navigation).
content::RenderFrameHost* render_frame_host =
web_contents->UnsafeFindFrameByFrameTreeNodeId(
content::FrameTreeNodeId(frame_id));
// Fail if the frame is not active or in prerendering (e.g. in the
// back/forward cache).
if (!render_frame_host ||
(!render_frame_host->IsActive() &&
!render_frame_host->IsInLifecycleState(
content::RenderFrameHost::LifecycleState::kPrerendering))) {
return nullptr;
}
return render_frame_host;
}
content::RenderFrameHost*
ExtensionApiFrameIdMap::GetRenderFrameHostByDocumentId(
const DocumentId& document_id) {
auto iter = document_id_map_.find(document_id);
if (iter == document_id_map_.end()) {
return nullptr;
}
return &iter->second->render_frame_host();
}
content::RenderFrameHost* ExtensionApiFrameIdMap::GetRenderFrameHostByFrameId(
int frame_id) {
// Frame_id values of 0 are not guaranteed to be unique. Values less than 0
// are invalid.
CHECK_GE(frame_id, 1);
content::RenderFrameHost* render_frame_host = nullptr;
for (const auto& iter : document_id_map_) {
if (frame_id ==
ExtensionApiFrameIdMap::GetFrameId(&iter.second->render_frame_host())) {
render_frame_host = &iter.second->render_frame_host();
break;
}
}
// Fail if the frame is not active or in prerendering (e.g. in the
// back/forward cache).
if (!render_frame_host ||
(!render_frame_host->IsActive() &&
!render_frame_host->IsInLifecycleState(
content::RenderFrameHost::LifecycleState::kPrerendering))) {
return nullptr;
}
return render_frame_host;
}
ExtensionApiFrameIdMap::DocumentId ExtensionApiFrameIdMap::DocumentIdFromString(
const std::string& document_id) {
if (document_id.length() != 32) {
return DocumentId();
}
std::string_view string_piece(document_id);
uint64_t high = 0;
uint64_t low = 0;
if (!base::HexStringToUInt64(string_piece.substr(0, 16), &high) ||
!base::HexStringToUInt64(string_piece.substr(16, 16), &low)) {
return DocumentId();
}
std::optional<base::UnguessableToken> token =
base::UnguessableToken::Deserialize(high, low);
if (!token.has_value()) {
return DocumentId();
}
return token.value();
}
ExtensionApiFrameIdMap::FrameData ExtensionApiFrameIdMap::KeyToValue(
content::GlobalRenderFrameHostId key,
bool require_live_frame) const {
return KeyToValue(content::RenderFrameHost::FromID(key), require_live_frame);
}
ExtensionApiFrameIdMap::FrameData ExtensionApiFrameIdMap::KeyToValue(
content::RenderFrameHost* render_frame_host,
bool require_live_frame) const {
if (!render_frame_host ||
(require_live_frame && !render_frame_host->IsRenderFrameLive())) {
return FrameData();
}
int tab_id = extension_misc::kUnknownTabId;
int window_id = extension_misc::kUnknownWindowId;
// The browser client can be null in unittests.
if (ExtensionsBrowserClient::Get()) {
ExtensionsBrowserClient::Get()->GetTabAndWindowIdForWebContents(
content::WebContents::FromRenderFrameHost(render_frame_host), &tab_id,
&window_id);
}
return FrameData(
GetFrameId(render_frame_host), GetParentFrameId(render_frame_host),
tab_id, window_id, GetDocumentId(render_frame_host),
GetDocumentId(render_frame_host->GetParentOrOuterDocument()),
GetFrameType(render_frame_host), GetDocumentLifecycle(render_frame_host));
}
ExtensionApiFrameIdMap::FrameData ExtensionApiFrameIdMap::GetFrameData(
content::GlobalRenderFrameHostId render_frame_host_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto frame_id_iter = deleted_frame_data_map_.find(render_frame_host_id);
if (frame_id_iter != deleted_frame_data_map_.end()) {
return frame_id_iter->second;
}
return KeyToValue(render_frame_host_id, true /* require_live_frame */);
}
ExtensionApiFrameIdMap::DocumentId ExtensionApiFrameIdMap::GetDocumentId(
content::RenderFrameHost* render_frame_host) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// This check allows callers to pass in the result from
// GetParentOrOuterDocument() without needing to check whether the resulting
// frame exists.
if (!render_frame_host) {
return DocumentId();
}
return ExtensionDocumentUserData::GetOrCreateForCurrentDocument(
render_frame_host)
->document_id();
}
ExtensionApiFrameIdMap::DocumentId ExtensionApiFrameIdMap::GetDocumentId(
content::NavigationHandle* navigation_handle) {
// We can only access NavigationHandle::GetRenderFrameHost if the navigation
// handle has committed or is waiting to commit. This is fine because
// otherwise the documentId is useless as it will point at the old
// document.
if (navigation_handle->IsWaitingToCommit() ||
navigation_handle->HasCommitted()) {
return GetDocumentId(navigation_handle->GetRenderFrameHost());
}
return DocumentId();
}
// static
base::Uuid ExtensionApiFrameIdMap::GetContextId(
content::RenderFrameHost* render_frame_host) {
return ExtensionDocumentUserData::GetOrCreateForCurrentDocument(
render_frame_host)
->context_id();
}
api::extension_types::FrameType ExtensionApiFrameIdMap::GetFrameType(
content::RenderFrameHost* render_frame_host) {
DCHECK(render_frame_host);
if (!render_frame_host->GetParentOrOuterDocument()) {
return api::extension_types::FrameType::kOutermostFrame;
}
if (render_frame_host->IsFencedFrameRoot()) {
return api::extension_types::FrameType::kFencedFrame;
}
return api::extension_types::FrameType::kSubFrame;
}
api::extension_types::FrameType ExtensionApiFrameIdMap::GetFrameType(
content::NavigationHandle* navigation_handle) {
switch (navigation_handle->GetNavigatingFrameType()) {
case content::FrameType::kSubframe:
return api::extension_types::FrameType::kSubFrame;
case content::FrameType::kFencedFrameRoot:
return api::extension_types::FrameType::kFencedFrame;
case content::FrameType::kPrimaryMainFrame:
case content::FrameType::kPrerenderMainFrame:
case content::FrameType::kGuestMainFrame:
return api::extension_types::FrameType::kOutermostFrame;
}
}
api::extension_types::DocumentLifecycle
ExtensionApiFrameIdMap::GetDocumentLifecycle(
content::RenderFrameHost* render_frame_host) {
DCHECK(render_frame_host);
// We use IsInLifecycleState as opposed to GetLifecycleState with a switch
// because we cannot call GetLifecycleState for speculative frames.
if (render_frame_host->IsInLifecycleState(
content::RenderFrameHost::LifecycleState::kActive)) {
return api::extension_types::DocumentLifecycle::kActive;
}
if (render_frame_host->IsInLifecycleState(
content::RenderFrameHost::LifecycleState::kInBackForwardCache)) {
return api::extension_types::DocumentLifecycle::kCached;
}
if (render_frame_host->IsInLifecycleState(
content::RenderFrameHost::LifecycleState::kPrerendering)) {
return api::extension_types::DocumentLifecycle::kPrerender;
}
if (render_frame_host->IsInLifecycleState(
content::RenderFrameHost::LifecycleState::kPendingDeletion)) {
return api::extension_types::DocumentLifecycle::kPendingDeletion;
}
return api::extension_types::DocumentLifecycle::kNone;
}
api::extension_types::DocumentLifecycle
ExtensionApiFrameIdMap::GetDocumentLifecycle(
content::NavigationHandle* navigation_handle) {
if (content::RenderFrameHost* parent_or_outer_document =
navigation_handle->GetParentFrameOrOuterDocument()) {
return GetDocumentLifecycle(parent_or_outer_document);
}
if (navigation_handle->IsInPrerenderedMainFrame()) {
return api::extension_types::DocumentLifecycle::kPrerender;
} else if (navigation_handle->IsInPrimaryMainFrame()) {
return api::extension_types::DocumentLifecycle::kActive;
}
return api::extension_types::DocumentLifecycle::kNone;
}
void ExtensionApiFrameIdMap::OnRenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(render_frame_host);
const content::GlobalRenderFrameHostId key(render_frame_host->GetGlobalId());
// TODO(http://crbug.com/522129): This is necessary right now because beacon
// requests made in window.onunload may start after this has been called.
// Delay the RemoveFrameData() call, so we will still have the frame data
// cached when the beacon request comes in.
deleted_frame_data_map_.insert(
{key, KeyToValue(render_frame_host, false /* require_live_frame */)});
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(
[](ExtensionApiFrameIdMap* self,
content::GlobalRenderFrameHostId key) {
self->deleted_frame_data_map_.erase(key);
},
base::Unretained(this), key));
}
ExtensionApiFrameIdMap::ExtensionDocumentUserData::ExtensionDocumentUserData(
content::RenderFrameHost* render_frame_host)
: content::DocumentUserData<ExtensionDocumentUserData>(render_frame_host),
document_id_(DocumentId::Create()),
context_id_(base::Uuid::GenerateRandomV4()) {
Get()->document_id_map_[document_id_] = this;
}
ExtensionApiFrameIdMap::ExtensionDocumentUserData::
~ExtensionDocumentUserData() {
Get()->document_id_map_.erase(document_id_);
}
DOCUMENT_USER_DATA_KEY_IMPL(ExtensionApiFrameIdMap::ExtensionDocumentUserData);
} // namespace extensions