diff --git a/include/v8.h b/include/v8.h index 8d011113d3..ee10f1297e 100644 --- a/include/v8.h +++ b/include/v8.h @@ -9904,6 +9904,32 @@ class V8_EXPORT Context { */ void DetachGlobal(); + /** + * Reason for detaching a window. + */ + enum DetachedWindowReason { + kWindowNotDetached = 0, + kDetachedWindowByNavigation, + kDetachedWindowByClosing, + kDetachedWindowByOtherReason + }; + + /** + * Sets a reason for detaching window, for reporting purposes. + * + * This API is experimental and may change or be deleted. Do not use! + * + * A window is a Blink concept. This API marks the context as backing + * a detached window. This doesn't necessarily mean that the context is + * detached. + * + * Every time a JS function is called within a context that has a non-zero + * DetachedWindowReason, Runtime::kReportDetachedWindowAccess is invoked, + * which will report this call to Blink via a callback, which in turn can + * report number of such calls via UKM metrics. + */ + void SetDetachedWindowReason(DetachedWindowReason reason); + /** * Creates a new context and returns a handle to the newly allocated * context. diff --git a/src/api/api.cc b/src/api/api.cc index e199267a03..a162257c5b 100644 --- a/src/api/api.cc +++ b/src/api/api.cc @@ -6002,6 +6002,20 @@ void Context::DetachGlobal() { isolate->bootstrapper()->DetachGlobal(context); } +void Context::SetDetachedWindowReason(DetachedWindowReason reason) { + i::Handle context = Utils::OpenHandle(this); + i::Isolate* isolate = context->GetIsolate(); + ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate); + CHECK(context->IsNativeContext()); + i::Handle native_context = + i::Handle::cast(context); + // Prioritize kDetachedWindowByNavigation over other reasons. + if (native_context->GetDetachedWindowReason() != + kDetachedWindowByNavigation) { + native_context->SetDetachedWindowReason(reason); + } +} + Local Context::GetExtrasBindingObject() { i::Handle context = Utils::OpenHandle(this); i::Isolate* isolate = context->GetIsolate(); diff --git a/src/builtins/arm/builtins-arm.cc b/src/builtins/arm/builtins-arm.cc index 97ac98df1d..ac0ea9d4a9 100644 --- a/src/builtins/arm/builtins-arm.cc +++ b/src/builtins/arm/builtins-arm.cc @@ -1953,6 +1953,14 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm, __ tst(r3, Operand(SharedFunctionInfo::IsNativeBit::kMask | SharedFunctionInfo::IsStrictBit::kMask)); __ b(ne, &done_convert); + + // Check if the window is marked as detached. + Label detached_window, after_detached_window; + __ LoadNativeContextSlot(Context::DETACHED_WINDOW_REASON_INDEX, r3); + __ cmp(r3, Operand(Smi::zero())); + __ b(ne, &detached_window); + __ bind(&after_detached_window); + { // ----------- S t a t e ------------- // -- r0 : the number of arguments (not including the receiver) @@ -2024,6 +2032,15 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm, __ push(r1); __ CallRuntime(Runtime::kThrowConstructorNonCallableError); } + + __ bind(&detached_window); + { + FrameScope frame(masm, StackFrame::INTERNAL); + __ PushCallerSaved(kDontSaveFPRegs, r3); + __ CallRuntime(Runtime::kReportDetachedWindowAccess); + __ PopCallerSaved(kDontSaveFPRegs, r3); + } + __ jmp(&after_detached_window); } namespace { diff --git a/src/builtins/arm64/builtins-arm64.cc b/src/builtins/arm64/builtins-arm64.cc index 4147eddcae..d8e371ab65 100644 --- a/src/builtins/arm64/builtins-arm64.cc +++ b/src/builtins/arm64/builtins-arm64.cc @@ -2361,6 +2361,14 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm, SharedFunctionInfo::IsNativeBit::kMask | SharedFunctionInfo::IsStrictBit::kMask, &done_convert); + + // Check if the window is marked as detached. + Label detached_window, after_detached_window; + __ LoadNativeContextSlot(Context::DETACHED_WINDOW_REASON_INDEX, x3); + __ CmpTagged(x3, Immediate(Smi::zero())); + __ B(ne, &detached_window); + __ bind(&after_detached_window); + { // ----------- S t a t e ------------- // -- x0 : the number of arguments (not including the receiver) @@ -2431,6 +2439,15 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm, __ PushArgument(x1); __ CallRuntime(Runtime::kThrowConstructorNonCallableError); } + + __ bind(&detached_window); + { + FrameScope frame(masm, StackFrame::INTERNAL); + __ PushCallerSaved(kDontSaveFPRegs, x3); + __ CallRuntime(Runtime::kReportDetachedWindowAccess); + __ PopCallerSaved(kDontSaveFPRegs, x3); + } + __ jmp(&after_detached_window); } namespace { diff --git a/src/builtins/x64/builtins-x64.cc b/src/builtins/x64/builtins-x64.cc index 7a39e49799..ea0caabddc 100644 --- a/src/builtins/x64/builtins-x64.cc +++ b/src/builtins/x64/builtins-x64.cc @@ -2206,6 +2206,15 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm, Immediate(SharedFunctionInfo::IsNativeBit::kMask | SharedFunctionInfo::IsStrictBit::kMask)); __ j(not_zero, &done_convert); + + // Check if the window is marked as detached. + Label detached_window, after_detached_window; + __ LoadNativeContextSlot(Context::DETACHED_WINDOW_REASON_INDEX, + kScratchRegister); + __ Cmp(kScratchRegister, Smi::zero()); + __ j(not_zero, &detached_window); + __ bind(&after_detached_window); + { // ----------- S t a t e ------------- // -- rax : the number of arguments (not including the receiver) @@ -2283,6 +2292,15 @@ void Builtins::Generate_CallFunction(MacroAssembler* masm, __ Push(rdi); __ CallRuntime(Runtime::kThrowConstructorNonCallableError); } + + __ bind(&detached_window); + { + FrameScope frame(masm, StackFrame::INTERNAL); + __ PushCallerSaved(kDontSaveFPRegs, kScratchRegister); + __ CallRuntime(Runtime::kReportDetachedWindowAccess); + __ PopCallerSaved(kDontSaveFPRegs, kScratchRegister); + } + __ jmp(&after_detached_window); } namespace { diff --git a/src/codegen/x64/macro-assembler-x64.cc b/src/codegen/x64/macro-assembler-x64.cc index 1f06d205cf..e2e7138ab4 100644 --- a/src/codegen/x64/macro-assembler-x64.cc +++ b/src/codegen/x64/macro-assembler-x64.cc @@ -1185,10 +1185,10 @@ void MacroAssembler::SmiCompare(Register dst, Smi src) { } void MacroAssembler::Cmp(Register dst, Smi src) { - DCHECK_NE(dst, kScratchRegister); if (src.value() == 0) { test_tagged(dst, dst); } else { + DCHECK_NE(dst, kScratchRegister); Register constant_reg = GetSmiConstant(src); cmp_tagged(dst, constant_reg); } diff --git a/src/heap/factory.cc b/src/heap/factory.cc index d941b2a35f..4bb125e28a 100644 --- a/src/heap/factory.cc +++ b/src/heap/factory.cc @@ -1431,6 +1431,7 @@ Handle Factory::NewNativeContext() { context->set_scope_info(ReadOnlyRoots(isolate()).native_scope_info()); context->set_previous(Context::unchecked_cast(Smi::zero())); context->set_extension(*undefined_value()); + context->SetDetachedWindowReason(v8::Context::kWindowNotDetached); context->set_errors_thrown(Smi::zero()); context->set_math_random_index(Smi::zero()); context->set_serialized_objects(*empty_fixed_array()); diff --git a/src/objects/contexts.cc b/src/objects/contexts.cc index b30b877197..b23459a840 100644 --- a/src/objects/contexts.cc +++ b/src/objects/contexts.cc @@ -485,5 +485,16 @@ STATIC_ASSERT(NativeContext::kSize == (Context::SizeFor(NativeContext::NATIVE_CONTEXT_SLOTS) + kSystemPointerSize)); +void NativeContext::SetDetachedWindowReason( + v8::Context::DetachedWindowReason reason) { + set_detached_window_reason(Smi::FromEnum(reason)); +} + +v8::Context::DetachedWindowReason NativeContext::GetDetachedWindowReason() + const { + return static_cast( + detached_window_reason().value()); +} + } // namespace internal } // namespace v8 diff --git a/src/objects/contexts.h b/src/objects/contexts.h index 2e67f49496..7d6afacafb 100644 --- a/src/objects/contexts.h +++ b/src/objects/contexts.h @@ -366,6 +366,7 @@ enum ContextLookupFlags { V(WEAKMAP_GET_INDEX, JSFunction, weakmap_get) \ V(WEAKSET_ADD_INDEX, JSFunction, weakset_add) \ V(OSR_CODE_CACHE_INDEX, WeakFixedArray, osr_code_cache) \ + V(DETACHED_WINDOW_REASON_INDEX, Smi, detached_window_reason) \ NATIVE_CONTEXT_INTRINSIC_FUNCTIONS(V) // A table of all script contexts. Every loaded top-level script with top-level @@ -729,6 +730,9 @@ class NativeContext : public Context { void IncrementErrorsThrown(); int GetErrorsThrown(); + void SetDetachedWindowReason(v8::Context::DetachedWindowReason reason); + v8::Context::DetachedWindowReason GetDetachedWindowReason() const; + private: STATIC_ASSERT(OffsetOfElementAt(EMBEDDER_DATA_INDEX) == Internals::kNativeContextEmbedderDataOffset); diff --git a/src/runtime/runtime-internal.cc b/src/runtime/runtime-internal.cc index 4085aad7c1..ed5fd4bcc7 100644 --- a/src/runtime/runtime-internal.cc +++ b/src/runtime/runtime-internal.cc @@ -82,6 +82,19 @@ RUNTIME_FUNCTION(Runtime_ThrowSymbolAsyncIteratorInvalid) { isolate, NewTypeError(MessageTemplate::kSymbolAsyncIteratorInvalid)); } +RUNTIME_FUNCTION(Runtime_ReportDetachedWindowAccess) { + HandleScope scope(isolate); + DCHECK_EQ(0, args.length()); + Handle native_context(isolate->context().native_context(), + isolate); + // TODO(bartekn,chromium:1018156): Report this to Blink, for it to emit it + // via UKM. Use native_context->detached_window_reason().value() + // This will be addressed as the first step after this CL lands. + + // The return value isn't needed, but RUNTIME_FUNCTION sets it up. + return ReadOnlyRoots(isolate).undefined_value(); +} + #define THROW_ERROR(isolate, args, call) \ HandleScope scope(isolate); \ DCHECK_LE(1, args.length()); \ diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index a3c165efb3..75583cac70 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -221,6 +221,7 @@ namespace internal { F(NewTypeError, 2, 1) \ F(OrdinaryHasInstance, 2, 1) \ F(PromoteScheduledException, 0, 1) \ + F(ReportDetachedWindowAccess, 0, 1) \ F(ReportMessage, 1, 1) \ F(ReThrow, 1, 1) \ F(RunMicrotaskCallback, 2, 1) \ diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index d4afec51b2..9c9ac64b20 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -9646,6 +9646,38 @@ TEST(DetachedAccesses) { } +TEST(DetachedWindow) { + LocalContext env1; + v8::HandleScope scope(env1->GetIsolate()); + + // Create second environment. + Local inner_global_template = + FunctionTemplate::New(env1->GetIsolate())->InstanceTemplate(); + v8::Local env2 = + Context::New(env1->GetIsolate(), nullptr, inner_global_template); + + Local foo = v8_str("foo"); + + // Set same security token for env1 and env2. + env1->SetSecurityToken(foo); + env2->SetSecurityToken(foo); + + { + v8::Context::Scope scope(env2); + CompileRun("function fun() { }"); + CHECK(env1->Global() + ->Set(env1.local(), v8_str("fun"), CompileRun("fun")) + .FromJust()); + } + + env2->SetDetachedWindowReason(v8::Context::kDetachedWindowByNavigation); + + CompileRun("fun()"); + // This merely tests for not crashing, because currently + // Runtime_ReportDetachedWindowAccess does nothing. +} + + static bool allowed_access = false; static bool AccessBlocker(Local accessing_context, Local accessed_object,