#include "extensions/renderer/script_context_set.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/task/single_thread_task_runner.h"
#include "content/public/common/url_constants.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/worker_thread.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/renderer/extension_frame_helper.h"
#include "extensions/renderer/extensions_renderer_client.h"
#include "extensions/renderer/isolated_world_manager.h"
#include "extensions/renderer/script_context.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "v8/include/v8-isolate.h"
#include "v8/include/v8-object.h"
namespace extensions {
namespace {
ScriptContextSet* g_context_set = nullptr;
}
ScriptContextSet::ScriptContextSet(ExtensionIdSet* active_extension_ids)
: active_extension_ids_(active_extension_ids) {
DCHECK(!g_context_set);
g_context_set = this;
}
ScriptContextSet::~ScriptContextSet() {
g_context_set = nullptr;
}
ScriptContext* ScriptContextSet::Register(
blink::WebLocalFrame* frame,
const v8::Local<v8::Context>& v8_context,
int32_t world_id,
bool is_webview) {
const Extension* extension =
GetExtensionFromFrameAndWorld(frame, world_id, false);
const Extension* effective_extension =
GetExtensionFromFrameAndWorld(frame, world_id, true);
mojom::ViewType view_type = mojom::ViewType::kInvalid;
content::RenderFrame* render_frame =
content::RenderFrame::FromWebFrame(frame);
if (render_frame) {
ExtensionFrameHelper* frame_helper =
ExtensionFrameHelper::Get(render_frame);
DCHECK(frame_helper);
view_type = frame_helper->view_type();
}
GURL frame_url = ScriptContext::GetDocumentLoaderURLForFrame(frame);
Feature::Context context_type = ClassifyJavaScriptContext(
extension, world_id, frame_url, frame->GetDocument().GetSecurityOrigin(),
view_type, is_webview);
Feature::Context effective_context_type = ClassifyJavaScriptContext(
effective_extension, world_id,
ScriptContext::GetEffectiveDocumentURLForContext(frame, frame_url, true),
frame->GetDocument().GetSecurityOrigin(), view_type, is_webview);
ScriptContext* context =
new ScriptContext(v8_context, frame, extension, context_type,
effective_extension, effective_context_type);
contexts_.insert(context);
return context;
}
void ScriptContextSet::Remove(ScriptContext* context) {
if (contexts_.erase(context)) {
context->Invalidate();
base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
context);
}
}
ScriptContext* ScriptContextSet::GetCurrent() const {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
return isolate->InContext() ? GetByV8Context(isolate->GetCurrentContext())
: nullptr;
}
ScriptContext* ScriptContextSet::GetByV8Context(
const v8::Local<v8::Context>& v8_context) const {
for (ScriptContext* script_context : contexts_) {
if (script_context->v8_context() == v8_context)
return script_context;
}
return nullptr;
}
ScriptContext* ScriptContextSet::GetContextByObject(
const v8::Local<v8::Object>& object) {
v8::Local<v8::Context> context;
if (!object->GetCreationContext().ToLocal(&context))
return nullptr;
return GetContextByV8Context(context);
}
ScriptContext* ScriptContextSet::GetContextByV8Context(
const v8::Local<v8::Context>& v8_context) {
return g_context_set ? g_context_set->GetByV8Context(v8_context) : nullptr;
}
ScriptContext* ScriptContextSet::GetMainWorldContextForFrame(
content::RenderFrame* render_frame) {
v8::HandleScope handle_scope(blink::MainThreadIsolate());
return GetContextByV8Context(
render_frame->GetWebFrame()->MainWorldScriptContext());
}
void ScriptContextSet::ForEach(
const std::string& extension_id,
content::RenderFrame* render_frame,
const base::RepeatingCallback<void(ScriptContext*)>& callback) {
std::set<ScriptContext*> contexts_copy = contexts_;
for (ScriptContext* context : contexts_copy) {
if (!context->is_valid())
continue;
if (!extension_id.empty()) {
const Extension* extension = context->extension();
if (!extension || (extension_id != extension->id()))
continue;
}
content::RenderFrame* context_render_frame = context->GetRenderFrame();
if (render_frame && render_frame != context_render_frame)
continue;
callback.Run(context);
}
}
void ScriptContextSet::OnExtensionUnloaded(const std::string& extension_id) {
ScriptContextSetIterable::ForEach(
extension_id,
base::BindRepeating(&ScriptContextSet::Remove, base::Unretained(this)));
}
void ScriptContextSet::AddForTesting(std::unique_ptr<ScriptContext> context) {
contexts_.insert(context.release());
}
const Extension* ScriptContextSet::GetExtensionFromFrameAndWorld(
blink::WebLocalFrame* frame,
int32_t world_id,
bool use_effective_url) {
std::string extension_id;
if (world_id != 0) {
extension_id =
IsolatedWorldManager::GetInstance().GetHostIdForIsolatedWorld(world_id);
} else {
GURL frame_url = ScriptContext::GetAccessCheckedFrameURL(frame);
frame_url = ScriptContext::GetEffectiveDocumentURLForContext(
frame, frame_url, use_effective_url);
extension_id =
RendererExtensionRegistry::Get()->GetExtensionOrAppIDByURL(frame_url);
}
const Extension* extension =
RendererExtensionRegistry::Get()->GetByID(extension_id);
if (!extension && !extension_id.empty() && extension_id != "invalid") {
}
return extension;
}
Feature::Context ScriptContextSet::ClassifyJavaScriptContext(
const Extension* extension,
int32_t world_id,
const GURL& url,
const blink::WebSecurityOrigin& origin,
mojom::ViewType view_type,
bool is_webview) {
if (world_id >= ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId()) {
CHECK_EQ(kMainThreadId, content::WorkerThread::GetCurrentId());
absl::optional<mojom::ExecutionWorld> execution_world =
IsolatedWorldManager::GetInstance().GetExecutionWorldForIsolatedWorld(
world_id);
if (execution_world == mojom::ExecutionWorld::kUserScript) {
CHECK(extension);
return Feature::USER_SCRIPT_CONTEXT;
}
return extension ?
Feature::CONTENT_SCRIPT_CONTEXT
: Feature::UNSPECIFIED_CONTEXT;
}
if (ScriptContext::IsSandboxedPage(url))
return Feature::WEB_PAGE_CONTEXT;
if (extension && active_extension_ids_->count(extension->id()) > 0) {
if (extension->is_hosted_app() &&
extension->location() != mojom::ManifestLocation::kComponent) {
return Feature::BLESSED_WEB_PAGE_CONTEXT;
}
if (is_lock_screen_context_)
return Feature::LOCK_SCREEN_EXTENSION_CONTEXT;
if (is_webview) {
return Feature::UNBLESSED_EXTENSION_CONTEXT;
}
if (view_type == mojom::ViewType::kOffscreenDocument)
return Feature::OFFSCREEN_EXTENSION_CONTEXT;
return Feature::BLESSED_EXTENSION_CONTEXT;
}
DCHECK_NE(mojom::ViewType::kOffscreenDocument, view_type);
if (!origin.IsOpaque() &&
RendererExtensionRegistry::Get()->ExtensionBindingsAllowed(url)) {
if (!extension)
return Feature::UNSPECIFIED_CONTEXT;
return extension->is_hosted_app() ? Feature::BLESSED_WEB_PAGE_CONTEXT
: Feature::UNBLESSED_EXTENSION_CONTEXT;
}
if (!url.is_valid())
return Feature::UNSPECIFIED_CONTEXT;
if (url.SchemeIs(content::kChromeUIScheme)
#if defined(OHOS_ARKWEB_EXTENSIONS)
|| url.SchemeIs(content::kArkWebUIScheme)
#endif
)
return Feature::WEBUI_CONTEXT;
if (url.SchemeIs(content::kChromeUIUntrustedScheme))
return Feature::WEBUI_UNTRUSTED_CONTEXT;
return Feature::WEB_PAGE_CONTEXT;
}
}