blob: 11732db812a0dbebe72751bfff57c6efbd95619b [file] [log] [blame]
// 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 "extensions/browser/extension_api_frame_id_map.h"
#include <tuple>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/task/post_task.h"
#include "content/public/browser/browser_thread.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 "content/public/common/child_process_host.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) {}
ExtensionApiFrameIdMap::FrameData::FrameData(
int frame_id,
int parent_frame_id,
int tab_id,
int window_id,
GURL last_committed_main_frame_url,
base::Optional<GURL> pending_main_frame_url)
: frame_id(frame_id),
parent_frame_id(parent_frame_id),
tab_id(tab_id),
window_id(window_id),
last_committed_main_frame_url(std::move(last_committed_main_frame_url)),
pending_main_frame_url(std::move(pending_main_frame_url)) {}
ExtensionApiFrameIdMap::FrameData::~FrameData() = default;
ExtensionApiFrameIdMap::FrameData::FrameData(
const ExtensionApiFrameIdMap::FrameData& other) = default;
ExtensionApiFrameIdMap::FrameData& ExtensionApiFrameIdMap::FrameData::operator=(
const ExtensionApiFrameIdMap::FrameData& other) = default;
ExtensionApiFrameIdMap::RenderFrameIdKey::RenderFrameIdKey()
: render_process_id(content::ChildProcessHost::kInvalidUniqueID),
frame_routing_id(MSG_ROUTING_NONE) {}
ExtensionApiFrameIdMap::RenderFrameIdKey::RenderFrameIdKey(
int render_process_id,
int frame_routing_id)
: render_process_id(render_process_id),
frame_routing_id(frame_routing_id) {}
bool ExtensionApiFrameIdMap::RenderFrameIdKey::operator<(
const RenderFrameIdKey& other) const {
return std::tie(render_process_id, frame_routing_id) <
std::tie(other.render_process_id, other.frame_routing_id);
}
bool ExtensionApiFrameIdMap::RenderFrameIdKey::operator==(
const RenderFrameIdKey& other) const {
return render_process_id == other.render_process_id &&
frame_routing_id == other.frame_routing_id;
}
ExtensionApiFrameIdMap::ExtensionApiFrameIdMap() {}
ExtensionApiFrameIdMap::~ExtensionApiFrameIdMap() {}
// static
ExtensionApiFrameIdMap* ExtensionApiFrameIdMap::Get() {
return g_map_instance.Pointer();
}
// static
int ExtensionApiFrameIdMap::GetFrameId(content::RenderFrameHost* rfh) {
if (!rfh)
return kInvalidFrameId;
if (rfh->GetParent())
return rfh->GetFrameTreeNodeId();
return kTopFrameId;
}
// static
int ExtensionApiFrameIdMap::GetFrameId(
content::NavigationHandle* navigation_handle) {
return navigation_handle->IsInMainFrame()
? kTopFrameId
: navigation_handle->GetFrameTreeNodeId();
}
// static
int ExtensionApiFrameIdMap::GetParentFrameId(content::RenderFrameHost* rfh) {
return rfh ? GetFrameId(rfh->GetParent()) : kInvalidFrameId;
}
// static
int ExtensionApiFrameIdMap::GetParentFrameId(
content::NavigationHandle* navigation_handle) {
if (navigation_handle->IsInMainFrame())
return kInvalidFrameId;
if (navigation_handle->IsParentMainFrame())
return kTopFrameId;
return navigation_handle->GetParentFrame()->GetFrameTreeNodeId();
}
// 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->GetMainFrame();
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).
return web_contents->UnsafeFindFrameByFrameTreeNodeId(frame_id);
}
ExtensionApiFrameIdMap::FrameData ExtensionApiFrameIdMap::KeyToValue(
const RenderFrameIdKey& key,
bool require_live_frame) const {
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
key.render_process_id, key.frame_routing_id);
if (!rfh || (require_live_frame && !rfh->IsRenderFrameLive()))
return FrameData();
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(rfh);
base::Optional<GURL> pending_main_frame_url;
// Only set |pending_main_frame_url| if |rfh| is the main frame and a pending
// entry exists.
if (rfh->GetParent() == nullptr && web_contents &&
web_contents->GetController().GetPendingEntry()) {
pending_main_frame_url =
web_contents->GetController().GetPendingEntry()->GetURL();
}
// The RenderFrameHost may not have an associated WebContents in cases
// such as interstitial pages.
GURL last_committed_main_frame_url =
web_contents ? web_contents->GetLastCommittedURL() : GURL();
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(
web_contents, &tab_id, &window_id);
}
return FrameData(GetFrameId(rfh), GetParentFrameId(rfh), tab_id, window_id,
std::move(last_committed_main_frame_url),
std::move(pending_main_frame_url));
}
ExtensionApiFrameIdMap::FrameData ExtensionApiFrameIdMap::GetFrameData(
int render_process_id,
int render_frame_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const RenderFrameIdKey key(render_process_id, render_frame_id);
auto frame_id_iter = deleted_frame_data_map_.find(key);
if (frame_id_iter != deleted_frame_data_map_.end())
return frame_id_iter->second;
return KeyToValue(key, true /* require_live_frame */);
}
void ExtensionApiFrameIdMap::OnRenderFrameDeleted(
content::RenderFrameHost* rfh) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(rfh);
const RenderFrameIdKey key(rfh->GetProcess()->GetID(), rfh->GetRoutingID());
// 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(key, false /* require_live_frame */)});
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
[](ExtensionApiFrameIdMap* self, const RenderFrameIdKey& key) {
self->deleted_frame_data_map_.erase(key);
},
base::Unretained(this), key));
}
} // namespace extensions