script_context_set.cc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. #include "extensions/renderer/script_context_set.h"
  5. #include "base/bind.h"
  6. #include "base/feature_list.h"
  7. #include "base/location.h"
  8. #include "base/task/single_thread_task_runner.h"
  9. #include "base/threading/thread_task_runner_handle.h"
  10. #include "content/public/common/url_constants.h"
  11. #include "content/public/renderer/render_frame.h"
  12. #include "extensions/common/extension.h"
  13. #include "extensions/common/extension_features.h"
  14. #include "extensions/renderer/extension_frame_helper.h"
  15. #include "extensions/renderer/extensions_renderer_client.h"
  16. #include "extensions/renderer/script_context.h"
  17. #include "extensions/renderer/script_injection.h"
  18. #include "third_party/blink/public/web/blink.h"
  19. #include "third_party/blink/public/web/web_document.h"
  20. #include "third_party/blink/public/web/web_local_frame.h"
  21. #include "v8/include/v8-isolate.h"
  22. #include "v8/include/v8-object.h"
  23. namespace extensions {
  24. namespace {
  25. // There is only ever one instance of the ScriptContextSet.
  26. ScriptContextSet* g_context_set = nullptr;
  27. }
  28. ScriptContextSet::ScriptContextSet(ExtensionIdSet* active_extension_ids)
  29. : active_extension_ids_(active_extension_ids) {
  30. DCHECK(!g_context_set);
  31. g_context_set = this;
  32. }
  33. ScriptContextSet::~ScriptContextSet() {
  34. g_context_set = nullptr;
  35. }
  36. ScriptContext* ScriptContextSet::Register(
  37. blink::WebLocalFrame* frame,
  38. const v8::Local<v8::Context>& v8_context,
  39. int32_t world_id) {
  40. const Extension* extension =
  41. GetExtensionFromFrameAndWorld(frame, world_id, false);
  42. const Extension* effective_extension =
  43. GetExtensionFromFrameAndWorld(frame, world_id, true);
  44. mojom::ViewType view_type = mojom::ViewType::kInvalid;
  45. content::RenderFrame* render_frame =
  46. content::RenderFrame::FromWebFrame(frame);
  47. // In production, we should always have a corresponding render frame.
  48. // Unfortunately, this isn't the case in unit tests, so we can't DCHECK here.
  49. if (render_frame) {
  50. ExtensionFrameHelper* frame_helper =
  51. ExtensionFrameHelper::Get(render_frame);
  52. DCHECK(frame_helper);
  53. view_type = frame_helper->view_type();
  54. // We should only find an offscreen document if the corresponding feature
  55. // is enabled.
  56. DCHECK(base::FeatureList::IsEnabled(
  57. extensions_features::kExtensionsOffscreenDocuments) ||
  58. view_type != mojom::ViewType::kOffscreenDocument);
  59. }
  60. GURL frame_url = ScriptContext::GetDocumentLoaderURLForFrame(frame);
  61. Feature::Context context_type = ClassifyJavaScriptContext(
  62. extension, world_id, frame_url, frame->GetDocument().GetSecurityOrigin(),
  63. view_type);
  64. Feature::Context effective_context_type = ClassifyJavaScriptContext(
  65. effective_extension, world_id,
  66. ScriptContext::GetEffectiveDocumentURLForContext(frame, frame_url, true),
  67. frame->GetDocument().GetSecurityOrigin(), view_type);
  68. ScriptContext* context =
  69. new ScriptContext(v8_context, frame, extension, context_type,
  70. effective_extension, effective_context_type);
  71. contexts_.insert(context); // takes ownership
  72. return context;
  73. }
  74. void ScriptContextSet::Remove(ScriptContext* context) {
  75. if (contexts_.erase(context)) {
  76. context->Invalidate();
  77. base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, context);
  78. }
  79. }
  80. ScriptContext* ScriptContextSet::GetCurrent() const {
  81. v8::Isolate* isolate = v8::Isolate::GetCurrent();
  82. return isolate->InContext() ? GetByV8Context(isolate->GetCurrentContext())
  83. : nullptr;
  84. }
  85. ScriptContext* ScriptContextSet::GetByV8Context(
  86. const v8::Local<v8::Context>& v8_context) const {
  87. for (ScriptContext* script_context : contexts_) {
  88. if (script_context->v8_context() == v8_context)
  89. return script_context;
  90. }
  91. return nullptr;
  92. }
  93. ScriptContext* ScriptContextSet::GetContextByObject(
  94. const v8::Local<v8::Object>& object) {
  95. v8::Local<v8::Context> context;
  96. if (!object->GetCreationContext().ToLocal(&context))
  97. return nullptr;
  98. return GetContextByV8Context(context);
  99. }
  100. ScriptContext* ScriptContextSet::GetContextByV8Context(
  101. const v8::Local<v8::Context>& v8_context) {
  102. // g_context_set can be null in unittests.
  103. return g_context_set ? g_context_set->GetByV8Context(v8_context) : nullptr;
  104. }
  105. ScriptContext* ScriptContextSet::GetMainWorldContextForFrame(
  106. content::RenderFrame* render_frame) {
  107. v8::HandleScope handle_scope(blink::MainThreadIsolate());
  108. return GetContextByV8Context(
  109. render_frame->GetWebFrame()->MainWorldScriptContext());
  110. }
  111. void ScriptContextSet::ForEach(
  112. const std::string& extension_id,
  113. content::RenderFrame* render_frame,
  114. const base::RepeatingCallback<void(ScriptContext*)>& callback) {
  115. // We copy the context list, because calling into javascript may modify it
  116. // out from under us.
  117. std::set<ScriptContext*> contexts_copy = contexts_;
  118. for (ScriptContext* context : contexts_copy) {
  119. // For the same reason as above, contexts may become invalid while we run.
  120. if (!context->is_valid())
  121. continue;
  122. if (!extension_id.empty()) {
  123. const Extension* extension = context->extension();
  124. if (!extension || (extension_id != extension->id()))
  125. continue;
  126. }
  127. content::RenderFrame* context_render_frame = context->GetRenderFrame();
  128. if (render_frame && render_frame != context_render_frame)
  129. continue;
  130. callback.Run(context);
  131. }
  132. }
  133. void ScriptContextSet::OnExtensionUnloaded(const std::string& extension_id) {
  134. ScriptContextSetIterable::ForEach(
  135. extension_id,
  136. base::BindRepeating(&ScriptContextSet::Remove, base::Unretained(this)));
  137. }
  138. void ScriptContextSet::AddForTesting(std::unique_ptr<ScriptContext> context) {
  139. contexts_.insert(context.release()); // Takes ownership
  140. }
  141. const Extension* ScriptContextSet::GetExtensionFromFrameAndWorld(
  142. blink::WebLocalFrame* frame,
  143. int32_t world_id,
  144. bool use_effective_url) {
  145. std::string extension_id;
  146. if (world_id != 0) {
  147. // Isolated worlds (content script).
  148. extension_id = ScriptInjection::GetHostIdForIsolatedWorld(world_id);
  149. } else {
  150. // For looking up the extension associated with this frame, we either want
  151. // to use the current url or possibly the data source url (which this frame
  152. // may be navigating to shortly), depending on the security origin of the
  153. // frame. We don't always want to use the data source url because some
  154. // frames (eg iframes and windows created via window.open) briefly contain
  155. // an about:blank script context that is scriptable by their parent/opener
  156. // before they finish navigating.
  157. GURL frame_url = ScriptContext::GetAccessCheckedFrameURL(frame);
  158. frame_url = ScriptContext::GetEffectiveDocumentURLForContext(
  159. frame, frame_url, use_effective_url);
  160. extension_id =
  161. RendererExtensionRegistry::Get()->GetExtensionOrAppIDByURL(frame_url);
  162. }
  163. // There are conditions where despite a context being associated with an
  164. // extension, no extension actually gets found. Ignore "invalid" because CSP
  165. // blocks extension page loading by switching the extension ID to "invalid".
  166. const Extension* extension =
  167. RendererExtensionRegistry::Get()->GetByID(extension_id);
  168. if (!extension && !extension_id.empty() && extension_id != "invalid") {
  169. // TODO(kalman): Do something here?
  170. }
  171. return extension;
  172. }
  173. Feature::Context ScriptContextSet::ClassifyJavaScriptContext(
  174. const Extension* extension,
  175. int32_t world_id,
  176. const GURL& url,
  177. const blink::WebSecurityOrigin& origin,
  178. mojom::ViewType view_type) {
  179. // WARNING: This logic must match ProcessMap::GetContextType, as much as
  180. // possible.
  181. // Worlds not within this range are not for content scripts, so ignore them.
  182. // TODO(devlin): Isolated worlds with a non-zero id could belong to
  183. // chrome-internal pieces, like dom distiller and translate. Do we need any
  184. // bindings (even those for basic web pages) for those?
  185. if (world_id >= ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId()) {
  186. return extension ? // TODO(kalman): when does this happen?
  187. Feature::CONTENT_SCRIPT_CONTEXT
  188. : Feature::UNSPECIFIED_CONTEXT;
  189. }
  190. // We have an explicit check for sandboxed pages before checking whether the
  191. // extension is active in this process because:
  192. // 1. Sandboxed pages run in the same process as regular extension pages, so
  193. // the extension is considered active.
  194. // 2. ScriptContext creation (which triggers bindings injection) happens
  195. // before the SecurityContext is updated with the sandbox flags (after
  196. // reading the CSP header), so the caller can't check if the context's
  197. // security origin is unique yet.
  198. if (ScriptContext::IsSandboxedPage(url))
  199. return Feature::WEB_PAGE_CONTEXT;
  200. if (extension && active_extension_ids_->count(extension->id()) > 0) {
  201. // |extension| is active in this process, but it could be either a true
  202. // extension process or within the extent of a hosted app. In the latter
  203. // case this would usually be considered a (blessed) web page context,
  204. // unless the extension in question is a component extension, in which case
  205. // we cheat and call it blessed.
  206. if (extension->is_hosted_app() &&
  207. extension->location() != mojom::ManifestLocation::kComponent) {
  208. return Feature::BLESSED_WEB_PAGE_CONTEXT;
  209. }
  210. if (is_lock_screen_context_)
  211. return Feature::LOCK_SCREEN_EXTENSION_CONTEXT;
  212. if (view_type == mojom::ViewType::kOffscreenDocument)
  213. return Feature::OFFSCREEN_EXTENSION_CONTEXT;
  214. return Feature::BLESSED_EXTENSION_CONTEXT;
  215. }
  216. // None of the following feature types should ever be present in an
  217. // offscreen document.
  218. DCHECK_NE(mojom::ViewType::kOffscreenDocument, view_type);
  219. // TODO(kalman): This IsOpaque() check is wrong, it should be performed as
  220. // part of ScriptContext::IsSandboxedPage().
  221. if (!origin.IsOpaque() &&
  222. RendererExtensionRegistry::Get()->ExtensionBindingsAllowed(url)) {
  223. if (!extension) // TODO(kalman): when does this happen?
  224. return Feature::UNSPECIFIED_CONTEXT;
  225. return extension->is_hosted_app() ? Feature::BLESSED_WEB_PAGE_CONTEXT
  226. : Feature::UNBLESSED_EXTENSION_CONTEXT;
  227. }
  228. if (!url.is_valid())
  229. return Feature::UNSPECIFIED_CONTEXT;
  230. if (url.SchemeIs(content::kChromeUIScheme))
  231. return Feature::WEBUI_CONTEXT;
  232. if (url.SchemeIs(content::kChromeUIUntrustedScheme))
  233. return Feature::WEBUI_UNTRUSTED_CONTEXT;
  234. return Feature::WEB_PAGE_CONTEXT;
  235. }
  236. } // namespace extensions