diff options
27 files changed, 549 insertions, 64 deletions
diff --git a/chrome/browser/extensions/extension_sidebar_api.cc b/chrome/browser/extensions/extension_sidebar_api.cc index bd32d5a..9639a3a 100644 --- a/chrome/browser/extensions/extension_sidebar_api.cc +++ b/chrome/browser/extensions/extension_sidebar_api.cc @@ -20,15 +20,16 @@ #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_error_utils.h" +#include "chrome/common/extensions/extension_sidebar_utils.h" #include "chrome/common/render_messages.h" -#include "chrome/common/url_constants.h" #include "ipc/ipc_message_utils.h" #include "third_party/skia/include/core/SkBitmap.h" namespace { // Errors. +const char kNoSidebarError[] = + "This extension has no sidebar specified."; const char kNoTabError[] = "No tab with id: *."; -const char kInvalidUrlError[] = "Invalid url: \"*\"."; const char kNoCurrentWindowError[] = "No current browser window was found"; const char kNoDefaultTabError[] = "No default tab was found"; const char kInvalidExpandContextError[] = @@ -49,31 +50,7 @@ namespace extension_sidebar_constants { const char kActiveState[] = "active"; const char kHiddenState[] = "hidden"; const char kShownState[] = "shown"; -} - -static GURL ResolvePossiblyRelativeURL(const std::string& url_string, - const Extension* extension) { - GURL url = GURL(url_string); - if (!url.is_valid()) - url = extension->GetResourceURL(url_string); - - return url; -} - -static bool CanUseHost(const Extension* extension, - const GURL& url, - std::string* error) { - if (extension->HasHostPermission(url)) - return true; - - if (error) { - *error = ExtensionErrorUtils::FormatErrorMessage( - extension_manifest_errors::kCannotAccessPage, url.spec()); - } - - return false; -} - +} // namespace extension_sidebar_constants // static void ExtensionSidebarEventRouter::OnStateChanged( @@ -89,9 +66,9 @@ void ExtensionSidebarEventRouter::OnStateChanged( std::string json_args; base::JSONWriter::Write(&args, false, &json_args); - const std::string& extension_id(content_id); profile->GetExtensionEventRouter()->DispatchEventToExtension( - extension_id, kOnStateChanged, json_args, profile, GURL()); + extension_sidebar_utils::GetExtensionIdByContentId(content_id), + kOnStateChanged, json_args, profile, GURL()); } @@ -111,6 +88,11 @@ static bool IsArgumentListEmpty(const ListValue* arguments) { } bool SidebarFunction::RunImpl() { + if (!GetExtension()->sidebar_defaults()) { + error_ = kNoSidebarError; + return false; + } + if (!args_.get()) return false; @@ -224,24 +206,13 @@ bool NavigateSidebarFunction::RunImpl(TabContents* tab, const DictionaryValue& details) { std::string url_string; EXTENSION_FUNCTION_VALIDATE(details.GetString(kUrlKey, &url_string)); - GURL url = ResolvePossiblyRelativeURL(url_string, GetExtension()); - if (!url.is_valid()) { - error_ = ExtensionErrorUtils::FormatErrorMessage(kInvalidUrlError, - url_string); - return false; - } - if (!url.SchemeIs(chrome::kExtensionScheme) && - !CanUseHost(GetExtension(), url, &error_)) { + + GURL url = extension_sidebar_utils::ResolveAndVerifyUrl( + url_string, GetExtension(), &error_); + if (!url.is_valid()) return false; - } - // Disallow requests outside of the requesting extension view's extension. - if (url.SchemeIs(chrome::kExtensionScheme)) { - std::string extension_id(url.host()); - if (extension_id != GetExtension()->id()) - return false; - } - SidebarManager::GetInstance()->NavigateSidebar(tab, content_id, GURL(url)); + SidebarManager::GetInstance()->NavigateSidebar(tab, content_id, url); return true; } diff --git a/chrome/browser/sidebar/sidebar_test.cc b/chrome/browser/sidebar/sidebar_browsertest.cc index 1d57333..041a683 100644 --- a/chrome/browser/sidebar/sidebar_test.cc +++ b/chrome/browser/sidebar/sidebar_browsertest.cc @@ -3,30 +3,52 @@ // found in the LICENSE file. #include "base/command_line.h" +#include "base/file_path.h" +#include "base/path_service.h" +#include "base/ref_counted.h" #include "chrome/browser/browser_window.h" +#include "chrome/browser/extensions/extension_browsertest.h" #include "chrome/browser/sidebar/sidebar_manager.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension.h" #include "chrome/test/in_process_browser_test.h" #include "chrome/test/ui_test_utils.h" #include "net/test/test_server.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/profiles/profile.h" + namespace { -const char kSampleContentId[] = "sample_content_id"; const char kSimplePage[] = "files/sidebar/simple_page.html"; -class SidebarTest : public InProcessBrowserTest { +class SidebarTest : public ExtensionBrowserTest { public: SidebarTest() { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableExperimentalExtensionApis); - set_show_window(true); } protected: + // InProcessBrowserTest overrides. + virtual void SetUpOnMainThread() { + ExtensionBrowserTest::SetUpOnMainThread(); + + // Load test sidebar extension. + FilePath extension_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extension_path)); + extension_path = extension_path.AppendASCII("sidebar"); + + ASSERT_TRUE(LoadExtension(extension_path)); + + // For now content_id == extension_id. + content_id_ = last_loaded_extension_id_; + } + void ShowSidebarForCurrentTab() { ShowSidebar(browser()->GetSelectedTabContents()); } @@ -50,10 +72,10 @@ class SidebarTest : public InProcessBrowserTest { SidebarManager* sidebar_manager = SidebarManager::GetInstance(); - sidebar_manager->NavigateSidebar(tab, kSampleContentId, url); + sidebar_manager->NavigateSidebar(tab, content_id_, url); SidebarContainer* sidebar_container = - sidebar_manager->GetSidebarContainerFor(tab, kSampleContentId); + sidebar_manager->GetSidebarContainerFor(tab, content_id_); TabContents* client_contents = sidebar_container->sidebar_contents(); ui_test_utils::WaitForNavigation(&client_contents->controller()); @@ -61,26 +83,26 @@ class SidebarTest : public InProcessBrowserTest { void ShowSidebar(TabContents* tab) { SidebarManager* sidebar_manager = SidebarManager::GetInstance(); - sidebar_manager->ShowSidebar(tab, kSampleContentId); + sidebar_manager->ShowSidebar(tab, content_id_); } void ExpandSidebar(TabContents* tab) { SidebarManager* sidebar_manager = SidebarManager::GetInstance(); - sidebar_manager->ExpandSidebar(tab, kSampleContentId); + sidebar_manager->ExpandSidebar(tab, content_id_); if (browser()->GetSelectedTabContents() == tab) EXPECT_GT(browser_view()->GetSidebarWidth(), 0); } void CollapseSidebar(TabContents* tab) { SidebarManager* sidebar_manager = SidebarManager::GetInstance(); - sidebar_manager->CollapseSidebar(tab, kSampleContentId); + sidebar_manager->CollapseSidebar(tab, content_id_); if (browser()->GetSelectedTabContents() == tab) EXPECT_EQ(0, browser_view()->GetSidebarWidth()); } void HideSidebar(TabContents* tab) { SidebarManager* sidebar_manager = SidebarManager::GetInstance(); - sidebar_manager->HideSidebar(tab, kSampleContentId); + sidebar_manager->HideSidebar(tab, content_id_); if (browser()->GetSelectedTabContents() == tab) EXPECT_EQ(0, browser_view()->GetSidebarWidth()); } @@ -92,6 +114,9 @@ class SidebarTest : public InProcessBrowserTest { BrowserView* browser_view() const { return static_cast<BrowserView*>(browser()->window()); } + + private: + std::string content_id_; }; IN_PROC_BROWSER_TEST_F(SidebarTest, OpenClose) { diff --git a/chrome/browser/sidebar/sidebar_container.cc b/chrome/browser/sidebar/sidebar_container.cc index 7112433..a28ef21 100644 --- a/chrome/browser/sidebar/sidebar_container.cc +++ b/chrome/browser/sidebar/sidebar_container.cc @@ -4,12 +4,18 @@ #include "chrome/browser/sidebar/sidebar_container.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/profiles/profile.h" #include "chrome/browser/renderer_host/render_view_host.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/navigation_entry.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/tab_contents/tab_contents_view.h" #include "chrome/common/bindings_policy.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_resource.h" +#include "chrome/common/extensions/extension_sidebar_defaults.h" +#include "chrome/common/extensions/extension_sidebar_utils.h" #include "googleurl/src/gurl.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -19,7 +25,9 @@ SidebarContainer::SidebarContainer(TabContents* tab, : tab_(tab), content_id_(content_id), delegate_(delegate), - icon_(new SkBitmap) { + icon_(new SkBitmap), + navigate_to_default_url_on_expand_(true), + use_default_icon_(true) { // Create TabContents for sidebar. sidebar_contents_.reset( new TabContents(tab->profile(), NULL, MSG_ROUTING_NONE, NULL, NULL)); @@ -36,11 +44,41 @@ void SidebarContainer::SidebarClosing() { delegate_->UpdateSidebar(this); } +void SidebarContainer::LoadDefaults() { + const Extension* extension = GetExtension(); + if (!extension) + return; // Can be NULL in tests. + const ExtensionSidebarDefaults* sidebar_defaults = + extension->sidebar_defaults(); + + title_ = sidebar_defaults->default_title(); + + if (!sidebar_defaults->default_icon_path().empty()) { + image_loading_tracker_.reset(new ImageLoadingTracker(this)); + image_loading_tracker_->LoadImage( + extension, + extension->GetResource(sidebar_defaults->default_icon_path()), + gfx::Size(Extension::kSidebarIconMaxSize, + Extension::kSidebarIconMaxSize), + ImageLoadingTracker::CACHE); + } +} + void SidebarContainer::Show() { delegate_->UpdateSidebar(this); } void SidebarContainer::Expand() { + if (navigate_to_default_url_on_expand_) { + navigate_to_default_url_on_expand_ = false; + // Check whether a default URL is specified for this sidebar. + const Extension* extension = GetExtension(); + if (extension) { // Can be NULL in tests. + if (extension->sidebar_defaults()->default_url().is_valid()) + Navigate(extension->sidebar_defaults()->default_url()); + } + } + delegate_->UpdateSidebar(this); sidebar_contents_->view()->SetInitialFocus(); } @@ -50,8 +88,8 @@ void SidebarContainer::Collapse() { } void SidebarContainer::Navigate(const GURL& url) { - DCHECK(sidebar_contents_.get()); // TODO(alekseys): add a progress UI. + navigate_to_default_url_on_expand_ = false; sidebar_contents_->controller().LoadURL( url, GURL(), PageTransition::START_PAGE); } @@ -61,6 +99,7 @@ void SidebarContainer::SetBadgeText(const string16& badge_text) { } void SidebarContainer::SetIcon(const SkBitmap& bitmap) { + use_default_icon_ = false; *icon_ = bitmap; } @@ -72,3 +111,20 @@ bool SidebarContainer::IsPopup(const TabContents* source) const { return false; } +void SidebarContainer::OnImageLoaded(SkBitmap* image, + ExtensionResource resource, + int index) { + if (image && use_default_icon_) { + *icon_ = *image; + delegate_->UpdateSidebar(this); + } +} + +const Extension* SidebarContainer::GetExtension() const { + ExtensionService* service = + sidebar_contents_->profile()->GetExtensionService(); + if (!service) + return NULL; + return service->GetExtensionById( + extension_sidebar_utils::GetExtensionIdByContentId(content_id_), false); +} diff --git a/chrome/browser/sidebar/sidebar_container.h b/chrome/browser/sidebar/sidebar_container.h index b519de8..bcd5cfd 100644 --- a/chrome/browser/sidebar/sidebar_container.h +++ b/chrome/browser/sidebar/sidebar_container.h @@ -10,6 +10,7 @@ #include "base/basictypes.h" #include "base/scoped_ptr.h" #include "base/string16.h" +#include "chrome/browser/extensions/image_loading_tracker.h" #include "chrome/browser/tab_contents/tab_contents_delegate.h" class BrowserWindow; @@ -25,7 +26,8 @@ class TabContents; // tab it is linked to, mini tab icon, title etc. // class SidebarContainer - : public TabContentsDelegate { + : public TabContentsDelegate, + private ImageLoadingTracker::Observer { public: // Interface to implement to listen for sidebar update notification. class Delegate { @@ -37,7 +39,8 @@ class SidebarContainer DISALLOW_COPY_AND_ASSIGN(Delegate); }; - SidebarContainer(TabContents* tab, const std::string& content_id, + SidebarContainer(TabContents* tab, + const std::string& content_id, Delegate* delegate); virtual ~SidebarContainer(); @@ -45,6 +48,9 @@ class SidebarContainer // Does all the necessary cleanup. void SidebarClosing(); + // Sets default sidebar parameters, as specified in extension manifest. + void LoadDefaults(); + // Returns sidebar's content id. const std::string& content_id() const { return content_id_; } @@ -110,6 +116,14 @@ class SidebarContainer virtual void UpdateTargetURL(TabContents* source, const GURL& url) {} virtual void ToolbarSizeChanged(TabContents* source, bool is_animating) {} + // Overridden from ImageLoadingTracker::Observer. + virtual void OnImageLoaded(SkBitmap* image, + ExtensionResource resource, + int index); + + // Returns an extension this sidebar belongs to. + const Extension* GetExtension() const; + // Contents of the tab this sidebar is linked to. TabContents* tab_; @@ -132,6 +146,20 @@ class SidebarContainer // Sidebar's title, displayed as a tooltip for sidebar's mini tab. string16 title_; + // On the first expand sidebar will be automatically navigated to the default + // url (specified in the extension manifest), but only if the extension has + // not explicitly navigated it yet. This variable is set to false on the first + // sidebar navigation. + bool navigate_to_default_url_on_expand_; + // Since the default icon (specified in the extension manifest) is loaded + // asynchronously, sidebar icon can already be set by the extension + // by the time it's loaded. This variable tracks whether the loaded default + // icon should be used or discarded. + bool use_default_icon_; + + // Helper to load icons from extension asynchronously. + scoped_ptr<ImageLoadingTracker> image_loading_tracker_; + DISALLOW_COPY_AND_ASSIGN(SidebarContainer); }; diff --git a/chrome/browser/sidebar/sidebar_manager.cc b/chrome/browser/sidebar/sidebar_manager.cc index 7030da8..27d7e33 100644 --- a/chrome/browser/sidebar/sidebar_manager.cc +++ b/chrome/browser/sidebar/sidebar_manager.cc @@ -107,6 +107,9 @@ void SidebarManager::ShowSidebar(TabContents* tab, if (!host) { host = new SidebarContainer(tab, content_id, this); RegisterSidebarContainerFor(tab, host); + // It might trigger UpdateSidebar notification, so load them after + // the registration. + host->LoadDefaults(); } host->Show(); diff --git a/chrome/browser/sidebar/sidebar_manager.h b/chrome/browser/sidebar/sidebar_manager.h index 6f2d20b..e1a0080 100644 --- a/chrome/browser/sidebar/sidebar_manager.h +++ b/chrome/browser/sidebar/sidebar_manager.h @@ -74,19 +74,23 @@ class SidebarManager : public NotificationObserver, void HideSidebar(TabContents* tab, const std::string& content_id); // Navigates sidebar identified by |tab| and |content_id| to |url|. - void NavigateSidebar(TabContents* tab, const std::string& content_id, + void NavigateSidebar(TabContents* tab, + const std::string& content_id, const GURL& url); // Changes sidebar's badge text (displayed on the mini tab). - void SetSidebarBadgeText(TabContents* tab, const std::string& content_id, + void SetSidebarBadgeText(TabContents* tab, + const std::string& content_id, const string16& badge_text); // Changes sidebar's icon (displayed on the mini tab). - void SetSidebarIcon(TabContents* tab, const std::string& content_id, + void SetSidebarIcon(TabContents* tab, + const std::string& content_id, const SkBitmap& bitmap); // Changes sidebar's title (mini tab's tooltip). - void SetSidebarTitle(TabContents* tab, const std::string& content_id, + void SetSidebarTitle(TabContents* tab, + const std::string& content_id, const string16& title); private: diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index 7d8d62d..5563d98 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -277,6 +277,9 @@ 'common/extensions/extension_message_bundle.h', 'common/extensions/extension_resource.cc', 'common/extensions/extension_resource.h', + 'common/extensions/extension_sidebar_defaults.h', + 'common/extensions/extension_sidebar_utils.cc', + 'common/extensions/extension_sidebar_utils.h', 'common/extensions/extension_unpacker.cc', 'common/extensions/extension_unpacker.h', 'common/extensions/update_manifest.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index d6e50d9..d9274b1 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -444,8 +444,6 @@ 'browser/accessibility/accessibility_win_browsertest.cc', 'browser/accessibility/browser_views_accessibility_browsertest.cc', - # TODO: port sidebar. - 'browser/sidebar/sidebar_test.cc', ], 'conditions': [ ['win_use_allocator_shim==1', { @@ -2224,6 +2222,8 @@ '<(SHARED_INTERMEDIATE_DIR)/chrome/theme_resources.rc', '<(SHARED_INTERMEDIATE_DIR)/webkit/webkit_chromium_resources.rc', '<(SHARED_INTERMEDIATE_DIR)/webkit/webkit_resources.rc', + # TODO(alekseys): port sidebar to linux/mac. + 'browser/sidebar/sidebar_browsertest.cc', ], 'include_dirs': [ '<(DEPTH)/third_party/wtl/include', diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index 5d7e1d2..e4384ca 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -18,6 +18,7 @@ #include "base/singleton.h" #include "base/stl_util-inl.h" #include "base/third_party/nss/blapi.h" +#include "base/string16.h" #include "base/string_number_conversions.h" #include "base/utf_string_conversions.h" #include "base/values.h" @@ -30,6 +31,8 @@ #include "chrome/common/extensions/extension_error_utils.h" #include "chrome/common/extensions/extension_l10n_util.h" #include "chrome/common/extensions/extension_resource.h" +#include "chrome/common/extensions/extension_sidebar_defaults.h" +#include "chrome/common/extensions/extension_sidebar_utils.h" #include "chrome/common/extensions/user_script.h" #include "chrome/common/url_constants.h" #include "googleurl/src/url_util.h" @@ -194,6 +197,7 @@ const int Extension::kIconSizes[] = { const int Extension::kPageActionIconMaxSize = 19; const int Extension::kBrowserActionIconMaxSize = 19; +const int Extension::kSidebarIconMaxSize = 16; // Explicit permissions -- permission declaration required. const char Extension::kBackgroundPermission[] = "background"; @@ -804,6 +808,51 @@ ExtensionAction* Extension::LoadExtensionActionHelper( return result.release(); } +ExtensionSidebarDefaults* Extension::LoadExtensionSidebarDefaults( + const DictionaryValue* extension_sidebar, std::string* error) { + scoped_ptr<ExtensionSidebarDefaults> result(new ExtensionSidebarDefaults()); + + std::string default_icon; + // Read sidebar's |default_icon| (optional). + if (extension_sidebar->HasKey(keys::kSidebarDefaultIcon)) { + if (!extension_sidebar->GetString(keys::kSidebarDefaultIcon, + &default_icon) || + default_icon.empty()) { + *error = errors::kInvalidSidebarDefaultIconPath; + return NULL; + } + result->set_default_icon_path(default_icon); + } + + // Read sidebar's |default_title| (optional). + string16 default_title; + if (extension_sidebar->HasKey(keys::kSidebarDefaultTitle)) { + if (!extension_sidebar->GetString(keys::kSidebarDefaultTitle, + &default_title)) { + *error = errors::kInvalidSidebarDefaultTitle; + return NULL; + } + } + result->set_default_title(default_title); + + // Read sidebar's |default_url| (optional). + std::string default_url; + if (extension_sidebar->HasKey(keys::kSidebarDefaultUrl)) { + if (!extension_sidebar->GetString(keys::kSidebarDefaultUrl, &default_url) || + default_url.empty()) { + *error = errors::kInvalidSidebarDefaultUrl; + return NULL; + } + GURL resolved_url = extension_sidebar_utils::ResolveAndVerifyUrl( + default_url, this, error); + if (!resolved_url.is_valid()) + return NULL; + result->set_default_url(resolved_url); + } + + return result.release(); +} + bool Extension::ContainsNonThemeKeys(const DictionaryValue& source) const { for (DictionaryValue::key_iterator key = source.begin_keys(); key != source.end_keys(); ++key) { @@ -1927,6 +1976,23 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_key, InitEffectiveHostPermissions(); + // Initialize sidebar action (optional). It has to be done after host + // permissions are initialized to verify default sidebar url. + if (source.HasKey(keys::kSidebar)) { + DictionaryValue* sidebar_value; + if (!source.GetDictionary(keys::kSidebar, &sidebar_value)) { + *error = errors::kInvalidSidebar; + return false; + } + if (!HasApiPermission(Extension::kExperimentalPermission)) { + *error = errors::kSidebarExperimental; + return false; + } + sidebar_defaults_.reset(LoadExtensionSidebarDefaults(sidebar_value, error)); + if (!sidebar_defaults_.get()) + return false; // Failed to parse sidebar definition. + } + // Although |source| is passed in as a const, it's still possible to modify // it. This is dangerous since the utility process re-uses |source| after // it calls InitFromValue, passing it up to the browser process which calls diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index 4c82fd6..f58d273 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -26,6 +26,7 @@ class DictionaryValue; class ExtensionAction; class ExtensionResource; +class ExtensionSidebarDefaults; class SkBitmap; class Version; @@ -151,6 +152,7 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // Max size (both dimensions) for browser and page actions. static const int kPageActionIconMaxSize; static const int kBrowserActionIconMaxSize; + static const int kSidebarIconMaxSize; // Each permission is a module that the extension is permitted to use. // @@ -414,6 +416,9 @@ class Extension : public base::RefCountedThreadSafe<Extension> { const UserScriptList& content_scripts() const { return content_scripts_; } ExtensionAction* page_action() const { return page_action_.get(); } ExtensionAction* browser_action() const { return browser_action_.get(); } + ExtensionSidebarDefaults* sidebar_defaults() const { + return sidebar_defaults_.get(); + } const std::vector<PluginInfo>& plugins() const { return plugins_; } const GURL& background_url() const { return background_url_; } const GURL& options_url() const { return options_url_; } @@ -533,6 +538,11 @@ class Extension : public base::RefCountedThreadSafe<Extension> { ExtensionAction* LoadExtensionActionHelper( const DictionaryValue* extension_action, std::string* error); + // Helper method to load an ExtensionSidebarDefaults from the sidebar manifest + // entry. + ExtensionSidebarDefaults* LoadExtensionSidebarDefaults( + const DictionaryValue* sidebar, std::string* error); + // Calculates the effective host permissions from the permissions and content // script petterns. void InitEffectiveHostPermissions(); @@ -626,6 +636,9 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // The extension's browser action, if any. scoped_ptr<ExtensionAction> browser_action_; + // The extension's sidebar, if any. + scoped_ptr<ExtensionSidebarDefaults> sidebar_defaults_; + // Optional list of NPAPI plugins and associated properties. std::vector<PluginInfo> plugins_; diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc index dd15afa..af8bff3 100644 --- a/chrome/common/extensions/extension_constants.cc +++ b/chrome/common/extensions/extension_constants.cc @@ -52,6 +52,10 @@ const char* kPluginsPath = "path"; const char* kPluginsPublic = "public"; const char* kPublicKey = "key"; const char* kRunAt = "run_at"; +const char* kSidebar = "sidebar"; +const char* kSidebarDefaultIcon = "default_icon"; +const char* kSidebarDefaultTitle = "default_title"; +const char* kSidebarDefaultUrl = "default_url"; const char* kSignature = "signature"; const char* kTheme = "theme"; const char* kThemeColors = "colors"; @@ -225,6 +229,14 @@ const char* kInvalidPluginsPublic = "Invalid value for 'plugins[*].public'."; const char* kInvalidRunAt = "Invalid value for 'content_scripts[*].run_at'."; +extern const char* kInvalidSidebar = + "Invalid value for 'sidebar'."; +extern const char* kInvalidSidebarDefaultIconPath = + "Invalid value for 'sidebar.default_icon'."; +extern const char* kInvalidSidebarDefaultTitle = + "Invalid value for 'sidebar.default_title'."; +extern const char* kInvalidSidebarDefaultUrl = + "Invalid value for 'sidebar.default_url'."; const char* kInvalidSignature = "Value 'signature' is missing or invalid."; const char* kInvalidTheme = @@ -288,6 +300,9 @@ const char* kOneUISurfaceOnly = "Only one of 'browser_action', 'page_action', and 'app' can be specified."; const char* kReservedMessageFound = "Reserved key * found in message catalog."; +const char* kSidebarExperimental = + "You must request the 'experimental' permission in order to use the" + " Sidebar API."; const char* kThemesCannotContainExtensions = "A theme cannot contain extensions code."; #if defined(OS_CHROMEOS) diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h index 3ae0729..d67966f 100644 --- a/chrome/common/extensions/extension_constants.h +++ b/chrome/common/extensions/extension_constants.h @@ -57,6 +57,10 @@ namespace extension_manifest_keys { extern const char* kPluginsPublic; extern const char* kPublicKey; extern const char* kRunAt; + extern const char* kSidebar; + extern const char* kSidebarDefaultIcon; + extern const char* kSidebarDefaultTitle; + extern const char* kSidebarDefaultUrl; extern const char* kSignature; extern const char* kTheme; extern const char* kThemeColors; @@ -160,6 +164,10 @@ namespace extension_manifest_errors { extern const char* kInvalidPluginsPath; extern const char* kInvalidPluginsPublic; extern const char* kInvalidRunAt; + extern const char* kInvalidSidebar; + extern const char* kInvalidSidebarDefaultIconPath; + extern const char* kInvalidSidebarDefaultTitle; + extern const char* kInvalidSidebarDefaultUrl; extern const char* kInvalidSignature; extern const char* kInvalidTheme; extern const char* kInvalidThemeColors; @@ -191,6 +199,7 @@ namespace extension_manifest_errors { extern const char* kMultipleOverrides; extern const char* kOneUISurfaceOnly; extern const char* kReservedMessageFound; + extern const char* kSidebarExperimental; extern const char* kThemesCannotContainExtensions; extern const char* kWebContentMustBeEnabled; #if defined(OS_CHROMEOS) diff --git a/chrome/common/extensions/extension_manifests_unittest.cc b/chrome/common/extensions/extension_manifests_unittest.cc index f7c8f27..694cf39 100644 --- a/chrome/common/extensions/extension_manifests_unittest.cc +++ b/chrome/common/extensions/extension_manifests_unittest.cc @@ -8,11 +8,13 @@ #include "base/path_service.h" #include "base/scoped_ptr.h" #include "base/string_util.h" +#include "base/utf_string_conversions.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_error_utils.h" +#include "chrome/common/extensions/extension_sidebar_defaults.h" #include "chrome/common/json_value_serializer.h" #include "testing/gtest/include/gtest/gtest.h" @@ -271,6 +273,47 @@ TEST_F(ExtensionManifestTest, DevToolsExtensions) { *CommandLine::ForCurrentProcess() = old_command_line; } +TEST_F(ExtensionManifestTest, Sidebar) { + LoadAndExpectError("sidebar.json", + errors::kExperimentalFlagRequired); + + CommandLine old_command_line = *CommandLine::ForCurrentProcess(); + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalExtensionApis); + + LoadAndExpectError("sidebar_no_permissions.json", + errors::kSidebarExperimental); + + LoadAndExpectError("sidebar_icon_empty.json", + errors::kInvalidSidebarDefaultIconPath); + LoadAndExpectError("sidebar_icon_invalid_type.json", + errors::kInvalidSidebarDefaultIconPath); + LoadAndExpectError("sidebar_title_invalid_type.json", + errors::kInvalidSidebarDefaultTitle); + LoadAndExpectError("sidebar_url_invalid.json", + errors::kInvalidSidebarDefaultUrl); + LoadAndExpectError("sidebar_url_invalid_type.json", + errors::kInvalidSidebarDefaultUrl); + LoadAndExpectError("sidebar_url_no_permissions.json", + extension_manifest_errors::kCannotAccessPage); + + scoped_refptr<Extension> extension(LoadAndExpectSuccess("sidebar.json")); + ASSERT_TRUE(extension->sidebar_defaults() != NULL); + EXPECT_EQ(extension->sidebar_defaults()->default_title(), + ASCIIToUTF16("Default title")); + EXPECT_EQ(extension->sidebar_defaults()->default_icon_path(), + "icon.png"); + EXPECT_EQ(extension->url().spec() + "sidebar.html", + extension->sidebar_defaults()->default_url().spec()); + + scoped_refptr<Extension> extension_external_url( + LoadAndExpectSuccess("sidebar_external_url.json")); + EXPECT_EQ("http://sidebar.url/sidebar.html", + extension_external_url->sidebar_defaults()->default_url().spec()); + + *CommandLine::ForCurrentProcess() = old_command_line; +} + TEST_F(ExtensionManifestTest, DisallowHybridApps) { LoadAndExpectError("disallow_hybrid_1.json", errors::kHostedAppsCannotIncludeExtensionFeatures); diff --git a/chrome/common/extensions/extension_sidebar_defaults.h b/chrome/common/extensions/extension_sidebar_defaults.h new file mode 100644 index 0000000..0b7fc6fa --- /dev/null +++ b/chrome/common/extensions/extension_sidebar_defaults.h @@ -0,0 +1,48 @@ +// Copyright (c) 2010 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_COMMON_EXTENSIONS_EXTENSION_SIDEBAR_DEFAULTS_H_ +#define CHROME_COMMON_EXTENSIONS_EXTENSION_SIDEBAR_DEFAULTS_H_ +#pragma once + +#include <string> + +#include "base/string16.h" +#include "googleurl/src/gurl.h" + +class SkBitmap; + +// ExtensionSidebarDefaults encapsulates the default parameters of a sidebar, +// as defined in the extension manifest. +class ExtensionSidebarDefaults { + public: + // Default title, stores manifest default_title key value. + void set_default_title(const string16& title) { + default_title_ = title; + } + const string16& default_title() const { return default_title_; } + + // Default icon path, stores manifest default_icon key value. + void set_default_icon_path(const std::string& path) { + default_icon_path_ = path; + } + const std::string& default_icon_path() const { + return default_icon_path_; + } + + // Default sidebar url, resolved and verified against extension permissions. + void set_default_url(const GURL& url) { + default_url_ = url; + } + const GURL& default_url() const { + return default_url_; + } + + private: + string16 default_title_; + std::string default_icon_path_; + GURL default_url_; +}; + +#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_SIDEBAR_DEFAULTS_H_ diff --git a/chrome/common/extensions/extension_sidebar_utils.cc b/chrome/common/extensions/extension_sidebar_utils.cc new file mode 100644 index 0000000..d2abfa9 --- /dev/null +++ b/chrome/common/extensions/extension_sidebar_utils.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2010 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/common/extensions/extension_sidebar_utils.h" + +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_error_utils.h" +#include "chrome/common/url_constants.h" +#include "googleurl/src/gurl.h" + +namespace { + +// Errors. +const char kInvalidUrlError[] = "Invalid url: \"*\"."; + +bool CanUseHost(const Extension* extension, + const GURL& url, + std::string* error) { + if (extension->HasHostPermission(url)) + return true; + + *error = ExtensionErrorUtils::FormatErrorMessage( + extension_manifest_errors::kCannotAccessPage, url.spec()); + return false; +} + +} // namespace + +namespace extension_sidebar_utils { + +std::string GetExtensionIdByContentId(const std::string& content_id) { + // At the moment, content_id == extension_id. + return content_id; +} + +GURL ResolveAndVerifyUrl(const std::string& url_string, + const Extension* extension, + std::string* error) { + // Resolve possibly relative URL. + GURL url(url_string); + if (!url.is_valid()) + url = extension->GetResourceURL(url_string); + + if (!url.is_valid()) { + *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidUrlError, + url_string); + return GURL(); + } + if (!url.SchemeIs(chrome::kExtensionScheme) && + !CanUseHost(extension, url, error)) { + return GURL(); + } + // Disallow requests outside of the requesting extension view's extension. + if (url.SchemeIs(chrome::kExtensionScheme)) { + std::string extension_id(url.host()); + if (extension_id != extension->id()) + return GURL(); + } + + return url; +} + +} // namespace extension_sidebar_utils diff --git a/chrome/common/extensions/extension_sidebar_utils.h b/chrome/common/extensions/extension_sidebar_utils.h new file mode 100644 index 0000000..08a0ef4 --- /dev/null +++ b/chrome/common/extensions/extension_sidebar_utils.h @@ -0,0 +1,29 @@ +// Copyright (c) 2010 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_COMMON_EXTENSIONS_EXTENSION_SIDEBAR_UTILS_H_ +#define CHROME_COMMON_EXTENSIONS_EXTENSION_SIDEBAR_UTILS_H_ +#pragma once + +#include <string> + +class Extension; +class GURL; + +namespace extension_sidebar_utils { + +// Returns id of an extension owning a sidebar identified by |content_id|. +std::string GetExtensionIdByContentId(const std::string& content_id); + +// Resolves |url_string| relative to |extension|'s url and verifies it +// against |extension|'s host permissions. +// In case of any problem, returns an empty invalid GURL and |error| receives +// the corresponding error message. +GURL ResolveAndVerifyUrl(const std::string& url_string, + const Extension* extension, + std::string* error); + +} // namespace extension_sidebar_utils + +#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_SIDEBAR_UTILS_H_ diff --git a/chrome/test/data/extensions/api_test/sidebar/manifest.json b/chrome/test/data/extensions/api_test/sidebar/manifest.json index 4a55643..dbbce99 100644 --- a/chrome/test/data/extensions/api_test/sidebar/manifest.json +++ b/chrome/test/data/extensions/api_test/sidebar/manifest.json @@ -3,5 +3,6 @@ "version": "0.1", "description": "end-to-end browser test for chrome.experimental.sidebar API", "background_page": "test.html", + "sidebar": {}, "permissions": ["tabs", "experimental"] } diff --git a/chrome/test/data/extensions/manifest_tests/sidebar.json b/chrome/test/data/extensions/manifest_tests/sidebar.json new file mode 100644 index 0000000..a1ed809 --- /dev/null +++ b/chrome/test/data/extensions/manifest_tests/sidebar.json @@ -0,0 +1,12 @@ +{ + "name": "test", + "version": "1", + "sidebar": { + "default_url": "sidebar.html", + "default_title": "Default title", + "default_icon": "icon.png" + }, + "permissions": [ + "experimental" + ] +} diff --git a/chrome/test/data/extensions/manifest_tests/sidebar_external_url.json b/chrome/test/data/extensions/manifest_tests/sidebar_external_url.json new file mode 100644 index 0000000..c6e1cdb --- /dev/null +++ b/chrome/test/data/extensions/manifest_tests/sidebar_external_url.json @@ -0,0 +1,10 @@ +{ + "name": "test", + "version": "1", + "sidebar": { + "default_url": "http://sidebar.url/sidebar.html" + }, + "permissions": [ + "experimental", "http://sidebar.url/" + ] +} diff --git a/chrome/test/data/extensions/manifest_tests/sidebar_icon_empty.json b/chrome/test/data/extensions/manifest_tests/sidebar_icon_empty.json new file mode 100644 index 0000000..4606f38 --- /dev/null +++ b/chrome/test/data/extensions/manifest_tests/sidebar_icon_empty.json @@ -0,0 +1,11 @@ +{ + "name": "test", + "version": "1", + "sidebar": { + "default_title": "Default title", + "default_icon": "" + }, + "permissions": [ + "experimental" + ] +} diff --git a/chrome/test/data/extensions/manifest_tests/sidebar_icon_invalid_type.json b/chrome/test/data/extensions/manifest_tests/sidebar_icon_invalid_type.json new file mode 100644 index 0000000..e88d09c --- /dev/null +++ b/chrome/test/data/extensions/manifest_tests/sidebar_icon_invalid_type.json @@ -0,0 +1,11 @@ +{ + "name": "test", + "version": "1", + "sidebar": { + "default_title": "Default title", + "default_icon": 42 + }, + "permissions": [ + "experimental" + ] +} diff --git a/chrome/test/data/extensions/manifest_tests/sidebar_no_permissions.json b/chrome/test/data/extensions/manifest_tests/sidebar_no_permissions.json new file mode 100644 index 0000000..f189352 --- /dev/null +++ b/chrome/test/data/extensions/manifest_tests/sidebar_no_permissions.json @@ -0,0 +1,9 @@ +{ + "name": "test", + "version": "1", + "sidebar": { + "default_title": "Default title", + "default_icon": "icon.png" + }, + "permissions": [] +} diff --git a/chrome/test/data/extensions/manifest_tests/sidebar_title_invalid_type.json b/chrome/test/data/extensions/manifest_tests/sidebar_title_invalid_type.json new file mode 100644 index 0000000..a16f342 --- /dev/null +++ b/chrome/test/data/extensions/manifest_tests/sidebar_title_invalid_type.json @@ -0,0 +1,11 @@ +{ + "name": "test", + "version": "1", + "sidebar": { + "default_title": 42, + "default_icon": "icon.png" + }, + "permissions": [ + "experimental" + ] +} diff --git a/chrome/test/data/extensions/manifest_tests/sidebar_url_invalid.json b/chrome/test/data/extensions/manifest_tests/sidebar_url_invalid.json new file mode 100644 index 0000000..be023db --- /dev/null +++ b/chrome/test/data/extensions/manifest_tests/sidebar_url_invalid.json @@ -0,0 +1,12 @@ +{ + "name": "test", + "version": "1", + "sidebar": { + "default_url": "", + "default_title": "Default title", + "default_icon": "icon.png" + }, + "permissions": [ + "experimental" + ] +} diff --git a/chrome/test/data/extensions/manifest_tests/sidebar_url_invalid_type.json b/chrome/test/data/extensions/manifest_tests/sidebar_url_invalid_type.json new file mode 100644 index 0000000..d212c90 --- /dev/null +++ b/chrome/test/data/extensions/manifest_tests/sidebar_url_invalid_type.json @@ -0,0 +1,12 @@ +{ + "name": "test", + "version": "1", + "sidebar": { + "default_url": 42, + "default_title": "Default title", + "default_icon": "icon.png" + }, + "permissions": [ + "experimental" + ] +} diff --git a/chrome/test/data/extensions/manifest_tests/sidebar_url_no_permissions.json b/chrome/test/data/extensions/manifest_tests/sidebar_url_no_permissions.json new file mode 100644 index 0000000..b4c145f --- /dev/null +++ b/chrome/test/data/extensions/manifest_tests/sidebar_url_no_permissions.json @@ -0,0 +1,12 @@ +{ + "name": "test", + "version": "1", + "sidebar": { + "default_url": "http://default.url", + "default_title": "Default title", + "default_icon": "icon.png" + }, + "permissions": [ + "experimental" + ] +} diff --git a/chrome/test/data/sidebar/manifest.json b/chrome/test/data/sidebar/manifest.json new file mode 100644 index 0000000..2031ef6 --- /dev/null +++ b/chrome/test/data/sidebar/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "SidebarTest", + "version": "0.1", + "description": "interactive_ui_tests, SidebarTest.*", + "sidebar": {}, + "permissions": ["experimental"] +} |