diff --git a/content/renderer/gm_object.cc b/content/renderer/gm_object.cc new file mode 100644 index 0000000000000..80d519606cc3a --- /dev/null +++ b/content/renderer/gm_object.cc @@ -0,0 +1,96 @@ +// Copyright 2026 The Chromium Authors & Alisa Esage +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/gm_object.h" + +#include + +#include "v8/include/v8-array-buffer.h" +#include "v8/include/v8-context.h" +#include "v8/include/v8-function.h" +#include "v8/include/v8-object.h" +#include "v8/include/v8-primitive.h" +#include "v8/include/v8-typed-array.h" + +namespace content { + +namespace { + +uint64_t ArgToAddr(const v8::FunctionCallbackInfo& args, int i) { + if (args.Length() <= i) return 0; + v8::Local ctx = args.GetIsolate()->GetCurrentContext(); + return static_cast(args[i]->NumberValue(ctx).FromMaybe(0.0)); +} + +uint32_t ArgToUint32(const v8::FunctionCallbackInfo& args, int i) { + if (args.Length() <= i) return 0; + v8::Local ctx = args.GetIsolate()->GetCurrentContext(); + return args[i]->Uint32Value(ctx).FromMaybe(0); +} + +// read_memory(addr, size) -> Uint8Array +// Reads |size| bytes from |addr| in the renderer's own address space. +void GmReadMemory(const v8::FunctionCallbackInfo& args) { + v8::Isolate* iso = args.GetIsolate(); + uint64_t addr = ArgToAddr(args, 0); + uint32_t size = ArgToUint32(args, 1); + if (!addr || !size) return; + + auto ab = v8::ArrayBuffer::New(iso, size); + memcpy(ab->GetBackingStore()->Data(), + reinterpret_cast(addr), size); + args.GetReturnValue().Set(v8::Uint8Array::New(ab, 0, size)); +} + +// addrof(obj) -> Number +// Returns the address of the V8 HeapObject. Works on any JS value. +// Clears the low tag bit (1 = HeapObject). +void GmAddrOf(const v8::FunctionCallbackInfo& args) { + v8::Isolate* iso = args.GetIsolate(); + if (args.Length() < 1) { + args.GetReturnValue().Set(v8::Number::New(iso, 0.0)); + return; + } + // In indirect-handle mode Local::operator*() returns the slot address + // (location_), not the tagged heap pointer stored in the slot. + // Double-dereference to get the actual tagged Address. +#ifdef V8_ENABLE_DIRECT_HANDLE + uintptr_t tagged = reinterpret_cast(*args[0]); +#else + uintptr_t tagged = *reinterpret_cast(*args[0]); +#endif + uintptr_t addr = tagged & ~static_cast(1); + args.GetReturnValue().Set(v8::Number::New(iso, static_cast(addr))); +} + +void InstallMethod(v8::Isolate* iso, + v8::Local ctx, + v8::Local obj, + const char* name, + v8::FunctionCallback cb, + int argc) { + auto fn = v8::Function::New(ctx, cb, v8::Local(), argc) + .ToLocalChecked(); + fn->SetName(v8::String::NewFromUtf8(iso, name).ToLocalChecked()); + obj->Set(ctx, v8::String::NewFromUtf8(iso, name).ToLocalChecked(), fn) + .Check(); +} + +} // namespace + +void InstallGmObject(v8::Local context) { + v8::Isolate* iso = v8::Isolate::GetCurrent(); + v8::HandleScope scope(iso); + v8::Context::Scope ctx_scope(context); + + v8::Local gm = v8::Object::New(iso); + InstallMethod(iso, context, gm, "read_memory", GmReadMemory, 2); + InstallMethod(iso, context, gm, "addrof", GmAddrOf, 1); + + context->Global() + ->Set(context, v8::String::NewFromUtf8Literal(iso, "gm"), gm) + .Check(); +} + +} // namespace content diff --git a/content/renderer/gm_object.h b/content/renderer/gm_object.h new file mode 100644 index 0000000000000..7ce44b93411cb --- /dev/null +++ b/content/renderer/gm_object.h @@ -0,0 +1,21 @@ +// Copyright 2026 The Chromium Authors & Alisa Esage +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_GM_OBJECT_H_ +#define CONTENT_RENDERER_GM_OBJECT_H_ + +#include "v8/include/v8-context.h" + +namespace content { + +// Installs a |gm| global object into the given V8 context. +// +// Exposed API: +// gm.read_memory(addr, size) -> Uint8Array read from renderer address space +// gm.addrof(obj) -> Number V8 HeapObject address (tag cleared) +void InstallGmObject(v8::Local context); + +} // namespace content + +#endif // CONTENT_RENDERER_GM_OBJECT_H_ diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn index bbb6f87c5b824..9aa8c9cb16fb4 100644 --- a/content/renderer/BUILD.gn +++ b/content/renderer/BUILD.gn @@ -70,6 +70,8 @@ target(link_target_type, "renderer") { "effective_connection_type_helper.h", "frame_owner_properties_converter.cc", "frame_owner_properties_converter.h", + "gm_object.cc", + "gm_object.h", "gpu_benchmarking_extension.cc", "gpu_benchmarking_extension.h", "in_process_renderer_thread.cc", diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index 6ee766c522028..71c38b15d3c1f 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -95,6 +95,7 @@ #include "content/renderer/dom_automation_controller.h" #include "content/renderer/effective_connection_type_helper.h" #include "content/renderer/frame_owner_properties_converter.h" +#include "content/renderer/gm_object.h" #include "content/renderer/gpu_benchmarking_extension.h" #include "content/renderer/local_resource_url_loader_factory.h" #include "content/renderer/media/media_permission_dispatcher.h" @@ -4765,6 +4766,9 @@ void RenderFrameImpl::DidCreateScriptContext(v8::Local context, context, std::move(mojo_js_interface_broker_)); } + if (world_id == ISOLATED_WORLD_ID_GLOBAL) + InstallGmObject(context); + for (auto& observer : observers_) observer.DidCreateScriptContext(context, world_id); }