| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "extensions/renderer/script_context_set.h" | 5 #include "extensions/renderer/script_context_set.h" |
| 6 | 6 |
| 7 #include "base/message_loop/message_loop.h" | 7 #include "base/message_loop/message_loop.h" |
| 8 #include "content/public/common/url_constants.h" | |
| 9 #include "content/public/renderer/render_view.h" | 8 #include "content/public/renderer/render_view.h" |
| 10 #include "extensions/common/extension.h" | 9 #include "extensions/common/extension.h" |
| 11 #include "extensions/renderer/extension_groups.h" | |
| 12 #include "extensions/renderer/script_context.h" | 10 #include "extensions/renderer/script_context.h" |
| 13 #include "extensions/renderer/script_injection.h" | |
| 14 #include "third_party/WebKit/public/web/WebDocument.h" | |
| 15 #include "third_party/WebKit/public/web/WebLocalFrame.h" | |
| 16 #include "v8/include/v8.h" | 11 #include "v8/include/v8.h" |
| 17 | 12 |
| 18 namespace extensions { | 13 namespace extensions { |
| 19 | 14 |
| 20 ScriptContextSet::ScriptContextSet(ExtensionSet* extensions, | 15 ScriptContextSet::ScriptContextSet() { |
| 21 ExtensionIdSet* active_extension_ids) | |
| 22 : extensions_(extensions), active_extension_ids_(active_extension_ids) { | |
| 23 } | 16 } |
| 24 | |
| 25 ScriptContextSet::~ScriptContextSet() { | 17 ScriptContextSet::~ScriptContextSet() { |
| 26 } | 18 } |
| 27 | 19 |
| 28 ScriptContext* ScriptContextSet::Register( | 20 int ScriptContextSet::size() const { |
| 29 blink::WebLocalFrame* frame, | 21 return static_cast<int>(contexts_.size()); |
| 30 const v8::Handle<v8::Context>& v8_context, | 22 } |
| 31 int extension_group, | |
| 32 int world_id) { | |
| 33 const Extension* extension = | |
| 34 GetExtensionFromFrameAndWorld(frame, world_id, false); | |
| 35 const Extension* effective_extension = | |
| 36 GetExtensionFromFrameAndWorld(frame, world_id, true); | |
| 37 | 23 |
| 38 GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame); | 24 void ScriptContextSet::Add(ScriptContext* context) { |
| 39 Feature::Context context_type = | 25 #if DCHECK_IS_ON() |
| 40 ClassifyJavaScriptContext(extension, extension_group, frame_url, | 26 // It's OK to insert the same context twice, but we should only ever have |
| 41 frame->document().securityOrigin()); | 27 // one ScriptContext per v8::Context. |
| 42 Feature::Context effective_context_type = ClassifyJavaScriptContext( | 28 for (ContextSet::iterator iter = contexts_.begin(); iter != contexts_.end(); |
| 43 effective_extension, extension_group, | 29 ++iter) { |
| 44 ScriptContext::GetEffectiveDocumentURL(frame, frame_url, true), | 30 ScriptContext* candidate = *iter; |
| 45 frame->document().securityOrigin()); | 31 if (candidate != context) |
| 46 | 32 DCHECK(candidate->v8_context() != context->v8_context()); |
| 47 ScriptContext* context = | 33 } |
| 48 new ScriptContext(v8_context, frame, extension, context_type, | 34 #endif |
| 49 effective_extension, effective_context_type); | 35 contexts_.insert(context); |
| 50 contexts_.insert(context); // takes ownership | |
| 51 return context; | |
| 52 } | 36 } |
| 53 | 37 |
| 54 void ScriptContextSet::Remove(ScriptContext* context) { | 38 void ScriptContextSet::Remove(ScriptContext* context) { |
| 55 if (contexts_.erase(context)) { | 39 if (contexts_.erase(context)) { |
| 56 context->Invalidate(); | 40 context->Invalidate(); |
| 57 base::MessageLoop::current()->DeleteSoon(FROM_HERE, context); | 41 base::MessageLoop::current()->DeleteSoon(FROM_HERE, context); |
| 58 } | 42 } |
| 59 } | 43 } |
| 60 | 44 |
| 45 ScriptContextSet::ContextSet ScriptContextSet::GetAll() const { |
| 46 return contexts_; |
| 47 } |
| 48 |
| 61 ScriptContext* ScriptContextSet::GetCurrent() const { | 49 ScriptContext* ScriptContextSet::GetCurrent() const { |
| 62 v8::Isolate* isolate = v8::Isolate::GetCurrent(); | 50 v8::Isolate* isolate = v8::Isolate::GetCurrent(); |
| 63 return isolate->InContext() ? GetByV8Context(isolate->GetCurrentContext()) | 51 return isolate->InContext() ? GetByV8Context(isolate->GetCurrentContext()) |
| 64 : nullptr; | 52 : NULL; |
| 65 } | 53 } |
| 66 | 54 |
| 67 ScriptContext* ScriptContextSet::GetCalling() const { | 55 ScriptContext* ScriptContextSet::GetCalling() const { |
| 68 v8::Isolate* isolate = v8::Isolate::GetCurrent(); | 56 v8::Isolate* isolate = v8::Isolate::GetCurrent(); |
| 69 v8::Local<v8::Context> calling = isolate->GetCallingContext(); | 57 v8::Local<v8::Context> calling = isolate->GetCallingContext(); |
| 70 return calling.IsEmpty() ? nullptr : GetByV8Context(calling); | 58 return calling.IsEmpty() ? NULL : GetByV8Context(calling); |
| 71 } | 59 } |
| 72 | 60 |
| 73 ScriptContext* ScriptContextSet::GetByV8Context( | 61 ScriptContext* ScriptContextSet::GetByV8Context( |
| 74 const v8::Handle<v8::Context>& v8_context) const { | 62 v8::Handle<v8::Context> v8_context) const { |
| 75 for (ScriptContext* script_context : contexts_) { | 63 for (ContextSet::const_iterator iter = contexts_.begin(); |
| 76 if (script_context->v8_context() == v8_context) | 64 iter != contexts_.end(); |
| 77 return script_context; | 65 ++iter) { |
| 66 if ((*iter)->v8_context() == v8_context) |
| 67 return *iter; |
| 78 } | 68 } |
| 79 return nullptr; | 69 |
| 70 return NULL; |
| 80 } | 71 } |
| 81 | 72 |
| 82 void ScriptContextSet::ForEach( | 73 void ScriptContextSet::ForEach( |
| 83 const std::string& extension_id, | 74 const std::string& extension_id, |
| 84 content::RenderView* render_view, | 75 content::RenderView* render_view, |
| 85 const base::Callback<void(ScriptContext*)>& callback) const { | 76 const base::Callback<void(ScriptContext*)>& callback) const { |
| 86 // We copy the context list, because calling into javascript may modify it | 77 // We copy the context list, because calling into javascript may modify it |
| 87 // out from under us. | 78 // out from under us. |
| 88 std::set<ScriptContext*> contexts_copy = contexts_; | 79 ContextSet contexts = GetAll(); |
| 89 | 80 |
| 90 for (ScriptContext* context : contexts_copy) { | 81 for (ContextSet::iterator it = contexts.begin(); it != contexts.end(); ++it) { |
| 82 ScriptContext* context = *it; |
| 83 |
| 91 // For the same reason as above, contexts may become invalid while we run. | 84 // For the same reason as above, contexts may become invalid while we run. |
| 92 if (!context->is_valid()) | 85 if (!context->is_valid()) |
| 93 continue; | 86 continue; |
| 94 | 87 |
| 95 if (!extension_id.empty()) { | 88 if (!extension_id.empty()) { |
| 96 const Extension* extension = context->extension(); | 89 const Extension* extension = context->extension(); |
| 97 if (!extension || (extension_id != extension->id())) | 90 if (!extension || (extension_id != extension->id())) |
| 98 continue; | 91 continue; |
| 99 } | 92 } |
| 100 | 93 |
| 101 content::RenderView* context_render_view = context->GetRenderView(); | 94 content::RenderView* context_render_view = context->GetRenderView(); |
| 102 if (!context_render_view) | 95 if (!context_render_view) |
| 103 continue; | 96 continue; |
| 104 | 97 |
| 105 if (render_view && render_view != context_render_view) | 98 if (render_view && render_view != context_render_view) |
| 106 continue; | 99 continue; |
| 107 | 100 |
| 108 callback.Run(context); | 101 callback.Run(context); |
| 109 } | 102 } |
| 110 } | 103 } |
| 111 | 104 |
| 112 std::set<ScriptContext*> ScriptContextSet::OnExtensionUnloaded( | 105 ScriptContextSet::ContextSet ScriptContextSet::OnExtensionUnloaded( |
| 113 const std::string& extension_id) { | 106 const std::string& extension_id) { |
| 114 std::set<ScriptContext*> removed; | 107 ContextSet contexts = GetAll(); |
| 115 ForEach(extension_id, | 108 ContextSet removed; |
| 116 base::Bind(&ScriptContextSet::DispatchOnUnloadEventAndRemove, | 109 |
| 117 base::Unretained(this), &removed)); | 110 // Clean up contexts belonging to the unloaded extension. This is done so |
| 111 // that content scripts (which remain injected into the page) don't continue |
| 112 // receiving events and sending messages. |
| 113 for (ContextSet::iterator it = contexts.begin(); it != contexts.end(); ++it) { |
| 114 if ((*it)->extension() && (*it)->extension()->id() == extension_id) { |
| 115 (*it)->DispatchOnUnloadEvent(); |
| 116 removed.insert(*it); |
| 117 Remove(*it); |
| 118 } |
| 119 } |
| 120 |
| 118 return removed; | 121 return removed; |
| 119 } | 122 } |
| 120 | 123 |
| 121 const Extension* ScriptContextSet::GetExtensionFromFrameAndWorld( | |
| 122 const blink::WebLocalFrame* frame, | |
| 123 int world_id, | |
| 124 bool use_effective_url) { | |
| 125 std::string extension_id; | |
| 126 if (world_id != 0) { | |
| 127 // Isolated worlds (content script). | |
| 128 extension_id = ScriptInjection::GetHostIdForIsolatedWorld(world_id); | |
| 129 } else if (!frame->document().securityOrigin().isUnique()) { | |
| 130 // TODO(kalman): Delete the above check. | |
| 131 // Extension pages (chrome-extension:// URLs). | |
| 132 GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame); | |
| 133 frame_url = ScriptContext::GetEffectiveDocumentURL(frame, frame_url, | |
| 134 use_effective_url); | |
| 135 extension_id = extensions_->GetExtensionOrAppIDByURL(frame_url); | |
| 136 } | |
| 137 | |
| 138 // There are conditions where despite a context being associated with an | |
| 139 // extension, no extension actually gets found. Ignore "invalid" because CSP | |
| 140 // blocks extension page loading by switching the extension ID to "invalid". | |
| 141 const Extension* extension = extensions_->GetByID(extension_id); | |
| 142 if (!extension && !extension_id.empty() && extension_id != "invalid") { | |
| 143 // TODO(kalman): Do something here? | |
| 144 } | |
| 145 return extension; | |
| 146 } | |
| 147 | |
| 148 Feature::Context ScriptContextSet::ClassifyJavaScriptContext( | |
| 149 const Extension* extension, | |
| 150 int extension_group, | |
| 151 const GURL& url, | |
| 152 const blink::WebSecurityOrigin& origin) { | |
| 153 // WARNING: This logic must match ProcessMap::GetContextType, as much as | |
| 154 // possible. | |
| 155 | |
| 156 DCHECK_GE(extension_group, 0); | |
| 157 if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS) { | |
| 158 return extension ? // TODO(kalman): when does this happen? | |
| 159 Feature::CONTENT_SCRIPT_CONTEXT | |
| 160 : Feature::UNSPECIFIED_CONTEXT; | |
| 161 } | |
| 162 | |
| 163 // We have an explicit check for sandboxed pages before checking whether the | |
| 164 // extension is active in this process because: | |
| 165 // 1. Sandboxed pages run in the same process as regular extension pages, so | |
| 166 // the extension is considered active. | |
| 167 // 2. ScriptContext creation (which triggers bindings injection) happens | |
| 168 // before the SecurityContext is updated with the sandbox flags (after | |
| 169 // reading the CSP header), so the caller can't check if the context's | |
| 170 // security origin is unique yet. | |
| 171 if (ScriptContext::IsSandboxedPage(*extensions_, url)) | |
| 172 return Feature::WEB_PAGE_CONTEXT; | |
| 173 | |
| 174 if (extension && active_extension_ids_->count(extension->id()) > 0) { | |
| 175 // |extension| is active in this process, but it could be either a true | |
| 176 // extension process or within the extent of a hosted app. In the latter | |
| 177 // case this would usually be considered a (blessed) web page context, | |
| 178 // unless the extension in question is a component extension, in which case | |
| 179 // we cheat and call it blessed. | |
| 180 return (extension->is_hosted_app() && | |
| 181 extension->location() != Manifest::COMPONENT) | |
| 182 ? Feature::BLESSED_WEB_PAGE_CONTEXT | |
| 183 : Feature::BLESSED_EXTENSION_CONTEXT; | |
| 184 } | |
| 185 | |
| 186 // TODO(kalman): This isUnique() check is wrong, it should be performed as | |
| 187 // part of ScriptContext::IsSandboxedPage(). | |
| 188 if (!origin.isUnique() && extensions_->ExtensionBindingsAllowed(url)) { | |
| 189 if (!extension) // TODO(kalman): when does this happen? | |
| 190 return Feature::UNSPECIFIED_CONTEXT; | |
| 191 return extension->is_hosted_app() ? Feature::BLESSED_WEB_PAGE_CONTEXT | |
| 192 : Feature::UNBLESSED_EXTENSION_CONTEXT; | |
| 193 } | |
| 194 | |
| 195 if (!url.is_valid()) | |
| 196 return Feature::UNSPECIFIED_CONTEXT; | |
| 197 | |
| 198 if (url.SchemeIs(content::kChromeUIScheme)) | |
| 199 return Feature::WEBUI_CONTEXT; | |
| 200 | |
| 201 return Feature::WEB_PAGE_CONTEXT; | |
| 202 } | |
| 203 | |
| 204 void ScriptContextSet::DispatchOnUnloadEventAndRemove( | |
| 205 std::set<ScriptContext*>* out, | |
| 206 ScriptContext* context) { | |
| 207 Remove(context); // deleted asynchronously | |
| 208 out->insert(context); | |
| 209 } | |
| 210 | |
| 211 } // namespace extensions | 124 } // namespace extensions |
| OLD | NEW |