diff options
author | kalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-13 00:22:57 +0000 |
---|---|---|
committer | kalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-13 00:22:57 +0000 |
commit | fc5e65d6b7066e8858fe157c23ed834b4dd1fded (patch) | |
tree | 30c8bd3916dd1f0a9720e1385f1e389f287c5f21 | |
parent | 242e4d9c7efcb6e14eef2b5657b012259ea97d63 (diff) | |
download | chromium_src-fc5e65d6b7066e8858fe157c23ed834b4dd1fded.zip chromium_src-fc5e65d6b7066e8858fe157c23ed834b4dd1fded.tar.gz chromium_src-fc5e65d6b7066e8858fe157c23ed834b4dd1fded.tar.bz2 |
Implement the activeTab permission, so that extensions with the permission
that get tab-specific permissions granted (currently, only via clicking a browser
action, but this will expand) can executeScript and captureVisibleTab on that tab
only, until the next page navigation or refresh.
BUG=93903
Review URL: https://chromiumcodereview.appspot.com/10443105
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@141801 0039d316-1c4b-4281-b951-d872f2087c98
32 files changed, 1341 insertions, 84 deletions
diff --git a/chrome/browser/extensions/active_tab_permission_manager.cc b/chrome/browser/extensions/active_tab_permission_manager.cc new file mode 100644 index 0000000..29d9311 --- /dev/null +++ b/chrome/browser/extensions/active_tab_permission_manager.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/active_tab_permission_manager.h" + +#include "chrome/browser/extensions/extension_tab_helper.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/tab_contents/tab_contents.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_messages.h" +#include "chrome/common/extensions/extension_permission_set.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/web_contents.h" + +using content::RenderProcessHost; +using content::WebContentsObserver; + +namespace extensions { + +ActiveTabPermissionManager::ActiveTabPermissionManager( + TabContents* tab_contents) + : WebContentsObserver(tab_contents->web_contents()), + tab_contents_(tab_contents) { + InsertActiveURL(web_contents()->GetURL()); + registrar_.Add(this, + chrome::NOTIFICATION_EXTENSION_UNLOADED, + content::Source<Profile>(tab_contents->profile())); +} + +ActiveTabPermissionManager::~ActiveTabPermissionManager() {} + +void ActiveTabPermissionManager::GrantIfRequested(const Extension* extension) { + if (!extension->HasAPIPermission(ExtensionAPIPermission::kActiveTab)) + return; + + if (active_urls_.is_empty()) + return; + + // Only need to check the number of permissions here rather than the URLs + // themselves, because the set can only ever grow. + const URLPatternSet* old_permissions = + extension->GetTabSpecificHostPermissions(tab_id()); + if (old_permissions && (old_permissions->size() == active_urls_.size())) + return; + + granted_.Insert(extension); + extension->SetTabSpecificHostPermissions(tab_id(), active_urls_); + Send(new ExtensionMsg_UpdateTabSpecificPermissions(GetPageID(), + tab_id(), + extension->id(), + active_urls_)); +} + +void ActiveTabPermissionManager::DidCommitProvisionalLoadForFrame( + int64 frame_id, + bool is_main_frame, + const GURL& url, + content::PageTransition transition_type, + content::RenderViewHost* render_view_host) { + if (is_main_frame) + ClearActiveURLsAndNotify(); + InsertActiveURL(url); +} + +void ActiveTabPermissionManager::WebContentsDestroyed( + content::WebContents* web_contents) { + ClearActiveURLsAndNotify(); +} + +void ActiveTabPermissionManager::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + DCHECK_EQ(type, chrome::NOTIFICATION_EXTENSION_UNLOADED); + const Extension* extension = + content::Details<UnloadedExtensionInfo>(details)->extension; + extension->ClearTabSpecificHostPermissions(tab_id()); + std::vector<std::string> single_id(1, extension->id()); + Send(new ExtensionMsg_ClearTabSpecificPermissions(tab_id(), single_id)); + granted_.Remove(extension->id()); +} + +void ActiveTabPermissionManager::ClearActiveURLsAndNotify() { + active_urls_.ClearPatterns(); + + if (granted_.is_empty()) + return; + + std::vector<std::string> extension_ids; + + for (ExtensionSet::const_iterator it = granted_.begin(); + it != granted_.end(); ++it) { + (*it)->ClearTabSpecificHostPermissions(tab_id()); + extension_ids.push_back((*it)->id()); + } + + Send(new ExtensionMsg_ClearTabSpecificPermissions(tab_id(), extension_ids)); + granted_.Clear(); +} + +void ActiveTabPermissionManager::InsertActiveURL(const GURL& url) { + URLPattern pattern(UserScript::kValidUserScriptSchemes); + if (pattern.Parse(url.spec()) == URLPattern::PARSE_SUCCESS) + active_urls_.AddPattern(pattern); +} + +int32 ActiveTabPermissionManager::tab_id() { + return tab_contents_->extension_tab_helper()->tab_id(); +} + +int32 ActiveTabPermissionManager::GetPageID() { + return tab_contents_->web_contents()->GetController().GetActiveEntry()-> + GetPageID(); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/active_tab_permission_manager.h b/chrome/browser/extensions/active_tab_permission_manager.h new file mode 100644 index 0000000..1dffe55 --- /dev/null +++ b/chrome/browser/extensions/active_tab_permission_manager.h @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_ACTIVE_TAB_PERMISSION_MANAGER_H_ +#define CHROME_BROWSER_EXTENSIONS_ACTIVE_TAB_PERMISSION_MANAGER_H_ +#pragma once + +#include <set> +#include <string> + +#include "chrome/common/extensions/extension_set.h" +#include "chrome/common/extensions/url_pattern_set.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/web_contents_observer.h" + +class TabContents; + +namespace extensions { + +class Extension; + +// Responsible for granting and revoking tab-specific permissions to extensions +// with the activeTab permission. +class ActiveTabPermissionManager : public content::WebContentsObserver, + public content::NotificationObserver { + public: + explicit ActiveTabPermissionManager(TabContents* tab_contents); + virtual ~ActiveTabPermissionManager(); + + // If |extension| has the activeTab permission, grants tab-specific + // permissions to it until the next page navigation or refresh. + void GrantIfRequested(const Extension* extension); + + // content::WebContentsObserver implementation. + // + // This is public to be called from ActiveTabTest as a way to fake creating + // frames on this tab. It's a bit fragile but there doesn't seem to be any + // other way to do it. + virtual void DidCommitProvisionalLoadForFrame( + int64 frame_id, + bool is_main_frame, + const GURL& url, + content::PageTransition transition_type, + content::RenderViewHost* render_view_host) OVERRIDE; + + private: + // More content::WebContentsObserver. + virtual void WebContentsDestroyed(content::WebContents* web_contents) + OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // Clears any granted tab-specific permissions for all extensions and + // notifies renderers. + void ClearActiveURLsAndNotify(); + + // Inserts a URL pattern giving access to the entire origin for |url|. + void InsertActiveURL(const GURL& url); + + // Gets the tab's id. + int tab_id(); + + // Gets the current page id. + int32 GetPageID(); + + // Our owning TabContents. + TabContents* tab_contents_; + + // The URLPatternSet for frames that are active on the current page. This is + // cleared on navigation. + // + // Note that concerned code probably only cares about the origins of these + // URLs, but let them deal with that. + URLPatternSet active_urls_; + + // Extensions with the activeTab permission that have been granted + // tab-specific permissions until the next navigation/refresh. + ExtensionSet granted_; + + // Listen to extension unloaded notifications. + content::NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(ActiveTabPermissionManager); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_ACTIVE_TAB_PERMISSION_MANAGER_H_ diff --git a/chrome/browser/extensions/active_tab_unittest.cc b/chrome/browser/extensions/active_tab_unittest.cc new file mode 100644 index 0000000..881829ce --- /dev/null +++ b/chrome/browser/extensions/active_tab_unittest.cc @@ -0,0 +1,424 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/values.h" +#include "chrome/browser/extensions/active_tab_permission_manager.h" +#include "chrome/browser/extensions/extension_tab_helper.h" +#include "chrome/browser/ui/tab_contents/tab_contents.h" +#include "chrome/browser/ui/tab_contents/test_tab_contents.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/extensions/extension.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/page_transition_types.h" +#include "content/public/test/test_browser_thread.h" + +using base::DictionaryValue; +using base::ListValue; +using content::BrowserThread; +using content::NavigationController; + +namespace extensions { +namespace { + +scoped_refptr<const Extension> CreateTestExtension( + const std::string& name, + bool has_active_tab_permission) { + DictionaryValue manifest; + manifest.SetString("name", name); + manifest.SetString("version", "1.0.0"); + manifest.SetInteger("manifest_version", 2); + + if (has_active_tab_permission) { + scoped_ptr<ListValue> permissions(new ListValue()); + permissions->Append(Value::CreateStringValue("activeTab")); + manifest.Set("permissions", permissions.release()); + } + + std::string error; + scoped_refptr<const Extension> extension = Extension::Create( + FilePath(), + Extension::INTERNAL, + manifest, + 0, // no flags. + name, + &error); + CHECK_EQ("", error); + return extension; +} + +class ActiveTabTest : public TabContentsTestHarness { + public: + ActiveTabTest() + : extension(CreateTestExtension("extension", true)), + another_extension(CreateTestExtension("another", true)), + extension_without_active_tab( + CreateTestExtension("without activeTab", false)), + ui_thread_(BrowserThread::UI, MessageLoop::current()) {} + + protected: + int tab_id() { + return tab_contents()->extension_tab_helper()->tab_id(); + } + + ActiveTabPermissionManager* active_tab_permission_manager() { + return tab_contents()->extension_tab_helper()-> + active_tab_permission_manager(); + } + + bool IsAllowed(const scoped_refptr<const Extension>& extension, + const GURL& url) { + return IsAllowed(extension, url, tab_id()); + } + + bool IsAllowed(const scoped_refptr<const Extension>& extension, + const GURL& url, + int tab_id) { + return (extension->CanExecuteScriptOnPage(url, tab_id, NULL, NULL) && + extension->CanCaptureVisiblePage(url, tab_id, NULL)); + } + + bool IsBlocked(const scoped_refptr<const Extension>& extension, + const GURL& url) { + return IsBlocked(extension, url, tab_id()); + } + + bool IsBlocked(const scoped_refptr<const Extension>& extension, + const GURL& url, + int tab_id) { + return (!extension->CanExecuteScriptOnPage(url, tab_id, NULL, NULL) && + !extension->CanCaptureVisiblePage(url, tab_id, NULL)); + } + + // Fakes loading a new frame on the page using the WebContentsObserver + // interface. + // TODO(kalman): if somebody can tell me a way to do this from the + // TabContentsTestHarness (or any other test harness) then pray tell. + void AddFrame(const GURL& url) { + active_tab_permission_manager()->DidCommitProvisionalLoadForFrame( + 0, // frame_id + false, // is_main_frame + url, + content::PAGE_TRANSITION_AUTO_SUBFRAME, + NULL); // render_view_host + } + + // An extension with the activeTab permission. + scoped_refptr<const Extension> extension; + + // Another extension with activeTab (for good measure). + scoped_refptr<const Extension> another_extension; + + // An extension without the activeTab permission. + scoped_refptr<const Extension> extension_without_active_tab; + + private: + content::TestBrowserThread ui_thread_; +}; + +TEST_F(ActiveTabTest, GrantToSinglePage) { + GURL google("http://www.google.com"); + NavigateAndCommit(google); + + // No access unless it's been granted. + EXPECT_TRUE(IsBlocked(extension, google)); + EXPECT_TRUE(IsBlocked(another_extension, google)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); + + active_tab_permission_manager()->GrantIfRequested(extension); + active_tab_permission_manager()->GrantIfRequested( + extension_without_active_tab); + + // Granted to extension and extension_without_active_tab, but the latter + // doesn't have the activeTab permission so not granted. + EXPECT_TRUE(IsAllowed(extension, google)); + EXPECT_TRUE(IsBlocked(another_extension, google)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); + + // Other subdomains shouldn't be given access. + GURL mail_google("http://mail.google.com"); + EXPECT_TRUE(IsBlocked(extension, mail_google)); + EXPECT_TRUE(IsBlocked(another_extension, google)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); + + // Reloading the page should clear the active permissions. + Reload(); + + EXPECT_TRUE(IsBlocked(extension, google)); + EXPECT_TRUE(IsBlocked(another_extension, google)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); + + // But they should still be able to be granted again. + active_tab_permission_manager()->GrantIfRequested(extension); + + EXPECT_TRUE(IsAllowed(extension, google)); + EXPECT_TRUE(IsBlocked(another_extension, google)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); + + // And grant a few more times redundantly for good measure. + active_tab_permission_manager()->GrantIfRequested(extension); + active_tab_permission_manager()->GrantIfRequested(extension); + active_tab_permission_manager()->GrantIfRequested(another_extension); + active_tab_permission_manager()->GrantIfRequested(another_extension); + active_tab_permission_manager()->GrantIfRequested(another_extension); + active_tab_permission_manager()->GrantIfRequested(extension); + active_tab_permission_manager()->GrantIfRequested(extension); + active_tab_permission_manager()->GrantIfRequested(another_extension); + active_tab_permission_manager()->GrantIfRequested(another_extension); + + EXPECT_TRUE(IsAllowed(extension, google)); + EXPECT_TRUE(IsAllowed(another_extension, google)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); + + // Navigating to a new URL should clear the active permissions. + GURL chromium("http://www.chromium.org"); + NavigateAndCommit(chromium); + + EXPECT_TRUE(IsBlocked(extension, google)); + EXPECT_TRUE(IsBlocked(another_extension, google)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); + + EXPECT_TRUE(IsBlocked(extension, chromium)); + EXPECT_TRUE(IsBlocked(another_extension, chromium)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, chromium)); + + // Should be able to grant to multiple extensions at the same time (if they + // have the activeTab permission, of course). + active_tab_permission_manager()->GrantIfRequested(extension); + active_tab_permission_manager()->GrantIfRequested(another_extension); + active_tab_permission_manager()->GrantIfRequested( + extension_without_active_tab); + + EXPECT_TRUE(IsBlocked(extension, google)); + EXPECT_TRUE(IsBlocked(another_extension, google)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); + + EXPECT_TRUE(IsAllowed(extension, chromium)); + EXPECT_TRUE(IsAllowed(another_extension, chromium)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, chromium)); + + // Should be able to go back to URLs that were previously cleared. + NavigateAndCommit(google); + + active_tab_permission_manager()->GrantIfRequested(extension); + active_tab_permission_manager()->GrantIfRequested(another_extension); + active_tab_permission_manager()->GrantIfRequested( + extension_without_active_tab); + + EXPECT_TRUE(IsAllowed(extension, google)); + EXPECT_TRUE(IsAllowed(another_extension, google)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, google)); + + EXPECT_TRUE(IsBlocked(extension, chromium)); + EXPECT_TRUE(IsBlocked(another_extension, chromium)); + EXPECT_TRUE(IsBlocked(extension_without_active_tab, chromium)); +}; + +TEST_F(ActiveTabTest, GrantToMultiplePages) { + GURL google("http://www.google.com"); + NavigateAndCommit(google); + + active_tab_permission_manager()->GrantIfRequested(extension); + + // Adding a frame after access was granted shouldn't give it access. + GURL chromium("http://www.chromium.org"); + AddFrame(chromium); + + EXPECT_TRUE(IsAllowed(extension, google)); + EXPECT_TRUE(IsBlocked(extension, chromium)); + + // Granting access to another extension should give it access to both the + // main and sub-frames, but still not to the first extension. + active_tab_permission_manager()->GrantIfRequested(another_extension); + + EXPECT_TRUE(IsAllowed(extension, google)); + EXPECT_TRUE(IsBlocked(extension, chromium)); + EXPECT_TRUE(IsAllowed(another_extension, google)); + EXPECT_TRUE(IsAllowed(another_extension, chromium)); + + // Granting access to the first extension should now give it access to the + // frame. + active_tab_permission_manager()->GrantIfRequested(extension); + + EXPECT_TRUE(IsAllowed(extension, google)); + EXPECT_TRUE(IsAllowed(extension, chromium)); + EXPECT_TRUE(IsAllowed(another_extension, google)); + EXPECT_TRUE(IsAllowed(another_extension, chromium)); + + // Reloading should clear all access. + Reload(); + + EXPECT_TRUE(IsBlocked(extension, google)); + EXPECT_TRUE(IsBlocked(extension, chromium)); + EXPECT_TRUE(IsBlocked(another_extension, google)); + EXPECT_TRUE(IsBlocked(another_extension, chromium)); + + // And after granting, no access to the frames that were there. + active_tab_permission_manager()->GrantIfRequested(extension); + + EXPECT_TRUE(IsAllowed(extension, google)); + EXPECT_TRUE(IsBlocked(extension, chromium)); + EXPECT_TRUE(IsBlocked(another_extension, google)); + EXPECT_TRUE(IsBlocked(another_extension, chromium)); + + // Having lots of frames on the same page should behave as expected. + GURL chromium_index("http://www.chromium.org/index.html"); + GURL chromium_about("http://www.chromium.org/about.html"); + GURL chromium_blank("http://www.chromium.org/blank.html"); + GURL gmail("http://www.gmail.com"); + GURL mail_google("http://mail.google.com"); + GURL plus_google("http://plus.google.com"); + GURL codereview_appspot("http://codereview.appspot.com"); + GURL omahaproxy_appspot("http://omahaproxy.appspot.com"); + + AddFrame(chromium_index); + AddFrame(chromium_about); + AddFrame(gmail); + AddFrame(mail_google); + + EXPECT_TRUE(IsBlocked(extension, chromium_index)); + EXPECT_TRUE(IsBlocked(extension, chromium_about)); + EXPECT_TRUE(IsBlocked(extension, chromium_blank)); + EXPECT_TRUE(IsBlocked(extension, gmail)); + EXPECT_TRUE(IsBlocked(extension, mail_google)); + EXPECT_TRUE(IsBlocked(extension, plus_google)); + EXPECT_TRUE(IsBlocked(extension, codereview_appspot)); + EXPECT_TRUE(IsBlocked(extension, omahaproxy_appspot)); + + EXPECT_TRUE(IsBlocked(another_extension, chromium_index)); + EXPECT_TRUE(IsBlocked(another_extension, chromium_about)); + EXPECT_TRUE(IsBlocked(another_extension, chromium_blank)); + EXPECT_TRUE(IsBlocked(another_extension, gmail)); + EXPECT_TRUE(IsBlocked(another_extension, mail_google)); + EXPECT_TRUE(IsBlocked(another_extension, plus_google)); + EXPECT_TRUE(IsBlocked(another_extension, codereview_appspot)); + EXPECT_TRUE(IsBlocked(another_extension, omahaproxy_appspot)); + + active_tab_permission_manager()->GrantIfRequested(extension); + + AddFrame(chromium_blank); + AddFrame(plus_google); + AddFrame(codereview_appspot); + + EXPECT_TRUE(IsAllowed(extension, chromium_index)); + EXPECT_TRUE(IsAllowed(extension, chromium_about)); + // Even though chromium_blank hasn't been given granted, this will work + // because it's on the same origin as the other codereview URLs. + // because k$b + EXPECT_TRUE(IsAllowed(extension, chromium_blank)); + EXPECT_TRUE(IsAllowed(extension, gmail)); + EXPECT_TRUE(IsAllowed(extension, mail_google)); + EXPECT_TRUE(IsBlocked(extension, plus_google)); + EXPECT_TRUE(IsBlocked(extension, codereview_appspot)); + EXPECT_TRUE(IsBlocked(extension, omahaproxy_appspot)); + + EXPECT_TRUE(IsBlocked(another_extension, chromium_index)); + EXPECT_TRUE(IsBlocked(another_extension, chromium_about)); + EXPECT_TRUE(IsBlocked(another_extension, chromium_blank)); + EXPECT_TRUE(IsBlocked(another_extension, gmail)); + EXPECT_TRUE(IsBlocked(another_extension, mail_google)); + EXPECT_TRUE(IsBlocked(another_extension, plus_google)); + EXPECT_TRUE(IsBlocked(another_extension, codereview_appspot)); + EXPECT_TRUE(IsBlocked(another_extension, omahaproxy_appspot)); + + active_tab_permission_manager()->GrantIfRequested(another_extension); + + AddFrame(omahaproxy_appspot); + + EXPECT_TRUE(IsAllowed(extension, chromium_index)); + EXPECT_TRUE(IsAllowed(extension, chromium_about)); + EXPECT_TRUE(IsAllowed(extension, chromium_blank)); + EXPECT_TRUE(IsAllowed(extension, gmail)); + EXPECT_TRUE(IsAllowed(extension, mail_google)); + EXPECT_TRUE(IsBlocked(extension, plus_google)); + EXPECT_TRUE(IsBlocked(extension, codereview_appspot)); + EXPECT_TRUE(IsBlocked(extension, omahaproxy_appspot)); + + EXPECT_TRUE(IsAllowed(another_extension, chromium_index)); + EXPECT_TRUE(IsAllowed(another_extension, chromium_about)); + EXPECT_TRUE(IsAllowed(another_extension, chromium_blank)); + EXPECT_TRUE(IsAllowed(another_extension, gmail)); + EXPECT_TRUE(IsAllowed(another_extension, mail_google)); + EXPECT_TRUE(IsAllowed(another_extension, plus_google)); + EXPECT_TRUE(IsAllowed(another_extension, codereview_appspot)); + EXPECT_TRUE(IsBlocked(another_extension, omahaproxy_appspot)); + + active_tab_permission_manager()->GrantIfRequested(extension); + + EXPECT_TRUE(IsAllowed(extension, chromium_index)); + EXPECT_TRUE(IsAllowed(extension, chromium_about)); + EXPECT_TRUE(IsAllowed(extension, chromium_blank)); + EXPECT_TRUE(IsAllowed(extension, gmail)); + EXPECT_TRUE(IsAllowed(extension, mail_google)); + EXPECT_TRUE(IsAllowed(extension, plus_google)); + EXPECT_TRUE(IsAllowed(extension, codereview_appspot)); + EXPECT_TRUE(IsAllowed(extension, omahaproxy_appspot)); + + EXPECT_TRUE(IsAllowed(another_extension, chromium_index)); + EXPECT_TRUE(IsAllowed(another_extension, chromium_about)); + EXPECT_TRUE(IsAllowed(another_extension, chromium_blank)); + EXPECT_TRUE(IsAllowed(another_extension, gmail)); + EXPECT_TRUE(IsAllowed(another_extension, mail_google)); + EXPECT_TRUE(IsAllowed(another_extension, plus_google)); + EXPECT_TRUE(IsAllowed(another_extension, codereview_appspot)); + EXPECT_TRUE(IsBlocked(another_extension, omahaproxy_appspot)); +} + +TEST_F(ActiveTabTest, Uninstalling) { + // Some semi-arbitrary setup. + GURL google("http://www.google.com"); + NavigateAndCommit(google); + + GURL chromium("http://www.chromium.org"); + AddFrame(chromium); + + active_tab_permission_manager()->GrantIfRequested(extension); + + GURL gmail("http://www.gmail.com"); + AddFrame(gmail); + + EXPECT_TRUE(IsAllowed(extension, google)); + EXPECT_TRUE(IsAllowed(extension, chromium)); + EXPECT_TRUE(IsBlocked(extension, gmail)); + + // Uninstalling the extension should clear its tab permissions. + UnloadedExtensionInfo details( + extension, + extension_misc::UNLOAD_REASON_DISABLE); + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_EXTENSION_UNLOADED, + content::Source<Profile>(tab_contents()->profile()), + content::Details<UnloadedExtensionInfo>(&details)); + + EXPECT_TRUE(IsBlocked(extension, google)); + EXPECT_TRUE(IsBlocked(extension, chromium)); + EXPECT_TRUE(IsBlocked(extension, gmail)); + + // Granting the extension again should give them back. + active_tab_permission_manager()->GrantIfRequested(extension); + + EXPECT_TRUE(IsAllowed(extension, google)); + EXPECT_TRUE(IsAllowed(extension, chromium)); + EXPECT_TRUE(IsAllowed(extension, gmail)); +} + +TEST_F(ActiveTabTest, OnlyActiveTab) { + GURL google("http://www.google.com"); + NavigateAndCommit(google); + + active_tab_permission_manager()->GrantIfRequested(extension); + + EXPECT_TRUE(IsAllowed(extension, google, tab_id())); + EXPECT_TRUE(IsBlocked(extension, google, tab_id() + 1)); +} + +} // namespace +} // namespace extensions diff --git a/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc index a5e8c17..71b9fd9 100644 --- a/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc +++ b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc @@ -801,7 +801,7 @@ bool UpdateOffscreenTabFunction::RunImpl() { tab_contents_ = offscreen_tab->tab_contents(); bool is_async = false; - if (!UpdateURLIfPresent(update_props, &is_async)) + if (!UpdateURLIfPresent(update_props, offscreen_tab_id, &is_async)) return false; // Update the width and height, if specified. diff --git a/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.cc b/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.cc index 9cd9c3f..cdcbd0d 100644 --- a/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.cc +++ b/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.cc @@ -90,7 +90,7 @@ bool ExecuteCodeInTabFunction::RunImpl() { CHECK(browser); CHECK(contents); if (!GetExtension()->CanExecuteScriptOnPage( - contents->web_contents()->GetURL(), NULL, &error_)) { + contents->web_contents()->GetURL(), execute_tab_id_, NULL, &error_)) { return false; } @@ -120,11 +120,8 @@ bool ExecuteCodeInTabFunction::RunImpl() { return false; } - if (!code_string.empty()) { - if (!Execute(code_string)) - return false; - return true; - } + if (!code_string.empty()) + return Execute(code_string); std::string relative_path; if (script_info->HasKey(keys::kFileKey)) { @@ -204,7 +201,8 @@ void ExecuteCodeInTabFunction::LocalizeCSS( void ExecuteCodeInTabFunction::DidLoadAndLocalizeFile(bool success, const std::string& data) { if (success) { - Execute(data); + if (!Execute(data)) + SendResponse(false); } else { #if defined(OS_POSIX) // TODO(viettrungluu): bug: there's no particular reason the path should be @@ -227,16 +225,12 @@ bool ExecuteCodeInTabFunction::Execute(const std::string& code_string) { execute_tab_id_, profile(), include_incognito(), &browser, NULL, &contents, NULL) && contents && browser; - if (!success) { - SendResponse(false); + if (!success) return false; - } const extensions::Extension* extension = GetExtension(); - if (!extension) { - SendResponse(false); + if (!extension) return false; - } ScriptExecutor::ScriptType script_type = ScriptExecutor::JAVASCRIPT; std::string function_name = name(); diff --git a/chrome/browser/extensions/api/tabs/tabs.cc b/chrome/browser/extensions/api/tabs/tabs.cc index 77eb4df..ec6e2de 100644 --- a/chrome/browser/extensions/api/tabs/tabs.cc +++ b/chrome/browser/extensions/api/tabs/tabs.cc @@ -1191,7 +1191,7 @@ bool UpdateTabFunction::RunImpl() { error_ = keys::kNoSelectedTabError; return false; } - tab_id = ExtensionTabUtil::GetTabId(contents->web_contents()); + tab_id = contents->extension_tab_helper()->tab_id(); } else { EXTENSION_FUNCTION_VALIDATE(tab_value->GetAsInteger(&tab_id)); } @@ -1211,7 +1211,7 @@ bool UpdateTabFunction::RunImpl() { // Navigate the tab to a new location if the url is different. bool is_async = false; - if (!UpdateURLIfPresent(update_props, &is_async)) + if (!UpdateURLIfPresent(update_props, tab_id, &is_async)) return false; bool active = false; @@ -1275,6 +1275,7 @@ bool UpdateTabFunction::RunImpl() { } bool UpdateTabFunction::UpdateURLIfPresent(DictionaryValue* update_props, + int tab_id, bool* is_async) { if (!update_props->HasKey(keys::kUrlKey)) return true; @@ -1301,7 +1302,7 @@ bool UpdateTabFunction::UpdateURLIfPresent(DictionaryValue* update_props, // we need to check host permissions before allowing them. if (url.SchemeIs(chrome::kJavaScriptScheme)) { if (!GetExtension()->CanExecuteScriptOnPage( - tab_contents_->web_contents()->GetURL(), NULL, &error_)) { + tab_contents_->web_contents()->GetURL(), tab_id, NULL, &error_)) { return false; } @@ -1614,8 +1615,12 @@ bool CaptureVisibleTabFunction::RunImpl() { // captureVisibleTab() can return an image containing sensitive information // that the browser would otherwise protect. Ensure the extension has // permission to do this. - if (!GetExtension()->CanCaptureVisiblePage(web_contents->GetURL(), &error_)) + if (!GetExtension()->CanCaptureVisiblePage( + web_contents->GetURL(), + tab_contents->extension_tab_helper()->tab_id(), + &error_)) { return false; + } RenderViewHost* render_view_host = web_contents->GetRenderViewHost(); content::RenderWidgetHostView* view = render_view_host->GetView(); diff --git a/chrome/browser/extensions/api/tabs/tabs.h b/chrome/browser/extensions/api/tabs/tabs.h index 0fe56f8..14276a0 100644 --- a/chrome/browser/extensions/api/tabs/tabs.h +++ b/chrome/browser/extensions/api/tabs/tabs.h @@ -121,6 +121,7 @@ class UpdateTabFunction : public AsyncExtensionFunction { protected: virtual ~UpdateTabFunction() {} virtual bool UpdateURLIfPresent(base::DictionaryValue* update_props, + int tab_id, bool* is_async); virtual void PopulateResult(); diff --git a/chrome/browser/extensions/extension_tab_helper.cc b/chrome/browser/extensions/extension_tab_helper.cc index 0a57770..2e4682f 100644 --- a/chrome/browser/extensions/extension_tab_helper.cc +++ b/chrome/browser/extensions/extension_tab_helper.cc @@ -30,11 +30,12 @@ #include "content/public/browser/web_contents.h" #include "ui/gfx/image/image.h" +using content::RenderViewHost; using content::WebContents; using extensions::Extension; +using extensions::PageActionController; using extensions::ScriptBadgeController; using extensions::ScriptExecutorImpl; -using extensions::PageActionController; namespace { @@ -48,7 +49,8 @@ ExtensionTabHelper::ExtensionTabHelper(TabContents* tab_contents) extension_app_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST( extension_function_dispatcher_(tab_contents->profile(), this)), - tab_contents_(tab_contents) { + tab_contents_(tab_contents), + active_tab_permission_manager_(tab_contents) { if (extensions::switch_utils::IsActionBoxEnabled()) { script_badge_controller_ = new ScriptBadgeController(tab_contents); } else { @@ -75,6 +77,14 @@ void ExtensionTabHelper::GetApplicationInfo(int32 page_id) { Send(new ExtensionMsg_GetApplicationInfo(routing_id(), page_id)); } +int ExtensionTabHelper::tab_id() const { + return tab_contents_->restore_tab_helper()->session_id().id(); +} + +int ExtensionTabHelper::window_id() const { + return tab_contents_->restore_tab_helper()->window_id().id(); +} + void ExtensionTabHelper::SetExtensionApp(const Extension* extension) { DCHECK(!extension || extension->GetFullLaunchURL().is_valid()); extension_app_ = extension; @@ -121,6 +131,11 @@ extensions::LocationBarController* return location_bar_controller_.get(); } +void ExtensionTabHelper::RenderViewCreated(RenderViewHost* render_view_host) { + render_view_host->Send( + new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(), tab_id())); +} + void ExtensionTabHelper::DidNavigateMainFrame( const content::LoadCommittedDetails& details, const content::FrameNavigateParams& params) { diff --git a/chrome/browser/extensions/extension_tab_helper.h b/chrome/browser/extensions/extension_tab_helper.h index 6aa2bca..ffcca1c 100644 --- a/chrome/browser/extensions/extension_tab_helper.h +++ b/chrome/browser/extensions/extension_tab_helper.h @@ -9,6 +9,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" +#include "chrome/browser/extensions/active_tab_permission_manager.h" #include "chrome/browser/extensions/app_notify_channel_setup.h" #include "chrome/browser/extensions/extension_function_dispatcher.h" #include "chrome/browser/extensions/image_loading_tracker.h" @@ -58,6 +59,12 @@ class ExtensionTabHelper // the data is available. void GetApplicationInfo(int32 page_id); + // Gets the ID of the tab. + int tab_id() const; + + // Gets the window ID of the tab. + int window_id() const; + // App extensions ------------------------------------------------------------ // Sets the extension denoting this as an app. If |extension| is non-null this @@ -102,12 +109,18 @@ class ExtensionTabHelper extensions::LocationBarController* location_bar_controller(); + extensions::ActiveTabPermissionManager* active_tab_permission_manager() { + return &active_tab_permission_manager_; + } + // Sets a non-extension app icon associated with WebContents and fires an // INVALIDATE_TYPE_TITLE navigation state change to trigger repaint of title. void SetAppIcon(const SkBitmap& app_icon); private: // content::WebContentsObserver overrides. + virtual void RenderViewCreated( + content::RenderViewHost* render_view_host) OVERRIDE; virtual void DidNavigateMainFrame( const content::LoadCommittedDetails& details, const content::FrameNavigateParams& params) OVERRIDE; @@ -196,6 +209,8 @@ class ExtensionTabHelper scoped_ptr<extensions::LocationBarController> location_bar_controller_; scoped_refptr<extensions::ScriptBadgeController> script_badge_controller_; + extensions::ActiveTabPermissionManager active_tab_permission_manager_; + DISALLOW_COPY_AND_ASSIGN(ExtensionTabHelper); }; diff --git a/chrome/browser/extensions/extension_tab_util.cc b/chrome/browser/extensions/extension_tab_util.cc index d2cd5b3..e8251a0 100644 --- a/chrome/browser/extensions/extension_tab_util.cc +++ b/chrome/browser/extensions/extension_tab_util.cc @@ -5,6 +5,7 @@ #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/api/tabs/tabs_constants.h" +#include "chrome/browser/extensions/extension_tab_helper.h" #include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sessions/restore_tab_helper.h" @@ -41,20 +42,18 @@ int ExtensionTabUtil::GetWindowIdOfTabStripModel( return -1; } -// TODO(sky): this function should really take a TabContents. int ExtensionTabUtil::GetTabId(const WebContents* web_contents) { const TabContents* tab = TabContents::FromWebContents(web_contents); - return tab ? tab->restore_tab_helper()->session_id().id() : -1; + return tab ? tab->extension_tab_helper()->tab_id() : -1; } std::string ExtensionTabUtil::GetTabStatusText(bool is_loading) { return is_loading ? keys::kStatusValueLoading : keys::kStatusValueComplete; } -// TODO(sky): this function should really take a TabContents. int ExtensionTabUtil::GetWindowIdOfTab(const WebContents* web_contents) { const TabContents* tab = TabContents::FromWebContents(web_contents); - return tab ? tab->restore_tab_helper()->window_id().id() : -1; + return tab ? tab->extension_tab_helper()->window_id() : -1; } DictionaryValue* ExtensionTabUtil::CreateTabValue(const WebContents* contents) { diff --git a/chrome/browser/extensions/extension_toolbar_model.cc b/chrome/browser/extensions/extension_toolbar_model.cc index d8e0ed0..cc6e6474 100644 --- a/chrome/browser/extensions/extension_toolbar_model.cc +++ b/chrome/browser/extensions/extension_toolbar_model.cc @@ -7,6 +7,7 @@ #include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_tab_helper.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" @@ -95,6 +96,9 @@ ExtensionToolbarModel::Action ExtensionToolbarModel::ExecuteBrowserAction( if (tab_id < 0) return ACTION_NONE; + tab_contents->extension_tab_helper()->active_tab_permission_manager()-> + GrantIfRequested(extension); + ExtensionAction* browser_action = extension->browser_action(); if (browser_action->HasPopup(tab_id)) { if (popup_url_out) diff --git a/chrome/browser/extensions/script_badge_controller.cc b/chrome/browser/extensions/script_badge_controller.cc index eb3c28d..7efd6a9 100644 --- a/chrome/browser/extensions/script_badge_controller.cc +++ b/chrome/browser/extensions/script_badge_controller.cc @@ -89,7 +89,6 @@ void ScriptBadgeController::ExecuteScript( run_at, world_type, this_callback); - } void ScriptBadgeController::OnExecuteScriptFinished( diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index a8f0c0e..9601a2d 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -53,6 +53,8 @@ 'sources': [ # All .cc, .h, .m, and .mm files under browser/extensions except for # tests and mocks. + 'browser/extensions/active_tab_permission_manager.cc', + 'browser/extensions/active_tab_permission_manager.h', 'browser/extensions/admin_policy.cc', 'browser/extensions/admin_policy.h', 'browser/extensions/api/api_function.cc', diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi index 58a9b7b..9273650 100644 --- a/chrome/chrome_renderer.gypi +++ b/chrome/chrome_renderer.gypi @@ -111,6 +111,8 @@ 'renderer/extensions/send_request_natives.h', 'renderer/extensions/set_icon_natives.cc', 'renderer/extensions/set_icon_natives.h', + 'renderer/extensions/tab_finder.cc', + 'renderer/extensions/tab_finder.h', 'renderer/extensions/tabs_custom_bindings.cc', 'renderer/extensions/tabs_custom_bindings.h', 'renderer/extensions/tts_custom_bindings.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 9191e1c..b4eaca7 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1181,6 +1181,7 @@ 'browser/enumerate_modules_model_unittest_win.cc', 'browser/event_disposition.cc', 'browser/event_disposition.h', + 'browser/extensions/active_tab_unittest.cc', 'browser/extensions/admin_policy_unittest.cc', 'browser/extensions/api/alarms/alarms_api_unittest.cc', 'browser/extensions/api/content_settings/content_settings_store_unittest.cc', diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index 63fffc7..a01096b 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -3409,6 +3409,7 @@ bool Extension::HasMultipleUISurfaces() const { } bool Extension::CanExecuteScriptOnPage(const GURL& page_url, + int tab_id, const UserScript* script, std::string* error) const { base::AutoLock auto_lock(runtime_data_lock_); @@ -3431,6 +3432,16 @@ bool Extension::CanExecuteScriptOnPage(const GURL& page_url, !CanExecuteScriptEverywhere()) return false; + // If a tab ID is specified, try the tab-specific permissions. + if (tab_id >= 0) { + const URLPatternSet* tab_permissions = + runtime_data_.GetTabSpecificHostPermissions(tab_id); + if (tab_permissions && + tab_permissions->MatchesSecurityOrigin(page_url)) { + return true; + } + } + // If a script is specified, use its matches. if (script) return script->MatchesURL(page_url); @@ -3493,7 +3504,17 @@ bool Extension::CanExecuteScriptEverywhere() const { } bool Extension::CanCaptureVisiblePage(const GURL& page_url, + int tab_id, std::string *error) const { + if (tab_id >= 0) { + const URLPatternSet* tab_permissions = + GetTabSpecificHostPermissions(tab_id); + if (tab_permissions && + tab_permissions->MatchesSecurityOrigin(page_url)) { + return true; + } + } + if (HasHostPermission(page_url) || page_url.GetOrigin() == url()) return true; @@ -3669,6 +3690,24 @@ ExtensionAction* Extension::GetScriptBadge() const { return script_badge_.get(); } +const URLPatternSet* Extension::GetTabSpecificHostPermissions( + int tab_id) const { + base::AutoLock auto_lock(runtime_data_lock_); + return runtime_data_.GetTabSpecificHostPermissions(tab_id); +} + +void Extension::SetTabSpecificHostPermissions( + int tab_id, + const URLPatternSet& permissions) const { + base::AutoLock auto_lock(runtime_data_lock_); + runtime_data_.SetTabSpecificHostPermissions(tab_id, permissions); +} + +void Extension::ClearTabSpecificHostPermissions(int tab_id) const { + base::AutoLock auto_lock(runtime_data_lock_); + runtime_data_.ClearTabSpecificHostPermissions(tab_id); +} + bool Extension::CheckPlatformAppFeatures(std::string* utf8_error) { if (!is_platform_app()) return true; @@ -3714,6 +3753,27 @@ void Extension::RuntimeData::SetActivePermissions( active_permissions_ = active; } +const URLPatternSet* + Extension::RuntimeData::GetTabSpecificHostPermissions(int tab_id) const { + CHECK_GE(tab_id, 0); + TabHostPermissionsMap::const_iterator it = + tab_specific_host_permissions_.find(tab_id); + return (it != tab_specific_host_permissions_.end()) ? it->second.get() : NULL; +} + +void Extension::RuntimeData::SetTabSpecificHostPermissions( + int tab_id, + const URLPatternSet& hosts) { + CHECK_GE(tab_id, 0); + tab_specific_host_permissions_[tab_id] = + make_linked_ptr(new URLPatternSet(hosts)); +} + +void Extension::RuntimeData::ClearTabSpecificHostPermissions(int tab_id) { + CHECK_GE(tab_id, 0); + tab_specific_host_permissions_.erase(tab_id); +} + UnloadedExtensionInfo::UnloadedExtensionInfo( const Extension* extension, extension_misc::UnloadedExtensionReason reason) diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index 6fac262..0239b80 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -494,6 +494,7 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // This method is also aware of certain special pages that extensions are // usually not allowed to run script on. bool CanExecuteScriptOnPage(const GURL& page_url, + int tab_id, const UserScript* script, std::string* error) const; @@ -505,7 +506,9 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // page as an image. Since a page may contain sensitive information, this // is restricted to the extension's host permissions as well as the // extension page itself. - bool CanCaptureVisiblePage(const GURL& page_url, std::string* error) const; + bool CanCaptureVisiblePage(const GURL& page_url, + int tab_id, + std::string* error) const; // Returns true if this extension updates itself using the extension // gallery. @@ -534,6 +537,20 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // shown for this extension in the location bar. ExtensionAction* GetScriptBadge() const; + // Gets the tab-specific host permissions of |tab_id|, or NULL if there + // aren't any. + // + // This is a weak pointer. Callers should create a copy before mutating any + // tab specific permissions. + const URLPatternSet* GetTabSpecificHostPermissions(int tab_id) const; + + // Sets the tab-specific host permissions of |tab_id| to |permissions|. + void SetTabSpecificHostPermissions(int tab_id, + const URLPatternSet& permissions) const; + + // Clears the tab-specific host permissions of |tab_id|. + void ClearTabSpecificHostPermissions(int tab_id) const; + // Accessors: const FilePath& path() const { return path_; } @@ -685,9 +702,19 @@ class Extension : public base::RefCountedThreadSafe<Extension> { void SetActivePermissions(const ExtensionPermissionSet* active); scoped_refptr<const ExtensionPermissionSet> GetActivePermissions() const; + const URLPatternSet* GetTabSpecificHostPermissions(int tab_id) const; + void SetTabSpecificHostPermissions(int tab_id, + const URLPatternSet& permissions); + void ClearTabSpecificHostPermissions(int tab_id); + private: friend class base::RefCountedThreadSafe<RuntimeData>; + scoped_refptr<const ExtensionPermissionSet> active_permissions_; + + typedef std::map<int, linked_ptr<const URLPatternSet> > + TabHostPermissionsMap; + TabHostPermissionsMap tab_specific_host_permissions_; }; // Chooses the extension ID for an extension based on a variety of criteria. diff --git a/chrome/common/extensions/extension_messages.cc b/chrome/common/extensions/extension_messages.cc index db167d1..82930970 100644 --- a/chrome/common/extensions/extension_messages.cc +++ b/chrome/common/extensions/extension_messages.cc @@ -43,6 +43,7 @@ scoped_refptr<Extension> extension->SetActivePermissions( new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts)); + return extension; } diff --git a/chrome/common/extensions/extension_messages.h b/chrome/common/extensions/extension_messages.h index 42aab16..a79c9de 100644 --- a/chrome/common/extensions/extension_messages.h +++ b/chrome/common/extensions/extension_messages.h @@ -234,10 +234,14 @@ IPC_MESSAGE_CONTROL1(ExtensionMsg_UpdateUserScripts, IPC_MESSAGE_ROUTED1(ExtensionMsg_GetApplicationInfo, int32 /*page_id*/) -// Tell the renderer which browser window it's being attached to. +// Tell the render view which browser window it's being attached to. IPC_MESSAGE_ROUTED1(ExtensionMsg_UpdateBrowserWindowId, int /* id of browser window */) +// Tell the render view what its tab ID is. +IPC_MESSAGE_ROUTED1(ExtensionMsg_SetTabId, + int /* id of tab */) + // Tell the renderer to update an extension's permission set. IPC_MESSAGE_CONTROL5(ExtensionMsg_UpdatePermissions, int /* UpdateExtensionPermissionsInfo::REASON */, @@ -246,6 +250,18 @@ IPC_MESSAGE_CONTROL5(ExtensionMsg_UpdatePermissions, URLPatternSet /* explicit_hosts */, URLPatternSet /* scriptable_hosts */) +// Tell the renderer about new tab-specific permissions for an extension. +IPC_MESSAGE_CONTROL4(ExtensionMsg_UpdateTabSpecificPermissions, + int32 /* page_id (only relevant for the target tab) */, + int /* tab_id */, + std::string /* extension_id */, + URLPatternSet /* host */) + +// Tell the renderer to clear tab-specific permissions for some extensions. +IPC_MESSAGE_CONTROL2(ExtensionMsg_ClearTabSpecificPermissions, + int /* tab_id */, + std::vector<std::string> /* extension_ids */) + // Tell the renderer which type this view is. IPC_MESSAGE_ROUTED1(ExtensionMsg_NotifyRenderViewType, chrome::ViewType /* view_type */) diff --git a/chrome/common/extensions/extension_unittest.cc b/chrome/common/extensions/extension_unittest.cc index 4c5f57a..eee2da6 100644 --- a/chrome/common/extensions/extension_unittest.cc +++ b/chrome/common/extensions/extension_unittest.cc @@ -615,62 +615,62 @@ TEST(ExtensionTest, WantsFileAccess) { // <all_urls> permission extension = LoadManifest("permissions", "permissions_all_urls.json"); EXPECT_TRUE(extension->wants_file_access()); - EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); + EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, -1, NULL, NULL)); extension = LoadManifest( "permissions", "permissions_all_urls.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); - EXPECT_TRUE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); + EXPECT_TRUE(extension->CanExecuteScriptOnPage(file_url, -1, NULL, NULL)); // file:///* permission extension = LoadManifest("permissions", "permissions_file_scheme.json"); EXPECT_TRUE(extension->wants_file_access()); - EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); + EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, -1, NULL, NULL)); extension = LoadManifest("permissions", "permissions_file_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); - EXPECT_TRUE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); + EXPECT_TRUE(extension->CanExecuteScriptOnPage(file_url, -1, NULL, NULL)); // http://* permission extension = LoadManifest("permissions", "permissions_http_scheme.json"); EXPECT_FALSE(extension->wants_file_access()); - EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); + EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, -1, NULL, NULL)); extension = LoadManifest("permissions", "permissions_http_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_FALSE(extension->wants_file_access()); - EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL)); + EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, -1, NULL, NULL)); // <all_urls> content script match extension = LoadManifest("permissions", "content_script_all_urls.json"); EXPECT_TRUE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage( - file_url, &extension->content_scripts()[0], NULL)); + file_url, -1, &extension->content_scripts()[0], NULL)); extension = LoadManifest("permissions", "content_script_all_urls.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); EXPECT_TRUE(extension->CanExecuteScriptOnPage( - file_url, &extension->content_scripts()[0], NULL)); + file_url, -1, &extension->content_scripts()[0], NULL)); // file:///* content script match extension = LoadManifest("permissions", "content_script_file_scheme.json"); EXPECT_TRUE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage( - file_url, &extension->content_scripts()[0], NULL)); + file_url, -1, &extension->content_scripts()[0], NULL)); extension = LoadManifest("permissions", "content_script_file_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_TRUE(extension->wants_file_access()); EXPECT_TRUE(extension->CanExecuteScriptOnPage( - file_url, &extension->content_scripts()[0], NULL)); + file_url, -1, &extension->content_scripts()[0], NULL)); // http://* content script match extension = LoadManifest("permissions", "content_script_http_scheme.json"); EXPECT_FALSE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage( - file_url, &extension->content_scripts()[0], NULL)); + file_url, -1, &extension->content_scripts()[0], NULL)); extension = LoadManifest("permissions", "content_script_http_scheme.json", Extension::ALLOW_FILE_ACCESS); EXPECT_FALSE(extension->wants_file_access()); EXPECT_FALSE(extension->CanExecuteScriptOnPage( - file_url, &extension->content_scripts()[0], NULL)); + file_url, -1, &extension->content_scripts()[0], NULL)); } TEST(ExtensionTest, ExtraFlags) { @@ -689,49 +689,92 @@ TEST(ExtensionTest, ExtraFlags) { // Base class for testing the CanExecuteScriptOnPage and CanCaptureVisiblePage // methods of Extension for extensions with various permissions. class ExtensionScriptAndCaptureVisibleTest : public testing::Test { - public: - ExtensionScriptAndCaptureVisibleTest() { - PathService::Get(chrome::DIR_TEST_DATA, &dirpath_); + protected: + ExtensionScriptAndCaptureVisibleTest() + : http_url("http://www.google.com"), + http_url_with_path("http://www.google.com/index.html"), + https_url("https://www.google.com"), + file_url("file:///foo/bar"), + favicon_url("chrome://favicon/http://www.google.com"), + extension_url("chrome-extension://" + + Extension::GenerateIdForPath(FilePath(FILE_PATH_LITERAL("foo")))), + settings_url("chrome://settings"), + about_url("about:flags") { + urls_.insert(http_url); + urls_.insert(http_url_with_path); + urls_.insert(https_url); + urls_.insert(file_url); + urls_.insert(favicon_url); + urls_.insert(extension_url); + urls_.insert(settings_url); + urls_.insert(about_url); } bool Allowed(const Extension* extension, const GURL& url) { - return (extension->CanExecuteScriptOnPage(url, NULL, NULL) && - extension->CanCaptureVisiblePage(url, NULL)); + return Allowed(extension, url, -1); + } + + bool Allowed(const Extension* extension, const GURL& url, int tab_id) { + return (extension->CanExecuteScriptOnPage(url, tab_id, NULL, NULL) && + extension->CanCaptureVisiblePage(url, tab_id, NULL)); } bool CaptureOnly(const Extension* extension, const GURL& url) { - return !extension->CanExecuteScriptOnPage(url, NULL, NULL) && - extension->CanCaptureVisiblePage(url, NULL); + return CaptureOnly(extension, url, -1); + } + + bool CaptureOnly(const Extension* extension, const GURL& url, int tab_id) { + return !extension->CanExecuteScriptOnPage(url, tab_id, NULL, NULL) && + extension->CanCaptureVisiblePage(url, tab_id, NULL); } bool Blocked(const Extension* extension, const GURL& url) { - return !(extension->CanExecuteScriptOnPage(url, NULL, NULL) || - extension->CanCaptureVisiblePage(url, NULL)); + return Blocked(extension, url, -1); } - protected: - FilePath dirpath_; -}; + bool Blocked(const Extension* extension, const GURL& url, int tab_id) { + return !(extension->CanExecuteScriptOnPage(url, tab_id, NULL, NULL) || + extension->CanCaptureVisiblePage(url, tab_id, NULL)); + } + + bool AllowedExclusivelyOnTab( + const Extension* extension, + const std::set<GURL>& allowed_urls, + int tab_id) { + bool result = true; + for (std::set<GURL>::iterator it = urls_.begin(); it != urls_.end(); ++it) { + const GURL& url = *it; + if (allowed_urls.count(url)) + result &= Allowed(extension, url, tab_id); + else + result &= Blocked(extension, url, tab_id); + } + return result; + } -TEST_F(ExtensionScriptAndCaptureVisibleTest, Permissions) { - scoped_refptr<Extension> extension; // URLs that are "safe" to provide scripting and capture visible tab access // to if the permissions allow it. - GURL http_url("http://www.google.com"); - GURL https_url("https://www.google.com"); - GURL file_url("file:///foo/bar"); + const GURL http_url; + const GURL http_url_with_path; + const GURL https_url; + const GURL file_url; // We should allow host permission but not scripting permission for favicon // urls. - GURL favicon_url("chrome://favicon/http://www.google.com"); - - std::string dummy_id = - Extension::GenerateIdForPath(FilePath(FILE_PATH_LITERAL("whatever"))); + const GURL favicon_url; // URLs that regular extensions should never get access to. - GURL extension_url("chrome-extension://" + dummy_id); - GURL settings_url("chrome://settings"); - GURL about_url("about:flags"); + const GURL extension_url; + const GURL settings_url; + const GURL about_url; + + private: + // The set of all URLs above. + std::set<GURL> urls_; +}; + +TEST_F(ExtensionScriptAndCaptureVisibleTest, Permissions) { + scoped_refptr<Extension> extension; // Test <all_urls> for regular extensions. extension = LoadManifestStrict("script_and_capture", @@ -810,6 +853,74 @@ TEST_F(ExtensionScriptAndCaptureVisibleTest, Permissions) { EXPECT_FALSE(extension->HasHostPermission(settings_url)); } +TEST_F(ExtensionScriptAndCaptureVisibleTest, TabSpecific) { + scoped_refptr<Extension> extension = + LoadManifestStrict("script_and_capture", "tab_specific.json"); + + EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(0)); + EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(1)); + EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(2)); + + std::set<GURL> no_urls; + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2)); + + URLPatternSet allowed_hosts; + allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL, + http_url.spec())); + std::set<GURL> allowed_urls; + allowed_urls.insert(http_url); + // http_url_with_path() will also be allowed, because Extension should be + // considering the security origin of the URL not the URL itself, and + // http_url is in allowed_hosts. + allowed_urls.insert(http_url_with_path); + + extension->SetTabSpecificHostPermissions(0, allowed_hosts); + EXPECT_EQ(allowed_hosts, *extension->GetTabSpecificHostPermissions(0)); + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, allowed_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2)); + + extension->ClearTabSpecificHostPermissions(0); + EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(0)); + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2)); + + std::set<GURL> more_allowed_urls = allowed_urls; + more_allowed_urls.insert(https_url); + URLPatternSet more_allowed_hosts = allowed_hosts; + more_allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL, + https_url.spec())); + + extension->SetTabSpecificHostPermissions(0, allowed_hosts); + EXPECT_EQ(allowed_hosts, *extension->GetTabSpecificHostPermissions(0)); + extension->SetTabSpecificHostPermissions(1, more_allowed_hosts); + EXPECT_EQ(more_allowed_hosts, *extension->GetTabSpecificHostPermissions(1)); + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, allowed_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, more_allowed_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2)); + + extension->ClearTabSpecificHostPermissions(0); + EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(0)); + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, more_allowed_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2)); + + extension->ClearTabSpecificHostPermissions(1); + EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(1)); + + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1)); + EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2)); +} + TEST(ExtensionTest, GenerateId) { std::string result; EXPECT_TRUE(Extension::GenerateId("", &result)); diff --git a/chrome/common/extensions/url_pattern_set.cc b/chrome/common/extensions/url_pattern_set.cc index c84df14..0948f81 100644 --- a/chrome/common/extensions/url_pattern_set.cc +++ b/chrome/common/extensions/url_pattern_set.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -8,6 +8,7 @@ #include <iterator> #include "base/logging.h" +#include "base/memory/linked_ptr.h" #include "base/values.h" #include "chrome/common/extensions/extension_error_utils.h" #include "chrome/common/extensions/url_pattern.h" @@ -53,6 +54,39 @@ void URLPatternSet::CreateUnion(const URLPatternSet& set1, out->patterns_, out->patterns_.begin())); } +// static +void URLPatternSet::CreateUnion(const std::vector<URLPatternSet>& sets, + URLPatternSet* out) { + out->ClearPatterns(); + if (sets.empty()) + return; + + // N-way union algorithm is basic O(nlog(n)) merge algorithm. + // + // Do the first merge step into a working set so that we don't mutate any of + // the input. + std::vector<URLPatternSet> working; + for (size_t i = 0; i < sets.size(); i += 2) { + if (i + 1 < sets.size()) { + URLPatternSet u; + URLPatternSet::CreateUnion(sets[i], sets[i + 1], &u); + working.push_back(u); + } else { + working.push_back(sets[i]); + } + } + + for (size_t skip = 1; skip < working.size(); skip *= 2) { + for (size_t i = 0; i < (working.size() - skip); i += skip) { + URLPatternSet u; + URLPatternSet::CreateUnion(working[i], working[i + skip], &u); + working[i].patterns_.swap(u.patterns_); + } + } + + out->patterns_.swap(working[0].patterns_); +} + URLPatternSet::URLPatternSet() {} URLPatternSet::URLPatternSet(const URLPatternSet& rhs) @@ -76,6 +110,10 @@ bool URLPatternSet::is_empty() const { return patterns_.empty(); } +size_t URLPatternSet::size() const { + return patterns_.size(); +} + void URLPatternSet::AddPattern(const URLPattern& pattern) { patterns_.insert(pattern); } diff --git a/chrome/common/extensions/url_pattern_set.h b/chrome/common/extensions/url_pattern_set.h index c912558..b94239f 100644 --- a/chrome/common/extensions/url_pattern_set.h +++ b/chrome/common/extensions/url_pattern_set.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -40,6 +40,10 @@ class URLPatternSet { const URLPatternSet& set2, URLPatternSet* out); + // Clears |out| and populates it with the union of all sets in |sets|. + static void CreateUnion(const std::vector<URLPatternSet>& sets, + URLPatternSet* out); + URLPatternSet(); URLPatternSet(const URLPatternSet& rhs); explicit URLPatternSet(const std::set<URLPattern>& patterns); @@ -49,6 +53,7 @@ class URLPatternSet { bool operator==(const URLPatternSet& rhs) const; bool is_empty() const; + size_t size() const; const std::set<URLPattern>& patterns() const { return patterns_; } const_iterator begin() const { return patterns_.begin(); } const_iterator end() const { return patterns_.end(); } diff --git a/chrome/common/extensions/url_pattern_set_unittest.cc b/chrome/common/extensions/url_pattern_set_unittest.cc index ff78d90..ed45948 100644 --- a/chrome/common/extensions/url_pattern_set_unittest.cc +++ b/chrome/common/extensions/url_pattern_set_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,12 +9,27 @@ namespace { -static void AddPattern(URLPatternSet* set, const std::string& pattern) { +void AddPattern(URLPatternSet* set, const std::string& pattern) { int schemes = URLPattern::SCHEME_ALL; set->AddPattern(URLPattern(schemes, pattern)); } +URLPatternSet Patterns(const std::string& pattern) { + URLPatternSet set; + AddPattern(&set, pattern); + return set; } + +URLPatternSet Patterns(const std::string& pattern1, + const std::string& pattern2) { + URLPatternSet set; + AddPattern(&set, pattern1); + AddPattern(&set, pattern2); + return set; +} + +} + TEST(URLPatternSetTest, Empty) { URLPatternSet set; EXPECT_FALSE(set.MatchesURL(GURL("http://www.foo.com/bar"))); @@ -199,3 +214,156 @@ TEST(URLPatternSetTest, Duplicates) { // The sets should still be equal after adding a duplicate. EXPECT_EQ(set2, set1); } + +TEST(URLPatternSetTest, NwayUnion) { + std::string google_a = "http://www.google.com/a*"; + std::string google_b = "http://www.google.com/b*"; + std::string google_c = "http://www.google.com/c*"; + std::string yahoo_a = "http://www.yahoo.com/a*"; + std::string yahoo_b = "http://www.yahoo.com/b*"; + std::string yahoo_c = "http://www.yahoo.com/c*"; + std::string reddit_a = "http://www.reddit.com/a*"; + std::string reddit_b = "http://www.reddit.com/b*"; + std::string reddit_c = "http://www.reddit.com/c*"; + + // Empty list. + { + std::vector<URLPatternSet> empty; + + URLPatternSet result; + URLPatternSet::CreateUnion(empty, &result); + + URLPatternSet expected; + EXPECT_EQ(expected, result); + } + + // Singleton list. + { + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected = Patterns(google_a); + EXPECT_EQ(expected, result); + } + + // List with 2 elements. + { + + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a, google_b)); + test.push_back(Patterns(google_b, google_c)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected; + AddPattern(&expected, google_a); + AddPattern(&expected, google_b); + AddPattern(&expected, google_c); + EXPECT_EQ(expected, result); + } + + // List with 3 elements. + { + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a, google_b)); + test.push_back(Patterns(google_b, google_c)); + test.push_back(Patterns(yahoo_a, yahoo_b)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected; + AddPattern(&expected, google_a); + AddPattern(&expected, google_b); + AddPattern(&expected, google_c); + AddPattern(&expected, yahoo_a); + AddPattern(&expected, yahoo_b); + EXPECT_EQ(expected, result); + } + + // List with 7 elements. + { + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a)); + test.push_back(Patterns(google_b)); + test.push_back(Patterns(google_c)); + test.push_back(Patterns(yahoo_a)); + test.push_back(Patterns(yahoo_b)); + test.push_back(Patterns(yahoo_c)); + test.push_back(Patterns(reddit_a)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected; + AddPattern(&expected, google_a); + AddPattern(&expected, google_b); + AddPattern(&expected, google_c); + AddPattern(&expected, yahoo_a); + AddPattern(&expected, yahoo_b); + AddPattern(&expected, yahoo_c); + AddPattern(&expected, reddit_a); + EXPECT_EQ(expected, result); + } + + // List with 8 elements. + { + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a)); + test.push_back(Patterns(google_b)); + test.push_back(Patterns(google_c)); + test.push_back(Patterns(yahoo_a)); + test.push_back(Patterns(yahoo_b)); + test.push_back(Patterns(yahoo_c)); + test.push_back(Patterns(reddit_a)); + test.push_back(Patterns(reddit_b)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected; + AddPattern(&expected, google_a); + AddPattern(&expected, google_b); + AddPattern(&expected, google_c); + AddPattern(&expected, yahoo_a); + AddPattern(&expected, yahoo_b); + AddPattern(&expected, yahoo_c); + AddPattern(&expected, reddit_a); + AddPattern(&expected, reddit_b); + EXPECT_EQ(expected, result); + } + + // List with 9 elements. + { + + std::vector<URLPatternSet> test; + test.push_back(Patterns(google_a)); + test.push_back(Patterns(google_b)); + test.push_back(Patterns(google_c)); + test.push_back(Patterns(yahoo_a)); + test.push_back(Patterns(yahoo_b)); + test.push_back(Patterns(yahoo_c)); + test.push_back(Patterns(reddit_a)); + test.push_back(Patterns(reddit_b)); + test.push_back(Patterns(reddit_c)); + + URLPatternSet result; + URLPatternSet::CreateUnion(test, &result); + + URLPatternSet expected; + AddPattern(&expected, google_a); + AddPattern(&expected, google_b); + AddPattern(&expected, google_c); + AddPattern(&expected, yahoo_a); + AddPattern(&expected, yahoo_b); + AddPattern(&expected, yahoo_c); + AddPattern(&expected, reddit_a); + AddPattern(&expected, reddit_b); + AddPattern(&expected, reddit_c); + EXPECT_EQ(expected, result); + } +} diff --git a/chrome/renderer/extensions/extension_dispatcher.cc b/chrome/renderer/extensions/extension_dispatcher.cc index 1104566..23af146 100644 --- a/chrome/renderer/extensions/extension_dispatcher.cc +++ b/chrome/renderer/extensions/extension_dispatcher.cc @@ -40,6 +40,7 @@ #include "chrome/renderer/extensions/page_capture_custom_bindings.h" #include "chrome/renderer/extensions/send_request_natives.h" #include "chrome/renderer/extensions/set_icon_natives.h" +#include "chrome/renderer/extensions/tab_finder.h" #include "chrome/renderer/extensions/tabs_custom_bindings.h" #include "chrome/renderer/extensions/tts_custom_bindings.h" #include "chrome/renderer/extensions/user_script_slave.h" @@ -72,6 +73,7 @@ using WebKit::WebString; using WebKit::WebVector; using WebKit::WebView; using content::RenderThread; +using content::RenderView; using extensions::ApiDefinitionsNatives; using extensions::AppWindowCustomBindings; using extensions::ContextMenusCustomBindings; @@ -91,6 +93,7 @@ using extensions::PageCaptureCustomBindings; using extensions::SendRequestNatives; using extensions::SetIconNatives; using extensions::TTSCustomBindings; +using extensions::TabFinder; using extensions::TabsCustomBindings; using extensions::UpdatedExtensionPermissionsInfo; using extensions::WebRequestCustomBindings; @@ -153,7 +156,7 @@ class LazyBackgroundPageNativeHandler : public ChromeV8Extension { extension_dispatcher()->v8_context_set().GetCurrent(); if (!context) return v8::Undefined(); - content::RenderView* render_view = context->GetRenderView(); + RenderView* render_view = context->GetRenderView(); if (IsContextLazyBackgroundPage(render_view, context->extension())) { render_view->Send(new ExtensionHostMsg_IncrementLazyKeepaliveCount( render_view->GetRoutingID())); @@ -166,7 +169,7 @@ class LazyBackgroundPageNativeHandler : public ChromeV8Extension { extension_dispatcher()->v8_context_set().GetCurrent(); if (!context) return v8::Undefined(); - content::RenderView* render_view = context->GetRenderView(); + RenderView* render_view = context->GetRenderView(); if (IsContextLazyBackgroundPage(render_view, context->extension())) { render_view->Send(new ExtensionHostMsg_DecrementLazyKeepaliveCount( render_view->GetRoutingID())); @@ -175,7 +178,7 @@ class LazyBackgroundPageNativeHandler : public ChromeV8Extension { } private: - bool IsContextLazyBackgroundPage(content::RenderView* render_view, + bool IsContextLazyBackgroundPage(RenderView* render_view, const Extension* extension) { if (!render_view) return false; @@ -265,6 +268,10 @@ bool ExtensionDispatcher::OnControlMessageReceived( OnSetScriptingWhitelist) IPC_MESSAGE_HANDLER(ExtensionMsg_ActivateExtension, OnActivateExtension) IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions) + IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateTabSpecificPermissions, + OnUpdateTabSpecificPermissions) + IPC_MESSAGE_HANDLER(ExtensionMsg_ClearTabSpecificPermissions, + OnClearTabSpecificPermissions) IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts) IPC_MESSAGE_HANDLER(ExtensionMsg_UsingWebRequestAPI, OnUsingWebRequestAPI) IPC_MESSAGE_HANDLER(ExtensionMsg_ShouldUnload, OnShouldUnload) @@ -346,7 +353,7 @@ void ExtensionDispatcher::OnMessageInvoke(const std::string& extension_id, const Extension* extension = extensions_.GetByID(extension_id); if (extension && extension->has_lazy_background_page() && function_name == kEventDispatchFunction) { - content::RenderView* background_view = + RenderView* background_view = ExtensionHelper::GetBackgroundPage(extension_id); if (background_view) { background_view->Send(new ExtensionHostMsg_EventAck( @@ -779,12 +786,13 @@ void ExtensionDispatcher::InitOriginPermissions(const Extension* extension) { false); } - UpdateOriginPermissions(UpdatedExtensionPermissionsInfo::ADDED, - extension, - extension->GetActivePermissions()->explicit_hosts()); + AddOrRemoveOriginPermissions( + UpdatedExtensionPermissionsInfo::ADDED, + extension, + extension->GetActivePermissions()->explicit_hosts()); } -void ExtensionDispatcher::UpdateOriginPermissions( +void ExtensionDispatcher::AddOrRemoveOriginPermissions( UpdatedExtensionPermissionsInfo::Reason reason, const Extension* extension, const URLPatternSet& origins) { @@ -828,15 +836,49 @@ void ExtensionDispatcher::OnUpdatePermissions( static_cast<UpdatedExtensionPermissionsInfo::Reason>(reason_id); const ExtensionPermissionSet* new_active = NULL; - if (reason == UpdatedExtensionPermissionsInfo::ADDED) { - new_active = ExtensionPermissionSet::CreateUnion(old_active, delta); - } else { - CHECK_EQ(UpdatedExtensionPermissionsInfo::REMOVED, reason); - new_active = ExtensionPermissionSet::CreateDifference(old_active, delta); + switch (reason) { + case UpdatedExtensionPermissionsInfo::ADDED: + new_active = ExtensionPermissionSet::CreateUnion(old_active, delta); + break; + case UpdatedExtensionPermissionsInfo::REMOVED: + new_active = ExtensionPermissionSet::CreateDifference(old_active, delta); + break; } extension->SetActivePermissions(new_active); - UpdateOriginPermissions(reason, extension, explicit_hosts); + AddOrRemoveOriginPermissions(reason, extension, explicit_hosts); +} + +void ExtensionDispatcher::OnUpdateTabSpecificPermissions( + int page_id, + int tab_id, + const std::string& extension_id, + const URLPatternSet& origin_set) { + RenderView* view = TabFinder::Find(tab_id); + + // For now, the message should only be sent to the render view that contains + // the target tab. This may change. Either way, if this is the target tab it + // gives us the chance to check against the page ID to avoid races. + DCHECK(view); + if (view && view->GetPageId() != page_id) + return; + + const Extension* extension = extensions_.GetByID(extension_id); + if (!extension) + return; + + extension->SetTabSpecificHostPermissions(tab_id, origin_set); +} + +void ExtensionDispatcher::OnClearTabSpecificPermissions( + int tab_id, + const std::vector<std::string>& extension_ids) { + for (std::vector<std::string>::const_iterator it = extension_ids.begin(); + it != extension_ids.end(); ++it) { + const Extension* extension = extensions_.GetByID(*it); + if (extension) + extension->ClearTabSpecificHostPermissions(tab_id); + } } void ExtensionDispatcher::OnUpdateUserScripts( diff --git a/chrome/renderer/extensions/extension_dispatcher.h b/chrome/renderer/extensions/extension_dispatcher.h index 88bb1e4..40eaac5 100644 --- a/chrome/renderer/extensions/extension_dispatcher.h +++ b/chrome/renderer/extensions/extension_dispatcher.h @@ -153,6 +153,13 @@ class ExtensionDispatcher : public content::RenderProcessObserver { const ExtensionAPIPermissionSet& apis, const URLPatternSet& explicit_hosts, const URLPatternSet& scriptable_hosts); + void OnUpdateTabSpecificPermissions(int page_id, + int tab_id, + const std::string& extension_id, + const URLPatternSet& origin_set); + void OnClearTabSpecificPermissions( + int tab_id, + const std::vector<std::string>& extension_ids); void OnUpdateUserScripts(base::SharedMemoryHandle table); void OnUsingWebRequestAPI( bool adblock, @@ -170,7 +177,7 @@ class ExtensionDispatcher : public content::RenderProcessObserver { // Sets up the host permissions for |extension|. void InitOriginPermissions(const extensions::Extension* extension); - void UpdateOriginPermissions( + void AddOrRemoveOriginPermissions( extensions::UpdatedExtensionPermissionsInfo::Reason reason, const extensions::Extension* extension, const URLPatternSet& origins); diff --git a/chrome/renderer/extensions/extension_helper.cc b/chrome/renderer/extensions/extension_helper.cc index cafb63d..a3460bf 100644 --- a/chrome/renderer/extensions/extension_helper.cc +++ b/chrome/renderer/extensions/extension_helper.cc @@ -144,6 +144,7 @@ ExtensionHelper::ExtensionHelper(content::RenderView* render_view, extension_dispatcher_(extension_dispatcher), pending_app_icon_requests_(0), view_type_(chrome::VIEW_TYPE_INVALID), + tab_id_(-1), browser_window_id_(-1) { } @@ -209,6 +210,7 @@ bool ExtensionHelper::OnMessageReceived(const IPC::Message& message) { OnExtensionDispatchOnDisconnect) IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode) IPC_MESSAGE_HANDLER(ExtensionMsg_GetApplicationInfo, OnGetApplicationInfo) + IPC_MESSAGE_HANDLER(ExtensionMsg_SetTabId, OnSetTabId) IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateBrowserWindowId, OnUpdateBrowserWindowId) IPC_MESSAGE_HANDLER(ExtensionMsg_NotifyRenderViewType, @@ -373,6 +375,12 @@ void ExtensionHelper::OnNotifyRendererViewType(chrome::ViewType type) { view_type_ = type; } +void ExtensionHelper::OnSetTabId(int init_tab_id) { + CHECK_EQ(tab_id_, -1); + CHECK_GE(init_tab_id, 0); + tab_id_ = init_tab_id; +} + void ExtensionHelper::OnUpdateBrowserWindowId(int window_id) { browser_window_id_ = window_id; } diff --git a/chrome/renderer/extensions/extension_helper.h b/chrome/renderer/extensions/extension_helper.h index 4e88467..99afbde 100644 --- a/chrome/renderer/extensions/extension_helper.h +++ b/chrome/renderer/extensions/extension_helper.h @@ -59,6 +59,7 @@ class ExtensionHelper bool InstallWebApplicationUsingDefinitionFile(WebKit::WebFrame* frame, string16* error); + int tab_id() const { return tab_id_; } int browser_window_id() const { return browser_window_id_; } chrome::ViewType view_type() const { return view_type_; } @@ -92,6 +93,7 @@ class ExtensionHelper void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params); void OnGetApplicationInfo(int page_id); void OnNotifyRendererViewType(chrome::ViewType view_type); + void OnSetTabId(int tab_id); void OnUpdateBrowserWindowId(int window_id); // Callback triggered when we finish downloading the application definition @@ -129,6 +131,9 @@ class ExtensionHelper // Type of view attached with RenderView. chrome::ViewType view_type_; + // Id of the tab which the RenderView is attached to. + int tab_id_; + // Id number of browser window which RenderView is attached to. int browser_window_id_; diff --git a/chrome/renderer/extensions/tab_finder.cc b/chrome/renderer/extensions/tab_finder.cc new file mode 100644 index 0000000..b821793 --- /dev/null +++ b/chrome/renderer/extensions/tab_finder.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/renderer/extensions/tab_finder.h" + +#include "chrome/renderer/extensions/extension_helper.h" +#include "content/public/renderer/render_view.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" + +using content::RenderView; + +namespace extensions { + +// static +content::RenderView* TabFinder::Find(int tab_id) { + TabFinder finder(tab_id); + RenderView::ForEach(&finder); + return finder.view_; +} + +TabFinder::TabFinder(int tab_id) : tab_id_(tab_id), view_(NULL) {} + +TabFinder::~TabFinder() {} + +// Note: Visit returns false to terminate the iteration. +bool TabFinder::Visit(RenderView* render_view) { + // Only interested in the top frame. + if (render_view->GetWebView()->mainFrame()->parent()) + return true; + + ExtensionHelper* helper = ExtensionHelper::Get(render_view); + if (helper && helper->tab_id() == tab_id_) + view_ = render_view; + + return !view_; +} + +} // namespace extensions diff --git a/chrome/renderer/extensions/tab_finder.h b/chrome/renderer/extensions/tab_finder.h new file mode 100644 index 0000000..04cfacb --- /dev/null +++ b/chrome/renderer/extensions/tab_finder.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_RENDERER_EXTENSIONS_TAB_FINDER_H_ +#define CHROME_RENDERER_EXTENSIONS_TAB_FINDER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/public/renderer/render_view_visitor.h" + +namespace content { +class RenderView; +} + +namespace extensions { + +// Finds the top RenderView associated with a tab ID. +class TabFinder : public content::RenderViewVisitor { + public: + // Returns the top RenderView with |tab_id|, or NULL if none is found. + static content::RenderView* Find(int tab_id); + + private: + explicit TabFinder(int tab_id); + virtual ~TabFinder(); + + // content::RenderViewVisitor implementation. + virtual bool Visit(content::RenderView* render_view) OVERRIDE; + + int tab_id_; + content::RenderView* view_; + + DISALLOW_COPY_AND_ASSIGN(TabFinder); +}; + +} // namespace extensions + +#endif // CHROME_RENDERER_EXTENSIONS_TAB_FINDER_H_ diff --git a/chrome/renderer/extensions/user_script_scheduler.cc b/chrome/renderer/extensions/user_script_scheduler.cc index dad8cb9..dc4ebee 100644 --- a/chrome/renderer/extensions/user_script_scheduler.cc +++ b/chrome/renderer/extensions/user_script_scheduler.cc @@ -133,6 +133,7 @@ void UserScriptScheduler::ExecuteCodeImpl( params.extension_id); content::RenderView* render_view = content::RenderView::FromWebView(frame_->view()); + ExtensionHelper* extension_helper = ExtensionHelper::Get(render_view); // Since extension info is sent separately from user script info, they can // be out of sync. We just ignore this situation. @@ -162,7 +163,9 @@ void UserScriptScheduler::ExecuteCodeImpl( // For child frames, we just skip ones the extension doesn't have access // to and carry on. if (!extension->CanExecuteScriptOnPage(frame->document().url(), - NULL, NULL)) { + extension_helper->tab_id(), + NULL, + NULL)) { if (frame->parent()) { continue; } else { diff --git a/chrome/renderer/extensions/user_script_slave.cc b/chrome/renderer/extensions/user_script_slave.cc index 8862631..9ddaf4d 100644 --- a/chrome/renderer/extensions/user_script_slave.cc +++ b/chrome/renderer/extensions/user_script_slave.cc @@ -279,8 +279,14 @@ void UserScriptSlave::InjectScripts(WebFrame* frame, if (!extension) continue; - if (!extension->CanExecuteScriptOnPage(data_source_url, script, NULL)) + // Content scripts are not tab-specific. + int kNoTabId = -1; + if (!extension->CanExecuteScriptOnPage(data_source_url, + kNoTabId, + script, + NULL)) { continue; + } // We rely on WebCore for CSS injection, but it's still useful to know how // many css files there are. diff --git a/chrome/test/data/extensions/script_and_capture/tab_specific.json b/chrome/test/data/extensions/script_and_capture/tab_specific.json new file mode 100644 index 0000000..52a16c3 --- /dev/null +++ b/chrome/test/data/extensions/script_and_capture/tab_specific.json @@ -0,0 +1,4 @@ +{ + "name": "Extension with no host permissions.", + "version": "1" +} |