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 #include "extensions/renderer/script_context.h"
6 
7 #include "base/command_line.h"
8 #include "base/containers/flat_set.h"
9 #include "base/logging.h"
10 #include "base/stl_util.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/values.h"
14 #include "content/public/common/content_switches.h"
15 #include "content/public/common/url_constants.h"
16 #include "content/public/renderer/render_frame.h"
17 #include "extensions/common/constants.h"
18 #include "extensions/common/extension.h"
19 #include "extensions/common/extension_api.h"
20 #include "extensions/common/extension_urls.h"
21 #include "extensions/common/manifest_handlers/sandboxed_page_info.h"
22 #include "extensions/common/permissions/permissions_data.h"
23 #include "extensions/renderer/renderer_extension_registry.h"
24 #include "extensions/renderer/v8_helpers.h"
25 #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
26 #include "third_party/blink/public/platform/web_security_origin.h"
27 #include "third_party/blink/public/web/web_document.h"
28 #include "third_party/blink/public/web/web_document_loader.h"
29 #include "third_party/blink/public/web/web_local_frame.h"
30 #include "v8/include/v8.h"
31 
32 namespace extensions {
33 
34 namespace {
35 
GetContextTypeDescriptionString(Feature::Context context_type)36 std::string GetContextTypeDescriptionString(Feature::Context context_type) {
37   switch (context_type) {
38     case Feature::UNSPECIFIED_CONTEXT:
39       return "UNSPECIFIED";
40     case Feature::BLESSED_EXTENSION_CONTEXT:
41       return "BLESSED_EXTENSION";
42     case Feature::UNBLESSED_EXTENSION_CONTEXT:
43       return "UNBLESSED_EXTENSION";
44     case Feature::CONTENT_SCRIPT_CONTEXT:
45       return "CONTENT_SCRIPT";
46     case Feature::WEB_PAGE_CONTEXT:
47       return "WEB_PAGE";
48     case Feature::BLESSED_WEB_PAGE_CONTEXT:
49       return "BLESSED_WEB_PAGE";
50     case Feature::WEBUI_CONTEXT:
51       return "WEBUI";
52     case Feature::WEBUI_UNTRUSTED_CONTEXT:
53       return "WEBUI_UNTRUSTED";
54     case Feature::LOCK_SCREEN_EXTENSION_CONTEXT:
55       return "LOCK_SCREEN_EXTENSION";
56   }
57   NOTREACHED();
58   return std::string();
59 }
60 
ToStringOrDefault(v8::Isolate * isolate,const v8::Local<v8::String> & v8_string,const std::string & dflt)61 static std::string ToStringOrDefault(v8::Isolate* isolate,
62                                      const v8::Local<v8::String>& v8_string,
63                                      const std::string& dflt) {
64   if (v8_string.IsEmpty())
65     return dflt;
66   std::string ascii_value = *v8::String::Utf8Value(isolate, v8_string);
67   return ascii_value.empty() ? dflt : ascii_value;
68 }
69 
70 using FrameToDocumentLoader =
71     base::flat_map<blink::WebLocalFrame*, blink::WebDocumentLoader*>;
72 
FrameDocumentLoaderMap()73 FrameToDocumentLoader& FrameDocumentLoaderMap() {
74   static base::NoDestructor<FrameToDocumentLoader> map;
75   return *map;
76 }
77 
CurrentDocumentLoader(const blink::WebLocalFrame * frame)78 blink::WebDocumentLoader* CurrentDocumentLoader(
79     const blink::WebLocalFrame* frame) {
80   auto& map = FrameDocumentLoaderMap();
81   auto it = map.find(frame);
82   return it == map.end() ? frame->GetDocumentLoader() : it->second;
83 }
84 
85 }  // namespace
86 
ScopedFrameDocumentLoader(blink::WebLocalFrame * frame,blink::WebDocumentLoader * document_loader)87 ScriptContext::ScopedFrameDocumentLoader::ScopedFrameDocumentLoader(
88     blink::WebLocalFrame* frame,
89     blink::WebDocumentLoader* document_loader)
90     : frame_(frame), document_loader_(document_loader) {
91   auto& map = FrameDocumentLoaderMap();
92   DCHECK(map.find(frame_) == map.end());
93   map[frame_] = document_loader_;
94 }
95 
~ScopedFrameDocumentLoader()96 ScriptContext::ScopedFrameDocumentLoader::~ScopedFrameDocumentLoader() {
97   auto& map = FrameDocumentLoaderMap();
98   DCHECK_EQ(document_loader_, map.find(frame_)->second);
99   map.erase(frame_);
100 }
101 
ScriptContext(const v8::Local<v8::Context> & v8_context,blink::WebLocalFrame * web_frame,const Extension * extension,Feature::Context context_type,const Extension * effective_extension,Feature::Context effective_context_type)102 ScriptContext::ScriptContext(const v8::Local<v8::Context>& v8_context,
103                              blink::WebLocalFrame* web_frame,
104                              const Extension* extension,
105                              Feature::Context context_type,
106                              const Extension* effective_extension,
107                              Feature::Context effective_context_type)
108     : is_valid_(true),
109       v8_context_(v8_context->GetIsolate(), v8_context),
110       web_frame_(web_frame),
111       extension_(extension),
112       context_type_(context_type),
113       effective_extension_(effective_extension),
114       effective_context_type_(effective_context_type),
115       context_id_(base::UnguessableToken::Create()),
116       safe_builtins_(this),
117       isolate_(v8_context->GetIsolate()),
118       service_worker_version_id_(blink::mojom::kInvalidServiceWorkerVersionId) {
119   VLOG(1) << "Created context:\n" << GetDebugString();
120   v8_context_.AnnotateStrongRetainer("extensions::ScriptContext::v8_context_");
121   if (web_frame_)
122     url_ = GetAccessCheckedFrameURL(web_frame_);
123 }
124 
~ScriptContext()125 ScriptContext::~ScriptContext() {
126   VLOG(1) << "Destroyed context for extension\n"
127           << "  extension id: " << GetExtensionID() << "\n"
128           << "  effective extension id: "
129           << (effective_extension_.get() ? effective_extension_->id() : "");
130   CHECK(!is_valid_) << "ScriptContexts must be invalidated before destruction";
131 }
132 
133 // static
IsSandboxedPage(const GURL & url)134 bool ScriptContext::IsSandboxedPage(const GURL& url) {
135   // TODO(kalman): This is checking the wrong thing. See comment in
136   // HasAccessOrThrowError.
137   if (url.SchemeIs(kExtensionScheme)) {
138     const Extension* extension =
139         RendererExtensionRegistry::Get()->GetByID(url.host());
140     if (extension) {
141       return SandboxedPageInfo::IsSandboxedPage(extension, url.path());
142     }
143   }
144   return false;
145 }
146 
SetModuleSystem(std::unique_ptr<ModuleSystem> module_system)147 void ScriptContext::SetModuleSystem(
148     std::unique_ptr<ModuleSystem> module_system) {
149   module_system_ = std::move(module_system);
150   module_system_->Initialize();
151 }
152 
Invalidate()153 void ScriptContext::Invalidate() {
154   DCHECK(thread_checker_.CalledOnValidThread());
155   CHECK(is_valid_);
156   is_valid_ = false;
157 
158   // TODO(kalman): Make ModuleSystem use AddInvalidationObserver.
159   // Ownership graph is a bit weird here.
160   if (module_system_)
161     module_system_->Invalidate();
162 
163   // Swap |invalidate_observers_| to a local variable to clear it, and to make
164   // sure it's not mutated as we iterate.
165   std::vector<base::OnceClosure> observers;
166   observers.swap(invalidate_observers_);
167   for (base::OnceClosure& observer : observers) {
168     std::move(observer).Run();
169   }
170   DCHECK(invalidate_observers_.empty())
171       << "Invalidation observers cannot be added during invalidation";
172 
173   v8_context_.Reset();
174 }
175 
AddInvalidationObserver(base::OnceClosure observer)176 void ScriptContext::AddInvalidationObserver(base::OnceClosure observer) {
177   DCHECK(thread_checker_.CalledOnValidThread());
178   invalidate_observers_.push_back(std::move(observer));
179 }
180 
GetExtensionID() const181 const std::string& ScriptContext::GetExtensionID() const {
182   DCHECK(thread_checker_.CalledOnValidThread());
183   return extension_.get() ? extension_->id() : base::EmptyString();
184 }
185 
GetRenderFrame() const186 content::RenderFrame* ScriptContext::GetRenderFrame() const {
187   DCHECK(thread_checker_.CalledOnValidThread());
188   if (web_frame_)
189     return content::RenderFrame::FromWebFrame(web_frame_);
190   return NULL;
191 }
192 
SafeCallFunction(const v8::Local<v8::Function> & function,int argc,v8::Local<v8::Value> argv[])193 void ScriptContext::SafeCallFunction(const v8::Local<v8::Function>& function,
194                                      int argc,
195                                      v8::Local<v8::Value> argv[]) {
196   SafeCallFunction(function, argc, argv,
197                    ScriptInjectionCallback::CompleteCallback());
198 }
199 
SafeCallFunction(const v8::Local<v8::Function> & function,int argc,v8::Local<v8::Value> argv[],const ScriptInjectionCallback::CompleteCallback & callback)200 void ScriptContext::SafeCallFunction(
201     const v8::Local<v8::Function>& function,
202     int argc,
203     v8::Local<v8::Value> argv[],
204     const ScriptInjectionCallback::CompleteCallback& callback) {
205   DCHECK(thread_checker_.CalledOnValidThread());
206   v8::HandleScope handle_scope(isolate());
207   v8::Context::Scope scope(v8_context());
208   v8::MicrotasksScope microtasks(isolate(),
209                                  v8::MicrotasksScope::kDoNotRunMicrotasks);
210   v8::Local<v8::Object> global = v8_context()->Global();
211   if (web_frame_) {
212     ScriptInjectionCallback* wrapper_callback = nullptr;
213     if (!callback.is_null()) {
214       // ScriptInjectionCallback manages its own lifetime.
215       wrapper_callback = new ScriptInjectionCallback(callback);
216     }
217     web_frame_->RequestExecuteV8Function(v8_context(), function, global, argc,
218                                          argv, wrapper_callback);
219   } else {
220     v8::MaybeLocal<v8::Value> maybe_result =
221         function->Call(v8_context(), global, argc, argv);
222     v8::Local<v8::Value> result;
223     if (!callback.is_null() && maybe_result.ToLocal(&result)) {
224       std::vector<v8::Local<v8::Value>> results(1, result);
225       callback.Run(results);
226     }
227   }
228 }
229 
GetAvailability(const std::string & api_name)230 Feature::Availability ScriptContext::GetAvailability(
231     const std::string& api_name) {
232   return GetAvailability(api_name, CheckAliasStatus::ALLOWED);
233 }
234 
GetAvailability(const std::string & api_name,CheckAliasStatus check_alias)235 Feature::Availability ScriptContext::GetAvailability(
236     const std::string& api_name,
237     CheckAliasStatus check_alias) {
238   DCHECK(thread_checker_.CalledOnValidThread());
239   if (base::StartsWith(api_name, "test", base::CompareCase::SENSITIVE)) {
240     bool allowed = base::CommandLine::ForCurrentProcess()->
241                        HasSwitch(::switches::kTestType);
242     Feature::AvailabilityResult result =
243         allowed ? Feature::IS_AVAILABLE : Feature::MISSING_COMMAND_LINE_SWITCH;
244     return Feature::Availability(result,
245                                  allowed ? "" : "Only allowed in tests");
246   }
247   // Hack: Hosted apps should have the availability of messaging APIs based on
248   // the URL of the page (which might have access depending on some extension
249   // with externally_connectable), not whether the app has access to messaging
250   // (which it won't).
251   const Extension* extension = extension_.get();
252   if (extension && extension->is_hosted_app() &&
253       (api_name == "runtime.connect" || api_name == "runtime.sendMessage")) {
254     extension = NULL;
255   }
256   return ExtensionAPI::GetSharedInstance()->IsAvailable(
257       api_name, extension, context_type_, url(), check_alias);
258 }
259 
GetContextTypeDescription() const260 std::string ScriptContext::GetContextTypeDescription() const {
261   DCHECK(thread_checker_.CalledOnValidThread());
262   return GetContextTypeDescriptionString(context_type_);
263 }
264 
GetEffectiveContextTypeDescription() const265 std::string ScriptContext::GetEffectiveContextTypeDescription() const {
266   DCHECK(thread_checker_.CalledOnValidThread());
267   return GetContextTypeDescriptionString(effective_context_type_);
268 }
269 
service_worker_scope() const270 const GURL& ScriptContext::service_worker_scope() const {
271   DCHECK(IsForServiceWorker());
272   return service_worker_scope_;
273 }
274 
IsForServiceWorker() const275 bool ScriptContext::IsForServiceWorker() const {
276   return service_worker_version_id_ !=
277          blink::mojom::kInvalidServiceWorkerVersionId;
278 }
279 
IsAnyFeatureAvailableToContext(const Feature & api,CheckAliasStatus check_alias)280 bool ScriptContext::IsAnyFeatureAvailableToContext(
281     const Feature& api,
282     CheckAliasStatus check_alias) {
283   DCHECK(thread_checker_.CalledOnValidThread());
284   // TODO(lazyboy): Decide what we should do for service workers, where
285   // web_frame() is null.
286   GURL url = web_frame() ? GetDocumentLoaderURLForFrame(web_frame()) : url_;
287   return ExtensionAPI::GetSharedInstance()->IsAnyFeatureAvailableToContext(
288       api, extension(), context_type(), url, check_alias);
289 }
290 
291 // static
GetDocumentLoaderURLForFrame(const blink::WebLocalFrame * frame)292 GURL ScriptContext::GetDocumentLoaderURLForFrame(
293     const blink::WebLocalFrame* frame) {
294   // Normally we would use frame->document().url() to determine the document's
295   // URL, but to decide whether to inject a content script, we use the URL from
296   // the data source. This "quirk" helps prevents content scripts from
297   // inadvertently adding DOM elements to the compose iframe in Gmail because
298   // the compose iframe's dataSource URL is about:blank, but the document URL
299   // changes to match the parent document after Gmail document.writes into
300   // it to create the editor.
301   // http://code.google.com/p/chromium/issues/detail?id=86742
302   blink::WebDocumentLoader* document_loader = CurrentDocumentLoader(frame);
303   return document_loader ? GURL(document_loader->GetUrl()) : GURL();
304 }
305 
306 // static
GetAccessCheckedFrameURL(const blink::WebLocalFrame * frame)307 GURL ScriptContext::GetAccessCheckedFrameURL(
308     const blink::WebLocalFrame* frame) {
309   const blink::WebURL& weburl = frame->GetDocument().Url();
310   if (weburl.IsEmpty()) {
311     blink::WebDocumentLoader* document_loader = CurrentDocumentLoader(frame);
312     if (document_loader &&
313         frame->GetSecurityOrigin().CanAccess(
314             blink::WebSecurityOrigin::Create(document_loader->GetUrl()))) {
315       return GURL(document_loader->GetUrl());
316     }
317   }
318   return GURL(weburl);
319 }
320 
321 // static
GetEffectiveDocumentURL(blink::WebLocalFrame * frame,const GURL & document_url,bool match_about_blank)322 GURL ScriptContext::GetEffectiveDocumentURL(blink::WebLocalFrame* frame,
323                                             const GURL& document_url,
324                                             bool match_about_blank) {
325   // Common scenario. If |match_about_blank| is false (as is the case in most
326   // extensions), or if the frame is not an about:-page, just return
327   // |document_url| (supposedly the URL of the frame).
328   if (!match_about_blank || !document_url.SchemeIs(url::kAboutScheme))
329     return document_url;
330 
331   blink::WebSecurityOrigin web_frame_origin = frame->GetSecurityOrigin();
332   url::Origin frame_origin = web_frame_origin;
333   // Check the origin of the frame, including whether it is an opaque origin
334   // (like about:blank) that has a non-opaque opener.
335   // Unfortunately, we still have to traverse the frame tree, because match
336   // patterns are associated with paths as well, not just origins. For instance,
337   // if an extension wants to run on google.com/maps/* with match_about_blank
338   // true, then it should run on about:blank frames created by google.com/maps,
339   // but not about:blank frames created by google.com (which is what the
340   // precursor tuple origin would be).
341   const url::SchemeHostPort& tuple_or_precursor_tuple_origin =
342       frame_origin.GetTupleOrPrecursorTupleIfOpaque();
343 
344   // There is no valid tuple origin (which can happen in the case of e.g. a
345   // browser-initiated navigation to an opaque URL). Bail.
346   if (!tuple_or_precursor_tuple_origin.IsValid())
347     return document_url;
348 
349   url::Origin precursor_origin =
350       url::Origin::Create(tuple_or_precursor_tuple_origin.GetURL());
351   // The frame can't access its precursor. Bail.
352   if (!web_frame_origin.CanAccess(blink::WebSecurityOrigin(precursor_origin)))
353     return document_url;
354 
355   // Non-sandboxed about:blank and about:srcdoc pages inherit their security
356   // origin from their parent frame/window. So, traverse the frame/window
357   // hierarchy to find the closest non-about:-page and return its URL.
358   blink::WebFrame* parent = frame;
359   blink::WebDocument parent_document;
360   base::flat_set<blink::WebFrame*> already_visited_frames;
361   do {
362     already_visited_frames.insert(parent);
363     if (parent->Parent())
364       parent = parent->Parent();
365     else
366       parent = parent->Opener();
367 
368     // Avoid an infinite loop - see https://crbug.com/568432 and
369     // https://crbug.com/883526.
370     if (base::Contains(already_visited_frames, parent))
371       return document_url;
372 
373     parent_document = parent && parent->IsWebLocalFrame()
374                           ? parent->ToWebLocalFrame()->GetDocument()
375                           : blink::WebDocument();
376 
377     // We reached the end of the ancestral chain without finding a valid parent.
378     // Bail and use the original URL.
379     if (parent_document.IsNull())
380       return document_url;
381 
382     url::SchemeHostPort parent_tuple_origin =
383         url::Origin(parent->GetSecurityOrigin())
384             .GetTupleOrPrecursorTupleIfOpaque();
385     if (!parent_tuple_origin.IsValid() ||
386         parent_tuple_origin != tuple_or_precursor_tuple_origin) {
387       // The parent has a different tuple origin than frame; this could happen
388       // in edge cases where a parent navigates an iframe or popup of a child
389       // frame at a different origin. [1] In this case, bail, since we can't
390       // find a full URL (i.e., one including the path) with the same security
391       // origin to use for the frame in question.
392       // [1] Consider a frame tree like:
393       // <html> <!--example.com-->
394       //   <iframe id="a" src="a.com">
395       //     <iframe id="b" src="b.com"></iframe>
396       //   </iframe>
397       // </html>
398       // Frame "a" is cross-origin from the top-level frame, and so the
399       // example.com top-level frame can't directly access frame "b". However,
400       // it can navigate it through
401       // window.frames[0].frames[0].location.href = 'about:blank';
402       // In that case, the precursor origin tuple origin of frame "b" would be
403       // example.com, but the parent tuple origin is a.com.
404       return document_url;
405     }
406   } while (GURL(parent_document.Url()).SchemeIs(url::kAboutScheme));
407 
408   DCHECK(!parent_document.IsNull());
409 
410   // We should know that the frame can access the parent document, since it
411   // has the same tuple origin as the frame, and we checked the frame access
412   // above.
413   DCHECK(web_frame_origin.CanAccess(parent_document.GetSecurityOrigin()));
414   return parent_document.Url();
415 }
416 
HasAPIPermission(APIPermission::ID permission) const417 bool ScriptContext::HasAPIPermission(APIPermission::ID permission) const {
418   DCHECK(thread_checker_.CalledOnValidThread());
419   if (effective_extension_.get()) {
420     return effective_extension_->permissions_data()->HasAPIPermission(
421         permission);
422   }
423   if (context_type() == Feature::WEB_PAGE_CONTEXT) {
424     // Only web page contexts may be granted content capabilities. Other
425     // contexts are either privileged WebUI or extensions with their own set of
426     // permissions.
427     if (content_capabilities_.find(permission) != content_capabilities_.end())
428       return true;
429   }
430   return false;
431 }
432 
HasAccessOrThrowError(const std::string & name)433 bool ScriptContext::HasAccessOrThrowError(const std::string& name) {
434   DCHECK(thread_checker_.CalledOnValidThread());
435   // Theoretically[1] we could end up with bindings being injected into
436   // sandboxed frames, for example content scripts. Don't let them execute API
437   // functions.
438   //
439   // In any case, this check is silly. The frame's document's security origin
440   // already tells us if it's sandboxed. The only problem is that until
441   // crbug.com/466373 is fixed, we don't know the security origin up-front and
442   // may not know it here, either.
443   //
444   // [1] citation needed. This ScriptContext should already be in a state that
445   // doesn't allow this, from ScriptContextSet::ClassifyJavaScriptContext.
446   if (extension() &&
447       SandboxedPageInfo::IsSandboxedPage(extension(), url_.path())) {
448     static const char kMessage[] =
449         "%s cannot be used within a sandboxed frame.";
450     std::string error_msg = base::StringPrintf(kMessage, name.c_str());
451     isolate()->ThrowException(v8::Exception::Error(
452         v8::String::NewFromUtf8(isolate(), error_msg.c_str(),
453                                 v8::NewStringType::kNormal)
454             .ToLocalChecked()));
455     return false;
456   }
457 
458   Feature::Availability availability = GetAvailability(name);
459   if (!availability.is_available()) {
460     isolate()->ThrowException(v8::Exception::Error(
461         v8::String::NewFromUtf8(isolate(), availability.message().c_str(),
462                                 v8::NewStringType::kNormal)
463             .ToLocalChecked()));
464     return false;
465   }
466 
467   return true;
468 }
469 
GetDebugString() const470 std::string ScriptContext::GetDebugString() const {
471   DCHECK(thread_checker_.CalledOnValidThread());
472   return base::StringPrintf(
473       "  extension id:           %s\n"
474       "  frame:                  %p\n"
475       "  URL:                    %s\n"
476       "  context_type:           %s\n"
477       "  effective extension id: %s\n"
478       "  effective context type: %s",
479       extension_.get() ? extension_->id().c_str() : "(none)", web_frame_,
480       url_.spec().c_str(), GetContextTypeDescription().c_str(),
481       effective_extension_.get() ? effective_extension_->id().c_str()
482                                  : "(none)",
483       GetEffectiveContextTypeDescription().c_str());
484 }
485 
GetStackTraceAsString() const486 std::string ScriptContext::GetStackTraceAsString() const {
487   DCHECK(thread_checker_.CalledOnValidThread());
488   v8::Local<v8::StackTrace> stack_trace =
489       v8::StackTrace::CurrentStackTrace(isolate(), 10);
490   if (stack_trace.IsEmpty() || stack_trace->GetFrameCount() <= 0) {
491     return "    <no stack trace>";
492   }
493   std::string result;
494   for (int i = 0; i < stack_trace->GetFrameCount(); ++i) {
495     v8::Local<v8::StackFrame> frame = stack_trace->GetFrame(isolate(), i);
496     CHECK(!frame.IsEmpty());
497     result += base::StringPrintf(
498         "\n    at %s (%s:%d:%d)",
499         ToStringOrDefault(isolate(), frame->GetFunctionName(), "<anonymous>")
500             .c_str(),
501         ToStringOrDefault(isolate(), frame->GetScriptName(), "<anonymous>")
502             .c_str(),
503         frame->GetLineNumber(), frame->GetColumn());
504   }
505   return result;
506 }
507 
RunScript(v8::Local<v8::String> name,v8::Local<v8::String> code,const RunScriptExceptionHandler & exception_handler,v8::ScriptCompiler::NoCacheReason no_cache_reason)508 v8::Local<v8::Value> ScriptContext::RunScript(
509     v8::Local<v8::String> name,
510     v8::Local<v8::String> code,
511     const RunScriptExceptionHandler& exception_handler,
512     v8::ScriptCompiler::NoCacheReason no_cache_reason) {
513   DCHECK(thread_checker_.CalledOnValidThread());
514   v8::EscapableHandleScope handle_scope(isolate());
515   v8::Context::Scope context_scope(v8_context());
516 
517   // Prepend extensions:: to |name| so that internal code can be differentiated
518   // from external code in stack traces. This has no effect on behaviour.
519   std::string internal_name = base::StringPrintf(
520       "extensions::%s", *v8::String::Utf8Value(isolate(), name));
521 
522   if (internal_name.size() >= v8::String::kMaxLength) {
523     NOTREACHED() << "internal_name is too long.";
524     return v8::Undefined(isolate());
525   }
526 
527   v8::MicrotasksScope microtasks(
528       isolate(), v8::MicrotasksScope::kDoNotRunMicrotasks);
529   v8::TryCatch try_catch(isolate());
530   try_catch.SetCaptureMessage(true);
531   v8::ScriptOrigin origin(
532       v8_helpers::ToV8StringUnsafe(isolate(), internal_name.c_str()));
533   v8::ScriptCompiler::Source script_source(code, origin);
534   v8::Local<v8::Script> script;
535   if (!v8::ScriptCompiler::Compile(v8_context(), &script_source,
536                                    v8::ScriptCompiler::kNoCompileOptions,
537                                    no_cache_reason)
538            .ToLocal(&script)) {
539     exception_handler.Run(try_catch);
540     return v8::Undefined(isolate());
541   }
542 
543   v8::Local<v8::Value> result;
544   if (!script->Run(v8_context()).ToLocal(&result)) {
545     exception_handler.Run(try_catch);
546     return v8::Undefined(isolate());
547   }
548 
549   return handle_scope.Escape(result);
550 }
551 
CallFunction(const v8::Local<v8::Function> & function,int argc,v8::Local<v8::Value> argv[]) const552 v8::Local<v8::Value> ScriptContext::CallFunction(
553     const v8::Local<v8::Function>& function,
554     int argc,
555     v8::Local<v8::Value> argv[]) const {
556   DCHECK(thread_checker_.CalledOnValidThread());
557   v8::EscapableHandleScope handle_scope(isolate());
558   v8::Context::Scope scope(v8_context());
559 
560   v8::MicrotasksScope microtasks(isolate(),
561                                  v8::MicrotasksScope::kDoNotRunMicrotasks);
562   if (!is_valid_) {
563     return handle_scope.Escape(
564         v8::Local<v8::Primitive>(v8::Undefined(isolate())));
565   }
566 
567   v8::Local<v8::Object> global = v8_context()->Global();
568   if (!web_frame_) {
569     v8::MaybeLocal<v8::Value> maybe_result =
570         function->Call(v8_context(), global, argc, argv);
571     v8::Local<v8::Value> result;
572     if (!maybe_result.ToLocal(&result)) {
573       return handle_scope.Escape(
574           v8::Local<v8::Primitive>(v8::Undefined(isolate())));
575     }
576     return handle_scope.Escape(result);
577   }
578 
579   v8::MaybeLocal<v8::Value> result =
580       web_frame_->CallFunctionEvenIfScriptDisabled(function, global, argc,
581                                                    argv);
582 
583   // TODO(devlin): Stop coercing this to a v8::Local.
584   v8::Local<v8::Value> coerced_result;
585   ignore_result(result.ToLocal(&coerced_result));
586   return handle_scope.Escape(coerced_result);
587 }
588 
589 }  // namespace extensions
590