1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_STATE_H_ 6 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_STATE_H_ 7 8 #include <memory> 9 10 #include "gin/public/context_holder.h" 11 #include "gin/public/gin_embedders.h" 12 #include "third_party/blink/public/common/tokens/tokens.h" 13 #include "third_party/blink/renderer/platform/bindings/scoped_persistent.h" 14 #include "third_party/blink/renderer/platform/bindings/v8_cross_origin_callback_info.h" 15 #include "third_party/blink/renderer/platform/heap/handle.h" 16 #include "third_party/blink/renderer/platform/heap/heap.h" 17 #include "third_party/blink/renderer/platform/heap/self_keep_alive.h" 18 #include "third_party/blink/renderer/platform/platform_export.h" 19 #include "v8/include/v8.h" 20 21 namespace blink { 22 23 class DOMWrapperWorld; 24 class ExecutionContext; 25 class ScriptValue; 26 class V8PerContextData; 27 28 // ScriptState is an abstraction class that holds all information about script 29 // execution (e.g., v8::Isolate, v8::Context, DOMWrapperWorld, ExecutionContext 30 // etc). If you need any info about the script execution, you're expected to 31 // pass around ScriptState in the code base. ScriptState is in a 1:1 32 // relationship with v8::Context. 33 // 34 // When you need ScriptState, you can add [CallWith=ScriptState] to IDL files 35 // and pass around ScriptState into a place where you need ScriptState. 36 // 37 // In some cases, you need ScriptState in code that doesn't have any JavaScript 38 // on the stack. Then you can store ScriptState on a C++ object using 39 // Member<ScriptState> or Persistent<ScriptState>. 40 // 41 // class SomeObject : public GarbageCollected<SomeObject> { 42 // void someMethod(ScriptState* scriptState) { 43 // script_state_ = scriptState; // Record the ScriptState. 44 // ...; 45 // } 46 // 47 // void asynchronousMethod() { 48 // if (!script_state_->contextIsValid()) { 49 // // It's possible that the context is already gone. 50 // return; 51 // } 52 // // Enter the ScriptState. 53 // ScriptState::Scope scope(script_state_); 54 // // Do V8 related things. 55 // ToV8(...); 56 // } 57 // 58 // virtual void Trace(Visitor* visitor) const { 59 // visitor->Trace(script_state_); // ScriptState also needs to be traced. 60 // } 61 // 62 // Member<ScriptState> script_state_; 63 // }; 64 // 65 // You should not store ScriptState on a C++ object that can be accessed 66 // by multiple worlds. For example, you can store ScriptState on 67 // ScriptPromiseResolver, ScriptValue etc because they can be accessed from one 68 // world. However, you cannot store ScriptState on a DOM object that has 69 // an IDL interface because the DOM object can be accessed from multiple 70 // worlds. If ScriptState of one world "leak"s to another world, you will 71 // end up with leaking any JavaScript objects from one Chrome extension 72 // to another Chrome extension, which is a severe security bug. 73 // 74 // Lifetime: 75 // ScriptState is created when v8::Context is created. 76 // ScriptState is destroyed when v8::Context is garbage-collected and 77 // all V8 proxy objects that have references to the ScriptState are destructed. 78 class PLATFORM_EXPORT ScriptState final : public GarbageCollected<ScriptState> { 79 public: 80 class Scope final { 81 STACK_ALLOCATED(); 82 83 public: 84 // You need to make sure that scriptState->context() is not empty before 85 // creating a Scope. Scope(ScriptState * script_state)86 explicit Scope(ScriptState* script_state) 87 : handle_scope_(script_state->GetIsolate()), 88 context_(script_state->GetContext()) { 89 DCHECK(script_state->ContextIsValid()); 90 context_->Enter(); 91 } 92 ~Scope()93 ~Scope() { context_->Exit(); } 94 95 private: 96 v8::HandleScope handle_scope_; 97 v8::Local<v8::Context> context_; 98 }; 99 100 // Use EscapableScope if you have to return a v8::Local to an outer scope. 101 // See v8::EscapableHandleScope. 102 class EscapableScope final { 103 STACK_ALLOCATED(); 104 105 public: 106 // You need to make sure that scriptState->context() is not empty before 107 // creating a Scope. EscapableScope(ScriptState * script_state)108 explicit EscapableScope(ScriptState* script_state) 109 : handle_scope_(script_state->GetIsolate()), 110 context_(script_state->GetContext()) { 111 DCHECK(script_state->ContextIsValid()); 112 context_->Enter(); 113 } 114 ~EscapableScope()115 ~EscapableScope() { context_->Exit(); } 116 Escape(v8::Local<v8::Value> value)117 v8::Local<v8::Value> Escape(v8::Local<v8::Value> value) { 118 return handle_scope_.Escape(value); 119 } 120 121 private: 122 v8::EscapableHandleScope handle_scope_; 123 v8::Local<v8::Context> context_; 124 }; 125 126 // If this ScriptState is associated with an ExecutionContext then it must be 127 // provided here, otherwise providing nullptr is fine. 128 ScriptState(v8::Local<v8::Context>, 129 scoped_refptr<DOMWrapperWorld>, 130 ExecutionContext* execution_context); 131 ~ScriptState(); 132 Trace(Visitor *)133 void Trace(Visitor*) const {} 134 Current(v8::Isolate * isolate)135 static ScriptState* Current(v8::Isolate* isolate) { // DEPRECATED 136 return From(isolate->GetCurrentContext()); 137 } 138 ForCurrentRealm(const v8::FunctionCallbackInfo<v8::Value> & info)139 static ScriptState* ForCurrentRealm( 140 const v8::FunctionCallbackInfo<v8::Value>& info) { 141 return From(info.GetIsolate()->GetCurrentContext()); 142 } 143 ForCurrentRealm(const v8::PropertyCallbackInfo<v8::Value> & info)144 static ScriptState* ForCurrentRealm( 145 const v8::PropertyCallbackInfo<v8::Value>& info) { 146 return From(info.GetIsolate()->GetCurrentContext()); 147 } 148 ForRelevantRealm(const v8::FunctionCallbackInfo<v8::Value> & info)149 static ScriptState* ForRelevantRealm( 150 const v8::FunctionCallbackInfo<v8::Value>& info) { 151 return From(info.Holder()->CreationContext()); 152 } 153 ForRelevantRealm(const V8CrossOriginCallbackInfo & info)154 static ScriptState* ForRelevantRealm(const V8CrossOriginCallbackInfo& info) { 155 return From(info.Holder()->CreationContext()); 156 } 157 ForRelevantRealm(const v8::PropertyCallbackInfo<v8::Value> & info)158 static ScriptState* ForRelevantRealm( 159 const v8::PropertyCallbackInfo<v8::Value>& info) { 160 return From(info.Holder()->CreationContext()); 161 } 162 ForRelevantRealm(const v8::PropertyCallbackInfo<void> & info)163 static ScriptState* ForRelevantRealm( 164 const v8::PropertyCallbackInfo<void>& info) { 165 return From(info.Holder()->CreationContext()); 166 } 167 From(v8::Local<v8::Context> context)168 static ScriptState* From(v8::Local<v8::Context> context) { 169 DCHECK(!context.IsEmpty()); 170 ScriptState* script_state = 171 static_cast<ScriptState*>(context->GetAlignedPointerFromEmbedderData( 172 kV8ContextPerContextDataIndex)); 173 // ScriptState::from() must not be called for a context that does not have 174 // valid embedder data in the embedder field. 175 SECURITY_CHECK(script_state); 176 SECURITY_CHECK(script_state->context_ == context); 177 return script_state; 178 } 179 GetIsolate()180 v8::Isolate* GetIsolate() const { return isolate_; } World()181 DOMWrapperWorld& World() const { return *world_; } GetToken()182 const V8ContextToken& GetToken() const { return token_; } 183 184 // This can return an empty handle if the v8::Context is gone. GetContext()185 v8::Local<v8::Context> GetContext() const { 186 return context_.NewLocal(isolate_); 187 } ContextIsValid()188 bool ContextIsValid() const { 189 return !context_.IsEmpty() && per_context_data_; 190 } 191 void DetachGlobalObject(); 192 PerContextData()193 V8PerContextData* PerContextData() const { return per_context_data_.get(); } 194 void DisposePerContextData(); 195 196 // This method is expected to be called only from 197 // WorkerOrWorkletScriptController to run operations that should have been 198 // invoked by a weak callback if a V8 GC were run, in a worker thread 199 // termination. 200 void DissociateContext(); 201 202 private: 203 static void OnV8ContextCollectedCallback( 204 const v8::WeakCallbackInfo<ScriptState>&); 205 206 v8::Isolate* isolate_; 207 // This persistent handle is weak. 208 ScopedPersistent<v8::Context> context_; 209 210 // This refptr doesn't cause a cycle because all persistent handles that 211 // DOMWrapperWorld holds are weak. 212 scoped_refptr<DOMWrapperWorld> world_; 213 214 // This std::unique_ptr causes a cycle: 215 // V8PerContextData --(Persistent)--> v8::Context --(RefPtr)--> ScriptState 216 // --(std::unique_ptr)--> V8PerContextData 217 // So you must explicitly clear the std::unique_ptr by calling 218 // disposePerContextData() once you no longer need V8PerContextData. 219 // Otherwise, the v8::Context will leak. 220 std::unique_ptr<V8PerContextData> per_context_data_; 221 222 // v8::Context has an internal field to this ScriptState* as a raw pointer, 223 // which is out of scope of Blink GC, but it must be a strong reference. We 224 // use |reference_from_v8_context_| to represent this strong reference. The 225 // lifetime of |reference_from_v8_context_| and the internal field must match 226 // exactly. 227 SelfKeepAlive<ScriptState> reference_from_v8_context_; 228 229 // Serves as a unique ID for this context, which can be used to name the 230 // context in browser/renderer communications. 231 V8ContextToken token_; 232 233 static constexpr int kV8ContextPerContextDataIndex = 234 static_cast<int>(gin::kPerContextDataStartIndex) + 235 static_cast<int>(gin::kEmbedderBlink); 236 237 DISALLOW_COPY_AND_ASSIGN(ScriptState); 238 }; 239 240 // ScriptStateProtectingContext keeps the context associated with the 241 // ScriptState alive. You need to call Clear() once you no longer need the 242 // context. Otherwise, the context will leak. 243 class ScriptStateProtectingContext final 244 : public GarbageCollected<ScriptStateProtectingContext> { 245 public: ScriptStateProtectingContext(ScriptState * script_state)246 explicit ScriptStateProtectingContext(ScriptState* script_state) 247 : script_state_(script_state) { 248 if (script_state_) { 249 context_.Set(script_state_->GetIsolate(), script_state_->GetContext()); 250 context_.Get().AnnotateStrongRetainer( 251 "blink::ScriptStateProtectingContext::context_"); 252 } 253 } 254 Trace(Visitor * visitor)255 void Trace(Visitor* visitor) const { visitor->Trace(script_state_); } 256 Get()257 ScriptState* Get() const { return script_state_; } Reset()258 void Reset() { 259 script_state_ = nullptr; 260 context_.Clear(); 261 } 262 263 // ScriptState like interface ContextIsValid()264 bool ContextIsValid() const { return script_state_->ContextIsValid(); } GetIsolate()265 v8::Isolate* GetIsolate() const { return script_state_->GetIsolate(); } GetContext()266 v8::Local<v8::Context> GetContext() const { 267 return script_state_->GetContext(); 268 } 269 270 private: 271 Member<ScriptState> script_state_; 272 ScopedPersistent<v8::Context> context_; 273 274 DISALLOW_COPY_AND_ASSIGN(ScriptStateProtectingContext); 275 }; 276 277 } // namespace blink 278 279 #endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_STATE_H_ 280