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