diff options
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/renderer/dispatcher.cc | 183 | ||||
-rw-r--r-- | extensions/renderer/dispatcher.h | 26 | ||||
-rw-r--r-- | extensions/renderer/document_custom_bindings.cc | 2 | ||||
-rw-r--r-- | extensions/renderer/request_sender.cc | 4 | ||||
-rw-r--r-- | extensions/renderer/script_context.cc | 25 | ||||
-rw-r--r-- | extensions/renderer/script_context.h | 28 | ||||
-rw-r--r-- | extensions/renderer/script_context_set.cc | 185 | ||||
-rw-r--r-- | extensions/renderer/script_context_set.h | 84 | ||||
-rw-r--r-- | extensions/renderer/script_context_set_unittest.cc | 62 |
9 files changed, 312 insertions, 287 deletions
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc index e7b1267..8f37d82 100644 --- a/extensions/renderer/dispatcher.cc +++ b/extensions/renderer/dispatcher.cc @@ -24,7 +24,6 @@ #include "content/public/renderer/render_view.h" #include "extensions/common/api/messaging/message.h" #include "extensions/common/constants.h" -#include "extensions/common/extension.h" #include "extensions/common/extension_api.h" #include "extensions/common/extension_messages.h" #include "extensions/common/extension_urls.h" @@ -37,7 +36,6 @@ #include "extensions/common/manifest_handlers/content_capabilities_handler.h" #include "extensions/common/manifest_handlers/externally_connectable.h" #include "extensions/common/manifest_handlers/options_page_info.h" -#include "extensions/common/manifest_handlers/sandboxed_page_info.h" #include "extensions/common/message_bundle.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permissions_data.h" @@ -56,7 +54,6 @@ #include "extensions/renderer/document_custom_bindings.h" #include "extensions/renderer/dom_activity_logger.h" #include "extensions/renderer/event_bindings.h" -#include "extensions/renderer/extension_groups.h" #include "extensions/renderer/extension_helper.h" #include "extensions/renderer/extensions_renderer_client.h" #include "extensions/renderer/file_system_natives.h" @@ -205,6 +202,8 @@ Dispatcher::Dispatcher(DispatcherDelegate* delegate) RenderThread::Get()->RegisterExtension(SafeBuiltins::CreateV8Extension()); + script_context_set_.reset( + new ScriptContextSet(&extensions_, &active_extension_ids_)); user_script_set_manager_.reset(new UserScriptSetManager(&extensions_)); script_injection_manager_.reset( new ScriptInjectionManager(&extensions_, user_script_set_manager_.get())); @@ -228,39 +227,6 @@ bool Dispatcher::IsExtensionActive(const std::string& extension_id) const { return is_active; } -const Extension* Dispatcher::GetExtensionFromFrameAndWorld( - const blink::WebFrame* frame, - int world_id, - bool use_effective_url) { - std::string extension_id; - if (world_id != 0) { - // Isolated worlds (content script). - extension_id = ScriptInjection::GetHostIdForIsolatedWorld(world_id); - } else if (!frame->document().securityOrigin().isUnique()) { - // TODO(kalman): Delete the above check. - - // Extension pages (chrome-extension:// URLs). - GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame); - frame_url = ScriptContext::GetEffectiveDocumentURL( - frame, frame_url, use_effective_url); - extension_id = extensions_.GetExtensionOrAppIDByURL(frame_url); - } - - const Extension* extension = extensions_.GetByID(extension_id); - if (!extension && !extension_id.empty()) { - // There are conditions where despite a context being associated with an - // extension, no extension actually gets found. Ignore "invalid" because - // CSP blocks extension page loading by switching the extension ID to - // "invalid". This isn't interesting. - if (extension_id != "invalid") { - LOG(ERROR) << "Extension \"" << extension_id << "\" not found"; - RenderThread::Get()->RecordAction( - UserMetricsAction("ExtensionNotFound_ED")); - } - } - return extension; -} - void Dispatcher::DidCreateScriptContext( blink::WebLocalFrame* frame, const v8::Handle<v8::Context>& v8_context, @@ -268,32 +234,13 @@ void Dispatcher::DidCreateScriptContext( int world_id) { const base::TimeTicks start_time = base::TimeTicks::Now(); - const Extension* extension = - GetExtensionFromFrameAndWorld(frame, world_id, false); - const Extension* effective_extension = - GetExtensionFromFrameAndWorld(frame, world_id, true); - - GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame); - Feature::Context context_type = - ClassifyJavaScriptContext(extension, - extension_group, - frame_url, - frame->document().securityOrigin()); - Feature::Context effective_context_type = ClassifyJavaScriptContext( - effective_extension, - extension_group, - ScriptContext::GetEffectiveDocumentURL(frame, frame_url, true), - frame->document().securityOrigin()); - - ScriptContext* context = - new ScriptContext(v8_context, frame, extension, context_type, - effective_extension, effective_context_type); - script_context_set_.Add(context); + ScriptContext* context = script_context_set_->Register( + frame, v8_context, extension_group, world_id); // Initialize origin permissions for content scripts, which can't be // initialized in |OnActivateExtension|. - if (context_type == Feature::CONTENT_SCRIPT_CONTEXT) - InitOriginPermissions(extension); + if (context->context_type() == Feature::CONTENT_SCRIPT_CONTEXT) + InitOriginPermissions(context->extension()); { scoped_ptr<ModuleSystem> module_system( @@ -328,7 +275,7 @@ void Dispatcher::DidCreateScriptContext( delegate_->RequireAdditionalModules(context, is_within_platform_app); const base::TimeDelta elapsed = base::TimeTicks::Now() - start_time; - switch (context_type) { + switch (context->context_type()) { case Feature::UNSPECIFIED_CONTEXT: UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_Unspecified", elapsed); @@ -356,14 +303,14 @@ void Dispatcher::DidCreateScriptContext( break; } - VLOG(1) << "Num tracked contexts: " << script_context_set_.size(); + VLOG(1) << "Num tracked contexts: " << script_context_set_->size(); } void Dispatcher::WillReleaseScriptContext( blink::WebLocalFrame* frame, const v8::Handle<v8::Context>& v8_context, int world_id) { - ScriptContext* context = script_context_set_.GetByV8Context(v8_context); + ScriptContext* context = script_context_set_->GetByV8Context(v8_context); if (!context) return; @@ -371,8 +318,8 @@ void Dispatcher::WillReleaseScriptContext( // TODO(kalman): add an invalidation observer interface to ScriptContext. request_sender_->InvalidateSource(context); - script_context_set_.Remove(context); - VLOG(1) << "Num tracked contexts: " << script_context_set_.size(); + script_context_set_->Remove(context); + VLOG(1) << "Num tracked contexts: " << script_context_set_->size(); } void Dispatcher::DidCreateDocumentElement(blink::WebFrame* frame) { @@ -443,7 +390,8 @@ bool Dispatcher::CheckContextAccessToExtensionAPI( // Theoretically we could end up with bindings being injected into sandboxed // frames, for example content scripts. Don't let them execute API functions. blink::WebFrame* frame = context->web_frame(); - if (IsSandboxedPage(ScriptContext::GetDataSourceURLForFrame(frame))) { + if (ScriptContext::IsSandboxedPage( + extensions_, ScriptContext::GetDataSourceURLForFrame(frame))) { static const char kMessage[] = "%s cannot be used within a sandboxed frame."; std::string error_msg = base::StringPrintf(kMessage, function_name.c_str()); @@ -470,11 +418,9 @@ void Dispatcher::DispatchEvent(const std::string& extension_id, // Needed for Windows compilation, since kEventBindings is declared extern. const char* local_event_bindings = kEventBindings; - script_context_set_.ForEach(extension_id, - base::Bind(&CallModuleMethod, - local_event_bindings, - kEventDispatchFunction, - &args)); + script_context_set_->ForEach( + extension_id, base::Bind(&CallModuleMethod, local_event_bindings, + kEventDispatchFunction, &args)); } void Dispatcher::InvokeModuleSystemMethod(content::RenderView* render_view, @@ -487,9 +433,8 @@ void Dispatcher::InvokeModuleSystemMethod(content::RenderView* render_view, if (user_gesture) web_user_gesture.reset(new WebScopedUserGesture); - script_context_set_.ForEach( - extension_id, - render_view, + script_context_set_->ForEach( + extension_id, render_view, base::Bind(&CallModuleMethod, module_name, function_name, &args)); // Reset the idle handler each time there's any activity like event or message @@ -894,8 +839,7 @@ void Dispatcher::OnDeliverMessage(int target_port_id, const Message& message) { new RequestSender::ScopedTabID(request_sender(), it->second)); } - MessagingBindings::DeliverMessage(script_context_set_, - target_port_id, + MessagingBindings::DeliverMessage(*script_context_set_, target_port_id, message, NULL); // All render frames. } @@ -912,19 +856,15 @@ void Dispatcher::OnDispatchOnConnect( source.tab.GetInteger("id", &sender_tab_id); port_to_tab_id_map_[target_port_id] = sender_tab_id; - MessagingBindings::DispatchOnConnect(script_context_set_, - target_port_id, - channel_name, - source, - info, + MessagingBindings::DispatchOnConnect(*script_context_set_, target_port_id, + channel_name, source, info, tls_channel_id, NULL); // All render frames. } void Dispatcher::OnDispatchOnDisconnect(int port_id, const std::string& error_message) { - MessagingBindings::DispatchOnDisconnect(script_context_set_, - port_id, + MessagingBindings::DispatchOnDisconnect(*script_context_set_, port_id, error_message, NULL); // All render frames. } @@ -1014,12 +954,10 @@ void Dispatcher::OnUnloaded(const std::string& id) { // Invalidate all of the contexts that were removed. // TODO(kalman): add an invalidation observer interface to ScriptContext. - ScriptContextSet::ContextSet removed_contexts = - script_context_set_.OnExtensionUnloaded(id); - for (ScriptContextSet::ContextSet::iterator it = removed_contexts.begin(); - it != removed_contexts.end(); - ++it) { - request_sender_->InvalidateSource(*it); + std::set<ScriptContext*> removed_contexts = + script_context_set_->OnExtensionUnloaded(id); + for (ScriptContext* context : removed_contexts) { + request_sender_->InvalidateSource(context); } // Update the available bindings for the remaining contexts. These may have @@ -1384,75 +1322,6 @@ bool Dispatcher::IsWithinPlatformApp() { return false; } -// TODO(kalman): This is checking for the wrong thing, it should be checking if -// the frame's security origin is unique. The extension sandbox directive is -// checked for in extensions/common/manifest_handlers/csp_info.cc. -bool Dispatcher::IsSandboxedPage(const GURL& url) const { - if (url.SchemeIs(kExtensionScheme)) { - const Extension* extension = extensions_.GetByID(url.host()); - if (extension) { - return SandboxedPageInfo::IsSandboxedPage(extension, url.path()); - } - } - return false; -} - -Feature::Context Dispatcher::ClassifyJavaScriptContext( - const Extension* extension, - int extension_group, - const GURL& url, - const blink::WebSecurityOrigin& origin) { - // WARNING: This logic must match ProcessMap::GetContextType, as much as - // possible. - - DCHECK_GE(extension_group, 0); - if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS) { - return extension ? // TODO(kalman): when does this happen? - Feature::CONTENT_SCRIPT_CONTEXT - : Feature::UNSPECIFIED_CONTEXT; - } - - // We have an explicit check for sandboxed pages before checking whether the - // extension is active in this process because: - // 1. Sandboxed pages run in the same process as regular extension pages, so - // the extension is considered active. - // 2. ScriptContext creation (which triggers bindings injection) happens - // before the SecurityContext is updated with the sandbox flags (after - // reading the CSP header), so the caller can't check if the context's - // security origin is unique yet. - if (IsSandboxedPage(url)) - return Feature::WEB_PAGE_CONTEXT; - - if (extension && IsExtensionActive(extension->id())) { - // |extension| is active in this process, but it could be either a true - // extension process or within the extent of a hosted app. In the latter - // case this would usually be considered a (blessed) web page context, - // unless the extension in question is a component extension, in which case - // we cheat and call it blessed. - return (extension->is_hosted_app() && - extension->location() != Manifest::COMPONENT) - ? Feature::BLESSED_WEB_PAGE_CONTEXT - : Feature::BLESSED_EXTENSION_CONTEXT; - } - - // TODO(kalman): This isUnique() check is wrong, it should be performed as - // part of IsSandboxedPage(). - if (!origin.isUnique() && extensions_.ExtensionBindingsAllowed(url)) { - if (!extension) // TODO(kalman): when does this happen? - return Feature::UNSPECIFIED_CONTEXT; - return extension->is_hosted_app() ? Feature::BLESSED_WEB_PAGE_CONTEXT - : Feature::UNBLESSED_EXTENSION_CONTEXT; - } - - if (!url.is_valid()) - return Feature::UNSPECIFIED_CONTEXT; - - if (url.SchemeIs(content::kChromeUIScheme)) - return Feature::WEBUI_CONTEXT; - - return Feature::WEB_PAGE_CONTEXT; -} - v8::Handle<v8::Object> Dispatcher::GetOrCreateObject( const v8::Handle<v8::Object>& object, const std::string& field, diff --git a/extensions/renderer/dispatcher.h b/extensions/renderer/dispatcher.h index c5ac365..c71cb64 100644 --- a/extensions/renderer/dispatcher.h +++ b/extensions/renderer/dispatcher.h @@ -11,10 +11,12 @@ #include <utility> #include <vector> +#include "base/memory/scoped_ptr.h" #include "base/scoped_observer.h" #include "base/timer/timer.h" #include "content/public/renderer/render_process_observer.h" #include "extensions/common/event_filter.h" +#include "extensions/common/extension.h" #include "extensions/common/extension_set.h" #include "extensions/common/extensions_client.h" #include "extensions/common/features/feature.h" @@ -53,7 +55,6 @@ class RenderThread; namespace extensions { class ContentWatcher; class DispatcherDelegate; -class Extension; class FilteredEventRouter; class ManifestPermissionSet; class RequestSender; @@ -76,7 +77,7 @@ class Dispatcher : public content::RenderProcessObserver, const ExtensionSet* extensions() const { return &extensions_; } const ScriptContextSet& script_context_set() const { - return script_context_set_; + return *script_context_set_; } V8SchemaRegistry* v8_schema_registry() { return v8_schema_registry_.get(); } @@ -89,14 +90,6 @@ class Dispatcher : public content::RenderProcessObserver, bool IsExtensionActive(const std::string& extension_id) const; - // Finds the extension for the JavaScript context associated with the - // specified |frame| and isolated world. If |world_id| is zero, finds the - // extension ID associated with the main world's JavaScript context. If the - // JavaScript context isn't from an extension, returns empty string. - const Extension* GetExtensionFromFrameAndWorld(const blink::WebFrame* frame, - int world_id, - bool use_effective_url); - void DidCreateScriptContext(blink::WebLocalFrame* frame, const v8::Handle<v8::Context>& context, int extension_group, @@ -242,15 +235,6 @@ class Dispatcher : public content::RenderProcessObserver, // Returns whether the current renderer hosts a platform app. bool IsWithinPlatformApp(); - bool IsSandboxedPage(const GURL& url) const; - - // Returns the Feature::Context type of context for a JavaScript context. - Feature::Context ClassifyJavaScriptContext( - const Extension* extension, - int extension_group, - const GURL& url, - const blink::WebSecurityOrigin& origin); - // Gets |field| from |object| or creates it as an empty object if it doesn't // exist. v8::Handle<v8::Object> GetOrCreateObject(const v8::Handle<v8::Object>& object, @@ -284,7 +268,7 @@ class Dispatcher : public content::RenderProcessObserver, // All the bindings contexts that are currently loaded for this renderer. // There is zero or one for each v8 context. - ScriptContextSet script_context_set_; + scoped_ptr<ScriptContextSet> script_context_set_; scoped_ptr<ContentWatcher> content_watcher_; @@ -300,7 +284,7 @@ class Dispatcher : public content::RenderProcessObserver, std::set<std::string> function_names_; // The extensions and apps that are active in this process. - std::set<std::string> active_extension_ids_; + ExtensionIdSet active_extension_ids_; ResourceBundleSourceMap source_map_; diff --git a/extensions/renderer/document_custom_bindings.cc b/extensions/renderer/document_custom_bindings.cc index bb9d7f7..f9f35d2 100644 --- a/extensions/renderer/document_custom_bindings.cc +++ b/extensions/renderer/document_custom_bindings.cc @@ -9,7 +9,7 @@ #include "base/bind.h" #include "extensions/renderer/script_context.h" #include "third_party/WebKit/public/web/WebDocument.h" -#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" #include "v8/include/v8.h" namespace extensions { diff --git a/extensions/renderer/request_sender.cc b/extensions/renderer/request_sender.cc index 60c77c6..5797953 100644 --- a/extensions/renderer/request_sender.cc +++ b/extensions/renderer/request_sender.cc @@ -10,7 +10,7 @@ #include "extensions/renderer/dispatcher.h" #include "extensions/renderer/script_context.h" #include "third_party/WebKit/public/web/WebDocument.h" -#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebScopedUserGesture.h" #include "third_party/WebKit/public/web/WebUserGestureIndicator.h" #include "third_party/WebKit/public/web/WebUserGestureToken.h" @@ -97,7 +97,7 @@ void RequestSender::StartRequest(Source* source, return; GURL source_url; - if (blink::WebFrame* webframe = context->web_frame()) + if (blink::WebLocalFrame* webframe = context->web_frame()) source_url = webframe->document().url(); InsertRequest(request_id, new PendingRequest(name, source, diff --git a/extensions/renderer/script_context.cc b/extensions/renderer/script_context.cc index e5677aa..84a1ce6 100644 --- a/extensions/renderer/script_context.cc +++ b/extensions/renderer/script_context.cc @@ -13,15 +13,19 @@ #include "content/public/common/url_constants.h" #include "content/public/renderer/render_frame.h" #include "content/public/renderer/render_view.h" +#include "extensions/common/constants.h" #include "extensions/common/extension.h" #include "extensions/common/extension_api.h" +#include "extensions/common/extension_set.h" #include "extensions/common/extension_urls.h" #include "extensions/common/features/base_feature_provider.h" +#include "extensions/common/manifest_handlers/sandboxed_page_info.h" #include "extensions/common/permissions/permissions_data.h" #include "gin/per_context_data.h" #include "third_party/WebKit/public/web/WebDataSource.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" #include "third_party/WebKit/public/web/WebSecurityOrigin.h" #include "third_party/WebKit/public/web/WebView.h" @@ -75,7 +79,7 @@ class ScriptContext::Runner : public gin::Runner { }; ScriptContext::ScriptContext(const v8::Handle<v8::Context>& v8_context, - blink::WebFrame* web_frame, + blink::WebLocalFrame* web_frame, const Extension* extension, Feature::Context context_type, const Extension* effective_extension, @@ -99,7 +103,9 @@ ScriptContext::ScriptContext(const v8::Handle<v8::Context>& v8_context, << (effective_extension_.get() ? effective_extension_->id() : "") << " effective context type: " << GetEffectiveContextTypeDescription(); - gin::PerContextData::From(v8_context)->set_runner(runner_.get()); + gin::PerContextData* gin_data = gin::PerContextData::From(v8_context); + CHECK(gin_data); // may fail if the v8::Context hasn't been registered yet + gin_data->set_runner(runner_.get()); } ScriptContext::~ScriptContext() { @@ -110,6 +116,21 @@ ScriptContext::~ScriptContext() { Invalidate(); } +// static +bool ScriptContext::IsSandboxedPage(const ExtensionSet& extensions, + const GURL& url) { + // TODO(kalman): This is checking for the wrong thing, it should be checking + // if the frame's security origin is unique. The extension sandbox directive + // is checked for in extensions/common/manifest_handlers/csp_info.cc. + if (url.SchemeIs(kExtensionScheme)) { + const Extension* extension = extensions.GetByID(url.host()); + if (extension) { + return SandboxedPageInfo::IsSandboxedPage(extension, url.path()); + } + } + return false; +} + void ScriptContext::Invalidate() { if (!is_valid()) return; diff --git a/extensions/renderer/script_context.h b/extensions/renderer/script_context.h index b6c90f2..745f522 100644 --- a/extensions/renderer/script_context.h +++ b/extensions/renderer/script_context.h @@ -20,6 +20,7 @@ namespace blink { class WebFrame; +class WebLocalFrame; } namespace content { @@ -29,18 +30,27 @@ class RenderView; namespace extensions { class Extension; +class ExtensionSet; // Extensions wrapper for a v8 context. class ScriptContext : public RequestSender::Source { public: ScriptContext(const v8::Handle<v8::Context>& context, - blink::WebFrame* frame, + blink::WebLocalFrame* frame, const Extension* extension, Feature::Context context_type, const Extension* effective_extension, Feature::Context effective_context_type); ~ScriptContext() override; + // Returns whether |url| is sandboxed (as declared in any Extension in + // |extension_set| as sandboxed). + // + // Declared in ScriptContext for lack of a better place, but this should + // become unnecessary at some point as crbug.com/466373 is worked on. + static bool IsSandboxedPage(const ExtensionSet& extension_set, + const GURL& url); + // Clears the WebFrame for this contexts and invalidates the associated // ModuleSystem. void Invalidate(); @@ -59,7 +69,7 @@ class ScriptContext : public RequestSender::Source { return effective_extension_.get(); } - blink::WebFrame* web_frame() const { return web_frame_; } + blink::WebLocalFrame* web_frame() const { return web_frame_; } Feature::Context context_type() const { return context_type_; } @@ -114,6 +124,14 @@ class ScriptContext : public RequestSender::Source { v8::Isolate* isolate() const { return isolate_; } // Get the URL of this context's web frame. + // + // TODO(kalman): Remove this and replace with a GetOrigin() call which reads + // of WebDocument::securityOrigin(): + // - The URL can change (e.g. pushState) but the origin cannot. Luckily it + // appears as though callers don't make security decisions based on the + // result of GetURL() so it's not a problem... yet. + // - Origin is the correct check to be making. + // - It might let us remove the about:blank resolving? GURL GetURL() const; // Returns whether the API |api| or any part of the API could be @@ -157,9 +175,9 @@ class ScriptContext : public RequestSender::Source { private: class Runner; - // The WebFrame associated with this context. This can be NULL because this - // object can outlive is destroyed asynchronously. - blink::WebFrame* web_frame_; + // The WebLocalFrame associated with this context. This can be NULL because + // this object can outlive is destroyed asynchronously. + blink::WebLocalFrame* web_frame_; // The extension associated with this context, or NULL if there is none. This // might be a hosted app in the case that this context is hosting a web URL. diff --git a/extensions/renderer/script_context_set.cc b/extensions/renderer/script_context_set.cc index 5f7be6c1..1e9e9e8 100644 --- a/extensions/renderer/script_context_set.cc +++ b/extensions/renderer/script_context_set.cc @@ -5,34 +5,50 @@ #include "extensions/renderer/script_context_set.h" #include "base/message_loop/message_loop.h" +#include "content/public/common/url_constants.h" #include "content/public/renderer/render_view.h" #include "extensions/common/extension.h" +#include "extensions/renderer/extension_groups.h" #include "extensions/renderer/script_context.h" +#include "extensions/renderer/script_injection.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" #include "v8/include/v8.h" namespace extensions { -ScriptContextSet::ScriptContextSet() { -} -ScriptContextSet::~ScriptContextSet() { +ScriptContextSet::ScriptContextSet(ExtensionSet* extensions, + ExtensionIdSet* active_extension_ids) + : extensions_(extensions), active_extension_ids_(active_extension_ids) { } -int ScriptContextSet::size() const { - return static_cast<int>(contexts_.size()); +ScriptContextSet::~ScriptContextSet() { } -void ScriptContextSet::Add(ScriptContext* context) { -#if DCHECK_IS_ON() - // It's OK to insert the same context twice, but we should only ever have - // one ScriptContext per v8::Context. - for (ContextSet::iterator iter = contexts_.begin(); iter != contexts_.end(); - ++iter) { - ScriptContext* candidate = *iter; - if (candidate != context) - DCHECK(candidate->v8_context() != context->v8_context()); - } -#endif - contexts_.insert(context); +ScriptContext* ScriptContextSet::Register( + blink::WebLocalFrame* frame, + const v8::Handle<v8::Context>& v8_context, + int extension_group, + int world_id) { + const Extension* extension = + GetExtensionFromFrameAndWorld(frame, world_id, false); + const Extension* effective_extension = + GetExtensionFromFrameAndWorld(frame, world_id, true); + + GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame); + Feature::Context context_type = + ClassifyJavaScriptContext(extension, extension_group, frame_url, + frame->document().securityOrigin()); + Feature::Context effective_context_type = ClassifyJavaScriptContext( + effective_extension, extension_group, + ScriptContext::GetEffectiveDocumentURL(frame, frame_url, true), + frame->document().securityOrigin()); + + ScriptContext* context = + new ScriptContext(v8_context, frame, extension, context_type, + effective_extension, effective_context_type); + contexts_.insert(context); // takes ownership + return context; } void ScriptContextSet::Remove(ScriptContext* context) { @@ -42,32 +58,25 @@ void ScriptContextSet::Remove(ScriptContext* context) { } } -ScriptContextSet::ContextSet ScriptContextSet::GetAll() const { - return contexts_; -} - ScriptContext* ScriptContextSet::GetCurrent() const { v8::Isolate* isolate = v8::Isolate::GetCurrent(); return isolate->InContext() ? GetByV8Context(isolate->GetCurrentContext()) - : NULL; + : nullptr; } ScriptContext* ScriptContextSet::GetCalling() const { v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::Local<v8::Context> calling = isolate->GetCallingContext(); - return calling.IsEmpty() ? NULL : GetByV8Context(calling); + return calling.IsEmpty() ? nullptr : GetByV8Context(calling); } ScriptContext* ScriptContextSet::GetByV8Context( - v8::Handle<v8::Context> v8_context) const { - for (ContextSet::const_iterator iter = contexts_.begin(); - iter != contexts_.end(); - ++iter) { - if ((*iter)->v8_context() == v8_context) - return *iter; + const v8::Handle<v8::Context>& v8_context) const { + for (ScriptContext* script_context : contexts_) { + if (script_context->v8_context() == v8_context) + return script_context; } - - return NULL; + return nullptr; } void ScriptContextSet::ForEach( @@ -76,11 +85,9 @@ void ScriptContextSet::ForEach( const base::Callback<void(ScriptContext*)>& callback) const { // We copy the context list, because calling into javascript may modify it // out from under us. - ContextSet contexts = GetAll(); - - for (ContextSet::iterator it = contexts.begin(); it != contexts.end(); ++it) { - ScriptContext* context = *it; + std::set<ScriptContext*> contexts_copy = contexts_; + for (ScriptContext* context : contexts_copy) { // For the same reason as above, contexts may become invalid while we run. if (!context->is_valid()) continue; @@ -102,23 +109,103 @@ void ScriptContextSet::ForEach( } } -ScriptContextSet::ContextSet ScriptContextSet::OnExtensionUnloaded( +std::set<ScriptContext*> ScriptContextSet::OnExtensionUnloaded( const std::string& extension_id) { - ContextSet contexts = GetAll(); - ContextSet removed; - - // Clean up contexts belonging to the unloaded extension. This is done so - // that content scripts (which remain injected into the page) don't continue - // receiving events and sending messages. - for (ContextSet::iterator it = contexts.begin(); it != contexts.end(); ++it) { - if ((*it)->extension() && (*it)->extension()->id() == extension_id) { - (*it)->DispatchOnUnloadEvent(); - removed.insert(*it); - Remove(*it); - } + std::set<ScriptContext*> removed; + ForEach(extension_id, + base::Bind(&ScriptContextSet::DispatchOnUnloadEventAndRemove, + base::Unretained(this), &removed)); + return removed; +} + +const Extension* ScriptContextSet::GetExtensionFromFrameAndWorld( + const blink::WebLocalFrame* frame, + int world_id, + bool use_effective_url) { + std::string extension_id; + if (world_id != 0) { + // Isolated worlds (content script). + extension_id = ScriptInjection::GetHostIdForIsolatedWorld(world_id); + } else if (!frame->document().securityOrigin().isUnique()) { + // TODO(kalman): Delete the above check. + // Extension pages (chrome-extension:// URLs). + GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame); + frame_url = ScriptContext::GetEffectiveDocumentURL(frame, frame_url, + use_effective_url); + extension_id = extensions_->GetExtensionOrAppIDByURL(frame_url); } - return removed; + // There are conditions where despite a context being associated with an + // extension, no extension actually gets found. Ignore "invalid" because CSP + // blocks extension page loading by switching the extension ID to "invalid". + const Extension* extension = extensions_->GetByID(extension_id); + if (!extension && !extension_id.empty() && extension_id != "invalid") { + // TODO(kalman): Do something here? + } + return extension; +} + +Feature::Context ScriptContextSet::ClassifyJavaScriptContext( + const Extension* extension, + int extension_group, + const GURL& url, + const blink::WebSecurityOrigin& origin) { + // WARNING: This logic must match ProcessMap::GetContextType, as much as + // possible. + + DCHECK_GE(extension_group, 0); + if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS) { + return extension ? // TODO(kalman): when does this happen? + Feature::CONTENT_SCRIPT_CONTEXT + : Feature::UNSPECIFIED_CONTEXT; + } + + // We have an explicit check for sandboxed pages before checking whether the + // extension is active in this process because: + // 1. Sandboxed pages run in the same process as regular extension pages, so + // the extension is considered active. + // 2. ScriptContext creation (which triggers bindings injection) happens + // before the SecurityContext is updated with the sandbox flags (after + // reading the CSP header), so the caller can't check if the context's + // security origin is unique yet. + if (ScriptContext::IsSandboxedPage(*extensions_, url)) + return Feature::WEB_PAGE_CONTEXT; + + if (extension && active_extension_ids_->count(extension->id()) > 0) { + // |extension| is active in this process, but it could be either a true + // extension process or within the extent of a hosted app. In the latter + // case this would usually be considered a (blessed) web page context, + // unless the extension in question is a component extension, in which case + // we cheat and call it blessed. + return (extension->is_hosted_app() && + extension->location() != Manifest::COMPONENT) + ? Feature::BLESSED_WEB_PAGE_CONTEXT + : Feature::BLESSED_EXTENSION_CONTEXT; + } + + // TODO(kalman): This isUnique() check is wrong, it should be performed as + // part of ScriptContext::IsSandboxedPage(). + if (!origin.isUnique() && extensions_->ExtensionBindingsAllowed(url)) { + if (!extension) // TODO(kalman): when does this happen? + return Feature::UNSPECIFIED_CONTEXT; + return extension->is_hosted_app() ? Feature::BLESSED_WEB_PAGE_CONTEXT + : Feature::UNBLESSED_EXTENSION_CONTEXT; + } + + if (!url.is_valid()) + return Feature::UNSPECIFIED_CONTEXT; + + if (url.SchemeIs(content::kChromeUIScheme)) + return Feature::WEBUI_CONTEXT; + + return Feature::WEB_PAGE_CONTEXT; +} + +void ScriptContextSet::DispatchOnUnloadEventAndRemove( + std::set<ScriptContext*>* out, + ScriptContext* context) { + Remove(context); // deleted asynchronously + out->insert(context); } } // namespace extensions diff --git a/extensions/renderer/script_context_set.h b/extensions/renderer/script_context_set.h index aa7b253..ac3cb17 100644 --- a/extensions/renderer/script_context_set.h +++ b/extensions/renderer/script_context_set.h @@ -8,8 +8,12 @@ #include <set> #include <string> -#include "base/basictypes.h" -#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "extensions/common/extension.h" +#include "extensions/common/extension_set.h" +#include "extensions/common/features/feature.h" +#include "url/gurl.h" #include "v8/include/v8.h" class GURL; @@ -18,39 +22,50 @@ namespace base { class ListValue; } -namespace content { -class RenderView; +namespace blink { +class WebLocalFrame; +class WebSecurityOrigin; } -namespace v8 { -class Context; +namespace content { +class RenderView; } namespace extensions { class ScriptContext; -// A container of ExtensionBindingsContext. Since calling JavaScript within a -// context can cause any number of contexts to be created or destroyed, this -// has additional smarts to help with the set changing underneath callers. +// A container of ScriptContexts, responsible for both creating and managing +// them. +// +// Since calling JavaScript within a context can cause any number of contexts +// to be created or destroyed, this has additional smarts to help with the set +// changing underneath callers. class ScriptContextSet { public: - ScriptContextSet(); + ScriptContextSet( + ExtensionSet* extensions, + // Set of the IDs of extensions that are active in this process. + // Must outlive this. TODO(kalman): Combine this and |extensions|. + ExtensionIdSet* active_extension_ids); + ~ScriptContextSet(); - int size() const; + // Returns the number of contexts being tracked by this set. + // This may also include invalid contexts. TODO(kalman): Useful? + size_t size() const { return contexts_.size(); } - // Takes ownership of |context|. - void Add(ScriptContext* context); + // Creates and starts managing a new ScriptContext. Ownership is held. + // Returns a weak reference to the new ScriptContext. + ScriptContext* Register(blink::WebLocalFrame* frame, + const v8::Handle<v8::Context>& v8_context, + int extension_group, + int world_id); // If the specified context is contained in this set, remove it, then delete // it asynchronously. After this call returns the context object will still // be valid, but its frame() pointer will be cleared. void Remove(ScriptContext* context); - // Returns a copy to protect against changes. - typedef std::set<ScriptContext*> ContextSet; - ContextSet GetAll() const; - // Gets the ScriptContext corresponding to v8::Context::GetCurrent(), or // NULL if no such context exists. ScriptContext* GetCurrent() const; @@ -61,7 +76,7 @@ class ScriptContextSet { // Gets the ScriptContext corresponding to the specified // v8::Context or NULL if no such context exists. - ScriptContext* GetByV8Context(v8::Handle<v8::Context> context) const; + ScriptContext* GetByV8Context(const v8::Handle<v8::Context>& context) const; // Synchronously runs |callback| with each ScriptContext that belongs to // |extension_id| in |render_view|. @@ -88,10 +103,39 @@ class ScriptContextSet { // Returns the set of ScriptContexts that were removed as a result. These // are safe to interact with until the end of the current event loop, since // they're deleted asynchronously. - ContextSet OnExtensionUnloaded(const std::string& extension_id); + std::set<ScriptContext*> OnExtensionUnloaded(const std::string& extension_id); private: - ContextSet contexts_; + // Finds the extension for the JavaScript context associated with the + // specified |frame| and isolated world. If |world_id| is zero, finds the + // extension ID associated with the main world's JavaScript context. If the + // JavaScript context isn't from an extension, returns empty string. + const Extension* GetExtensionFromFrameAndWorld( + const blink::WebLocalFrame* frame, + int world_id, + bool use_effective_url); + + // Returns the Feature::Context type of context for a JavaScript context. + Feature::Context ClassifyJavaScriptContext( + const Extension* extension, + int extension_group, + const GURL& url, + const blink::WebSecurityOrigin& origin); + + // Calls Remove on |context| then appends |context| to |out|. + // This is a helper designed to be used by OnExtensionUnloaded with ForEach. + void DispatchOnUnloadEventAndRemove(std::set<ScriptContext*>* out, + ScriptContext* context); + + // Weak reference to all installed Extensions. + ExtensionSet* extensions_; + + // Weak reference to all installed Extensions that are also active in this + // process. + ExtensionIdSet* active_extension_ids_; + + // The set of all ScriptContexts we own. + std::set<ScriptContext*> contexts_; DISALLOW_COPY_AND_ASSIGN(ScriptContextSet); }; diff --git a/extensions/renderer/script_context_set_unittest.cc b/extensions/renderer/script_context_set_unittest.cc index e795dd9..d4e56ef 100644 --- a/extensions/renderer/script_context_set_unittest.cc +++ b/extensions/renderer/script_context_set_unittest.cc @@ -2,61 +2,63 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <vector> + #include "base/message_loop/message_loop.h" #include "extensions/common/extension.h" +#include "extensions/common/extension_set.h" #include "extensions/common/features/feature.h" #include "extensions/renderer/script_context.h" #include "extensions/renderer/script_context_set.h" #include "gin/public/context_holder.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebView.h" #include "v8/include/v8.h" namespace extensions { -TEST(ScriptContextSet, Lifecycle) { +TEST(ScriptContextSetTest, Lifecycle) { base::MessageLoop loop; - ScriptContextSet context_set; + blink::WebView* webview = blink::WebView::create(nullptr); + blink::WebLocalFrame* frame = blink::WebLocalFrame::create(nullptr); + webview->setMainFrame(frame); + // Do this after construction of the webview, since it may construct the + // Isolate. v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); + v8::Local<v8::Context> v8_context = v8::Context::New(isolate); + v8::Context::Scope context_scope(v8_context); + // ScriptContext relies on gin, it just doesn't look like it from here. gin::ContextHolder context_holder(isolate); - context_holder.SetContext(v8::Context::New(isolate)); - - blink::WebView* webview = blink::WebView::create(nullptr); - blink::WebFrame* frame = blink::WebLocalFrame::create(nullptr); - webview->setMainFrame(frame); - const Extension* extension = NULL; - ScriptContext* context = - new ScriptContext(context_holder.context(), - frame, - extension, - Feature::BLESSED_EXTENSION_CONTEXT, - extension, - Feature::BLESSED_EXTENSION_CONTEXT); + context_holder.SetContext(v8_context); - context_set.Add(context); - EXPECT_EQ(1u, context_set.GetAll().count(context)); - EXPECT_EQ(context, context_set.GetByV8Context(context->v8_context())); + ExtensionSet extensions; + ExtensionIdSet active_extensions; + ScriptContextSet context_set(&extensions, &active_extensions); + ScriptContext* context = context_set.Register( + frame, v8_context, 0, 0); // no extension group or world ID - // Adding the same item multiple times should be OK and deduped. - context_set.Add(context); - EXPECT_EQ(1u, context_set.GetAll().count(context)); + // Context is valid and resembles correctness. + EXPECT_TRUE(context->is_valid()); + EXPECT_EQ(frame, context->web_frame()); + EXPECT_EQ(v8_context, context->v8_context()); - // GetAll() returns a copy so removing from one should not remove from others. - ScriptContextSet::ContextSet set_copy = context_set.GetAll(); - EXPECT_EQ(1u, set_copy.count(context)); + // Context has been correctly added. + EXPECT_EQ(1u, context_set.size()); + EXPECT_EQ(context, context_set.GetByV8Context(v8_context)); + // Test context is correctly removed. context_set.Remove(context); - EXPECT_EQ(0, context_set.size()); - EXPECT_FALSE(context_set.GetByV8Context(context->v8_context())); - EXPECT_EQ(1u, set_copy.size()); + EXPECT_EQ(0u, context_set.size()); + EXPECT_EQ(nullptr, context_set.GetByV8Context(v8_context)); - // After removal, the context should be marked for destruction. - EXPECT_FALSE(context->web_frame()); + // After removal, the context should be invalid. + EXPECT_FALSE(context->is_valid()); + EXPECT_EQ(nullptr, context->web_frame()); // Run loop to do the actual deletion. loop.RunUntilIdle(); |