diff options
author | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-24 04:35:08 +0000 |
---|---|---|
committer | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-24 04:35:08 +0000 |
commit | 92c6f9b9eac950c487f580709e3aab03843d15bc (patch) | |
tree | 4f25ceede36cf3655de577a48281910752f67262 /chrome | |
parent | 20ee7a74a7587986ed5db9b13aac2a78a5f0bdfc (diff) | |
download | chromium_src-92c6f9b9eac950c487f580709e3aab03843d15bc.zip chromium_src-92c6f9b9eac950c487f580709e3aab03843d15bc.tar.gz chromium_src-92c6f9b9eac950c487f580709e3aab03843d15bc.tar.bz2 |
Refactor BrowserActions, and add support for
tab-specific state.
Future changelists will move Page Actions over to
ExtensionAction2, then replace ExtensionAction and
ExtensionActionState with ExtensionAction2.
Also, fix a bug in setIcon({path:...}) where it
would work only the first time.
BUG=24669,24472
Review URL: http://codereview.chromium.org/306044
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@29997 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
29 files changed, 952 insertions, 355 deletions
diff --git a/chrome/browser/extensions/browser_action_apitest.cc b/chrome/browser/extensions/browser_action_apitest.cc index 13b73d8..3e47ad1 100644 --- a/chrome/browser/extensions/browser_action_apitest.cc +++ b/chrome/browser/extensions/browser_action_apitest.cc @@ -13,7 +13,7 @@ #include "chrome/browser/views/browser_actions_container.h" #include "chrome/browser/views/extensions/extension_popup.h" #include "chrome/browser/views/toolbar_view.h" -#include "chrome/common/extensions/extension_action.h" +#include "chrome/common/extensions/extension_action2.h" #include "chrome/test/ui_test_utils.h" IN_PROC_BROWSER_TEST_F(ExtensionApiTest, BrowserAction) { @@ -35,20 +35,19 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, BrowserAction) { ASSERT_TRUE(catcher.GetNextResult()); // Test that we received the changes. - ExtensionActionState* action_state = extension->browser_action_state(); - ASSERT_EQ("Modified", action_state->title()); - ASSERT_EQ("badge", action_state->badge_text()); + ExtensionAction2* action = extension->browser_action(); + ASSERT_EQ("Modified", action->GetTitle(ExtensionAction2::kDefaultTabId)); + ASSERT_EQ("badge", action->GetBadgeText(ExtensionAction2::kDefaultTabId)); ASSERT_EQ(SkColorSetARGB(255, 255, 255, 255), - action_state->badge_background_color()); + action->GetBadgeBackgroundColor(ExtensionAction2::kDefaultTabId)); // Simulate the browser action being clicked. ui_test_utils::NavigateToURL(browser(), GURL("http://localhost:1337/files/extensions/test_file.txt")); - ExtensionAction* browser_action = service->GetBrowserActions(false)[0]; int window_id = ExtensionTabUtil::GetWindowId(browser()); ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( - browser()->profile(), browser_action->extension_id(), browser()); + browser()->profile(), action->extension_id(), browser()); // Verify the command worked. TabContents* tab = browser()->GetSelectedTabContents(); @@ -97,6 +96,50 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, DynamicBrowserAction) { // TODO(aa): Would be nice here to actually compare that the pixels change. } +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, TabSpecificBrowserActionState) { + ASSERT_TRUE(RunExtensionTest("browser_action_tab_specific_state")) + << message_; + + // Test that there is a browser action in the toolbar and that it has no icon. + BrowserActionsContainer* browser_actions = + browser()->window()->GetBrowserWindowTesting()->GetToolbarView()-> + browser_actions(); + ASSERT_EQ(1, browser_actions->num_browser_actions()); + ASSERT_FALSE(browser_actions->GetBrowserActionViewAt(0)->button()->icon() + .empty()); + + // Execute the action, its title should change + std::wstring text; + ResultCatcher catcher; + browser_actions->TestExecuteBrowserAction(0); + ASSERT_TRUE(catcher.GetNextResult()); + ASSERT_TRUE( + browser_actions->GetBrowserActionViewAt(0)->button()->GetTooltipText( + 0, 0, &text)); + ASSERT_EQ(L"Showing icon 2", text); + + // open a new tab, the title should go back + browser()->NewTab(); + ASSERT_TRUE( + browser_actions->GetBrowserActionViewAt(0)->button()->GetTooltipText( + 0, 0, &text)); + ASSERT_EQ(L"hi!", text); + + // go back to first tab, changed title should reappear + browser()->SelectTabContentsAt(0, true); + ASSERT_TRUE( + browser_actions->GetBrowserActionViewAt(0)->button()->GetTooltipText( + 0, 0, &text)); + ASSERT_EQ(L"Showing icon 2", text); + + // reload that tab, default title should come back + ui_test_utils::NavigateToURL(browser(), GURL("about:blank")); + ASSERT_TRUE( + browser_actions->GetBrowserActionViewAt(0)->button()->GetTooltipText( + 0, 0, &text)); + ASSERT_EQ(L"hi!", text); +} + IN_PROC_BROWSER_TEST_F(ExtensionApiTest, BrowserActionPopup) { ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("popup"))); diff --git a/chrome/browser/extensions/extension_browser_actions_api.cc b/chrome/browser/extensions/extension_browser_actions_api.cc index 6c83ee0..92e0951 100644 --- a/chrome/browser/extensions/extension_browser_actions_api.cc +++ b/chrome/browser/extensions/extension_browser_actions_api.cc @@ -17,97 +17,59 @@ const char kIconIndexOutOfBounds[] = "Browser action icon index out of bounds."; } -bool BrowserActionSetIconFunction::RunImpl() { +bool BrowserActionFunction::RunImpl() { EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_DICTIONARY)); - const DictionaryValue* args = static_cast<const DictionaryValue*>(args_); + details_ = static_cast<DictionaryValue*>(args_); + + if (details_->HasKey(L"tabId")) + EXTENSION_FUNCTION_VALIDATE(details_->GetInteger(L"tabId", &tab_id_)); Extension* extension = dispatcher()->GetExtension(); - if (!extension->browser_action()) { + browser_action_ = extension->browser_action(); + if (!browser_action_) { error_ = kNoBrowserActionError; return false; } - // setIcon can take a variant argument: either a canvas ImageData, or an - // icon index. - BinaryValue* binary; - int icon_index; - if (args->GetBinary(L"imageData", &binary)) { - IPC::Message bitmap_pickle(binary->GetBuffer(), binary->GetSize()); - void* iter = NULL; - scoped_ptr<SkBitmap> bitmap(new SkBitmap); - EXTENSION_FUNCTION_VALIDATE( - IPC::ReadParam(&bitmap_pickle, &iter, bitmap.get())); - extension->browser_action_state()->set_icon(bitmap.release()); - } else if (args->GetInteger(L"iconIndex", &icon_index)) { - if (icon_index < 0 || static_cast<size_t>(icon_index) >= - - extension->browser_action()->icon_paths().size()) { - error_ = kIconIndexOutOfBounds; - return false; - } - extension->browser_action_state()->set_icon_index(icon_index); - extension->browser_action_state()->set_icon(NULL); - } else { - EXTENSION_FUNCTION_VALIDATE(false); - } + if (!RunBrowserAction()) + return false; NotificationService::current()->Notify( NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, - Source<ExtensionAction>(extension->browser_action()), - Details<ExtensionActionState>(extension->browser_action_state())); + Source<ExtensionAction2>(browser_action_), + NotificationService::NoDetails()); return true; } -bool BrowserActionSetTitleFunction::RunImpl() { - EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_DICTIONARY)); - DictionaryValue* details = static_cast<DictionaryValue*>(args_); +bool BrowserActionSetIconFunction::RunBrowserAction() { + BinaryValue* binary = NULL; + EXTENSION_FUNCTION_VALIDATE(details_->GetBinary(L"imageData", &binary)); + IPC::Message bitmap_pickle(binary->GetBuffer(), binary->GetSize()); + void* iter = NULL; + SkBitmap bitmap; + EXTENSION_FUNCTION_VALIDATE( + IPC::ReadParam(&bitmap_pickle, &iter, &bitmap)); + browser_action_->SetIcon(tab_id_, bitmap); + return true; +} +bool BrowserActionSetTitleFunction::RunBrowserAction() { std::string title; - EXTENSION_FUNCTION_VALIDATE(details->GetString(L"title", &title)); - - Extension* extension = dispatcher()->GetExtension(); - if (!extension->browser_action()) { - error_ = kNoBrowserActionError; - return false; - } - - extension->browser_action_state()->set_title(title); - - NotificationService::current()->Notify( - NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, - Source<ExtensionAction>(extension->browser_action()), - Details<ExtensionActionState>(extension->browser_action_state())); + EXTENSION_FUNCTION_VALIDATE(details_->GetString(L"title", &title)); + browser_action_->SetTitle(tab_id_, title); return true; } -bool BrowserActionSetBadgeTextFunction::RunImpl() { - EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_DICTIONARY)); - DictionaryValue* details = static_cast<DictionaryValue*>(args_); - +bool BrowserActionSetBadgeTextFunction::RunBrowserAction() { std::string badge_text; - EXTENSION_FUNCTION_VALIDATE(details->GetString(L"text", &badge_text)); - - Extension* extension = dispatcher()->GetExtension(); - if (!extension->browser_action()) { - error_ = kNoBrowserActionError; - return false; - } - - extension->browser_action_state()->set_badge_text(badge_text); - - NotificationService::current()->Notify( - NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, - Source<ExtensionAction>(extension->browser_action()), - Details<ExtensionActionState>(extension->browser_action_state())); + EXTENSION_FUNCTION_VALIDATE(details_->GetString(L"text", &badge_text)); + browser_action_->SetBadgeText(tab_id_, badge_text); return true; } -bool BrowserActionSetBadgeBackgroundColorFunction::RunImpl() { - EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_DICTIONARY)); - DictionaryValue* details = static_cast<DictionaryValue*>(args_); - +bool BrowserActionSetBadgeBackgroundColorFunction::RunBrowserAction() { ListValue* list = NULL; - EXTENSION_FUNCTION_VALIDATE(details->GetList(L"color", &list)); + EXTENSION_FUNCTION_VALIDATE(details_->GetList(L"color", &list)); EXTENSION_FUNCTION_VALIDATE(list->GetSize() == 4); int color_array[4] = {0}; @@ -117,18 +79,7 @@ bool BrowserActionSetBadgeBackgroundColorFunction::RunImpl() { SkColor color = SkColorSetARGB(color_array[3], color_array[0], color_array[1], color_array[2]); + browser_action_->SetBadgeBackgroundColor(tab_id_, color); - Extension* extension = dispatcher()->GetExtension(); - if (!extension->browser_action()) { - error_ = kNoBrowserActionError; - return false; - } - - extension->browser_action_state()->set_badge_background_color(color); - - NotificationService::current()->Notify( - NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, - Source<ExtensionAction>(extension->browser_action()), - Details<ExtensionActionState>(extension->browser_action_state())); return true; } diff --git a/chrome/browser/extensions/extension_browser_actions_api.h b/chrome/browser/extensions/extension_browser_actions_api.h index 8d92b8a..3b6d768 100644 --- a/chrome/browser/extensions/extension_browser_actions_api.h +++ b/chrome/browser/extensions/extension_browser_actions_api.h @@ -6,25 +6,47 @@ #define CHROME_BROWSER_EXTENSIONS_EXTENSION_BROWSER_ACTIONS_API_H_ #include "chrome/browser/extensions/extension_function.h" +#include "chrome/common/extensions/extension_action2.h" -class BrowserActionSetIconFunction : public SyncExtensionFunction { +class ExtensionAction; +class ExtensionActionState; + +class BrowserActionFunction : public SyncExtensionFunction { + protected: + BrowserActionFunction() : tab_id_(ExtensionAction2::kDefaultTabId) {} virtual bool RunImpl(); + virtual bool RunBrowserAction() = 0; + + // All the browser action APIs take a single argument called details that is + // a dictionary. + DictionaryValue* details_; + + // The tab id the browser action function should apply to, if any, or + // kDefaultTabId if none was specified. + int tab_id_; + + // The browser action for the current extension. + ExtensionAction2* browser_action_; +}; + +class BrowserActionSetIconFunction : public BrowserActionFunction { + virtual bool RunBrowserAction(); DECLARE_EXTENSION_FUNCTION_NAME("browserAction.setIcon") }; -class BrowserActionSetTitleFunction : public SyncExtensionFunction { - virtual bool RunImpl(); +class BrowserActionSetTitleFunction : public BrowserActionFunction { + virtual bool RunBrowserAction(); DECLARE_EXTENSION_FUNCTION_NAME("browserAction.setTitle") }; -class BrowserActionSetBadgeTextFunction : public SyncExtensionFunction { - virtual bool RunImpl(); +class BrowserActionSetBadgeTextFunction : public BrowserActionFunction { + virtual bool RunBrowserAction(); DECLARE_EXTENSION_FUNCTION_NAME("browserAction.setBadgeText") }; class BrowserActionSetBadgeBackgroundColorFunction - : public SyncExtensionFunction { - virtual bool RunImpl(); + : public BrowserActionFunction { + virtual bool RunBrowserAction(); DECLARE_EXTENSION_FUNCTION_NAME("browserAction.setBadgeBackgroundColor") }; diff --git a/chrome/browser/extensions/extension_file_util.cc b/chrome/browser/extensions/extension_file_util.cc index 23bd085..60d4981 100644 --- a/chrome/browser/extensions/extension_file_util.cc +++ b/chrome/browser/extensions/extension_file_util.cc @@ -242,11 +242,11 @@ bool ValidateExtension(Extension* extension, std::string* error) { } // Validate icon location for browser actions. - const ExtensionAction* browser_action = extension->browser_action(); + ExtensionAction2* browser_action = extension->browser_action(); if (browser_action) { - const std::vector<std::string>& icon_paths = browser_action->icon_paths(); - for (std::vector<std::string>::const_iterator iter = icon_paths.begin(); - iter != icon_paths.end(); ++iter) { + std::vector<std::string>* icon_paths = browser_action->icon_paths(); + for (std::vector<std::string>::iterator iter = icon_paths->begin(); + iter != icon_paths->end(); ++iter) { if (extension->GetResource(*iter).GetFilePath().empty()) { *error = StringPrintf("Could not load icon '%s' for browser action.", iter->c_str()); diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 5b87bd1..354d7d2 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -141,12 +141,16 @@ void ExtensionsService::Init() { } std::vector<ExtensionAction*> ExtensionsService::GetPageActions() const { - return GetExtensionActions(ExtensionAction::PAGE_ACTION, true); -} + std::vector<ExtensionAction*> result; + + // TODO(finnur): Sort the icons in some meaningful way. + for (ExtensionList::const_iterator iter = extensions_.begin(); + iter != extensions_.end(); ++iter) { + if ((*iter)->page_action()) + result.push_back((*iter)->page_action()); + } -std::vector<ExtensionAction*> ExtensionsService::GetBrowserActions( - bool include_popups) const { - return GetExtensionActions(ExtensionAction::BROWSER_ACTION, include_popups); + return result; } void ExtensionsService::InstallExtension(const FilePath& extension_path) { @@ -366,29 +370,6 @@ void ExtensionsService::NotifyExtensionUnloaded(Extension* extension) { } } -std::vector<ExtensionAction*> ExtensionsService::GetExtensionActions( - ExtensionAction::ExtensionActionType action_type, - bool include_popups) const { - std::vector<ExtensionAction*> result; - - // TODO(finnur): Sort the icons in some meaningful way. - for (ExtensionList::const_iterator iter = extensions_.begin(); - iter != extensions_.end(); ++iter) { - if (action_type == ExtensionAction::PAGE_ACTION) { - ExtensionAction* page_action = (*iter)->page_action(); - if (page_action && (include_popups || !page_action->is_popup())) { - result.push_back(page_action); - } - } else { - ExtensionAction* browser_action = (*iter)->browser_action(); - if (browser_action && (include_popups || !browser_action->is_popup())) - result.push_back(browser_action); - } - } - - return result; -} - void ExtensionsService::UpdateExtensionBlacklist( const std::vector<std::string>& blacklist) { // Use this set to indicate if an extension in the blacklist has been used. diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index 4235c22..0dad4cd 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -106,10 +106,6 @@ class ExtensionsService // they belong to. std::vector<ExtensionAction*> GetPageActions() const; - // Retrieves a vector of all browser actions, irrespective of which extension - // they belong to. - std::vector<ExtensionAction*> GetBrowserActions(bool include_popups) const; - // Install the extension file at |extension_path|. Will install as an // update if an older version is already installed. // For fresh installs, this method also causes the extension to be @@ -249,13 +245,6 @@ class ExtensionsService // Handles sending notification that |extension| was unloaded. void NotifyExtensionUnloaded(Extension* extension); - // Retrieves a vector of all page actions or browser actions, irrespective of - // which extension they belong to. If |include_popups| is false, actions that - // are popups are excluded. - std::vector<ExtensionAction*> GetExtensionActions( - ExtensionAction::ExtensionActionType action_type, - bool include_popups) const; - // The profile this ExtensionsService is part of. Profile* profile_; diff --git a/chrome/browser/gtk/browser_actions_toolbar_gtk.cc b/chrome/browser/gtk/browser_actions_toolbar_gtk.cc index b30d053..b137203 100644 --- a/chrome/browser/gtk/browser_actions_toolbar_gtk.cc +++ b/chrome/browser/gtk/browser_actions_toolbar_gtk.cc @@ -17,7 +17,9 @@ #include "chrome/browser/gtk/gtk_chrome_button.h" #include "chrome/browser/gtk/gtk_theme_provider.h" #include "chrome/browser/profile.h" +#include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_action2.h" #include "chrome/common/gtk_util.h" #include "chrome/common/notification_details.h" #include "chrome/common/notification_service.h" @@ -35,28 +37,30 @@ static const int kBrowserActionButtonPadding = 3; class BrowserActionButton : public NotificationObserver, public ImageLoadingTracker::Observer { public: - BrowserActionButton(Browser* browser, Extension* extension) - : browser_(browser), + BrowserActionButton(BrowserActionsToolbarGtk* toolbar, + Extension* extension) + : toolbar_(toolbar), extension_(extension), button_(gtk_chrome_button_new()), - gdk_icon_(NULL) { + tracker_(NULL), + tab_specific_icon_(NULL), + default_icon_(NULL) { DCHECK(extension_->browser_action()); gtk_widget_set_size_request(button_.get(), kButtonSize, kButtonSize); - browser_action_icons_.resize( - extension->browser_action()->icon_paths().size()); - tracker_ = new ImageLoadingTracker(this, browser_action_icons_.size()); - for (size_t i = 0; i < extension->browser_action()->icon_paths().size(); - ++i) { - tracker_->PostLoadImageTask( - extension->GetResource(extension->browser_action()->icon_paths()[i]), + UpdateState(); + + // The Browser Action API does not allow the default icon path to be + // changed at runtime, so we can load this now and cache it. + std::string path = extension_->browser_action()->GetDefaultIconPath(); + if (!path.empty()) { + tracker_ = new ImageLoadingTracker(this, 1); + tracker_->PostLoadImageTask(extension_->GetResource(path), gfx::Size(Extension::kBrowserActionIconMaxSize, Extension::kBrowserActionIconMaxSize)); } - OnStateUpdated(); - // We need to hook up extension popups here. http://crbug.com/23897 g_signal_connect(button_.get(), "clicked", G_CALLBACK(OnButtonClicked), this); @@ -64,7 +68,7 @@ class BrowserActionButton : public NotificationObserver, G_CALLBACK(OnExposeEvent), this); registrar_.Add(this, NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, - Source<ExtensionAction>(extension->browser_action())); + Source<ExtensionAction2>(extension->browser_action())); registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, NotificationService::AllSources()); @@ -72,11 +76,16 @@ class BrowserActionButton : public NotificationObserver, } ~BrowserActionButton() { - if (gdk_icon_) - g_object_unref(gdk_icon_); + if (tab_specific_icon_) + g_object_unref(tab_specific_icon_); + + if (default_icon_) + g_object_unref(default_icon_); button_.Destroy(); - tracker_->StopTrackingImageLoad(); + + if (tracker_) + tracker_->StopTrackingImageLoad(); } GtkWidget* widget() { return button_.get(); } @@ -85,7 +94,7 @@ class BrowserActionButton : public NotificationObserver, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED) - OnStateUpdated(); + UpdateState(); else if (type == NotificationType::BROWSER_THEME_CHANGED) OnThemeChanged(); else @@ -94,71 +103,79 @@ class BrowserActionButton : public NotificationObserver, // ImageLoadingTracker::Observer implementation. void OnImageLoaded(SkBitmap* image, size_t index) { - DCHECK(index < browser_action_icons_.size()); - browser_action_icons_[index] = image ? *image : SkBitmap(); - - OnStateUpdated(); + default_icon_ = gfx::GdkPixbufFromSkBitmap(image); + UpdateState(); } - private: - // Called when the tooltip has changed or an image has loaded. - void OnStateUpdated() { + // Updates the button based on the latest state from the associated + // browser action. + void UpdateState() { + int tab_id = toolbar_->GetCurrentTabId(); + if (tab_id < 0) + return; + gtk_widget_set_tooltip_text(button_.get(), - extension_->browser_action_state()->title().c_str()); - - SkBitmap* image = extension_->browser_action_state()->icon(); - if (!image) { - if (static_cast<size_t>( - extension_->browser_action_state()->icon_index()) < - browser_action_icons_.size()) { - image = &browser_action_icons_[ - extension_->browser_action_state()->icon_index()]; - } + extension_->browser_action()->GetTitle(tab_id).c_str()); + + SkBitmap image = extension_->browser_action()->GetIcon(tab_id); + if (!image.isNull()) { + GdkPixbuf* previous_gdk_icon = tab_specific_icon_; + tab_specific_icon_ = gfx::GdkPixbufFromSkBitmap(&image); + SetImage(tab_specific_icon_); + if (previous_gdk_icon) + g_object_unref(previous_gdk_icon); + } else if (default_icon_) { + SetImage(default_icon_); } + } - if (image && !image->empty()) { - GdkPixbuf* current_gdk_icon = gdk_icon_; - gdk_icon_ = gfx::GdkPixbufFromSkBitmap(image); - gtk_button_set_image(GTK_BUTTON(button_.get()), - gtk_image_new_from_pixbuf(gdk_icon_)); - if (current_gdk_icon) - g_object_unref(current_gdk_icon); - } + private: + void SetImage(GdkPixbuf* image) { + gtk_button_set_image(GTK_BUTTON(button_.get()), + gtk_image_new_from_pixbuf(image)); } void OnThemeChanged() { gtk_chrome_button_set_use_gtk_rendering(GTK_CHROME_BUTTON(button_.get()), - GtkThemeProvider::GetFrom(browser_->profile())->UseGtkTheme()); + GtkThemeProvider::GetFrom( + toolbar_->browser()->profile())->UseGtkTheme()); } static void OnButtonClicked(GtkWidget* widget, BrowserActionButton* action) { - if (action->extension_->browser_action()->is_popup()) { - ExtensionPopupGtk::Show( - action->extension_->browser_action()->popup_url(), - action->browser_, - gtk_util::GetWidgetRectRelativeToToplevel(widget)); - } else { - ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( - action->browser_->profile(), action->extension_->id(), - action->browser_); - } + if (action->extension_->browser_action()->has_popup()) { + ExtensionPopupGtk::Show( + action->extension_->browser_action()->popup_url(), + action->toolbar_->browser(), + gtk_util::GetWidgetRectRelativeToToplevel(widget)); + } else { + ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( + action->toolbar_->browser()->profile(), action->extension_->id(), + action->toolbar_->browser()); + } } static gboolean OnExposeEvent(GtkWidget* widget, GdkEventExpose* event, - BrowserActionButton* action) { - if (action->extension_->browser_action_state()->badge_text().empty()) + BrowserActionButton* button) { + int tab_id = button->toolbar_->GetCurrentTabId(); + if (tab_id < 0) + return FALSE; + + ExtensionAction2* action = button->extension_->browser_action(); + if (action->GetBadgeText(tab_id).empty()) return FALSE; gfx::CanvasPaint canvas(event, false); gfx::Rect bounding_rect(widget->allocation); - action->extension_->browser_action_state()->PaintBadge(&canvas, - bounding_rect); + ExtensionActionState::PaintBadge(&canvas, bounding_rect, + action->GetBadgeText(tab_id), + action->GetBadgeTextColor(tab_id), + action->GetBadgeBackgroundColor(tab_id)); return FALSE; } - // The Browser that executes a command when the button is pressed. - Browser* browser_; + // The toolbar containing this button. + BrowserActionsToolbarGtk* toolbar_; // The extension that contains this browser action. Extension* extension_; @@ -169,13 +186,11 @@ class BrowserActionButton : public NotificationObserver, // Loads the button's icons for us on the file thread. ImageLoadingTracker* tracker_; - // Icons for all the different states the button can be in. These will be - // empty while they are loading. - std::vector<SkBitmap> browser_action_icons_; + // If we are displaying a tab-specific icon, it will be here. + GdkPixbuf* tab_specific_icon_; - // SkBitmap must be converted to GdkPixbuf before assignment to the button. - // This stores the current icon while it is in use. - GdkPixbuf* gdk_icon_; + // If the browser action has a default icon, it will be here. + GdkPixbuf* default_icon_; NotificationRegistrar registrar_; }; @@ -199,6 +214,21 @@ BrowserActionsToolbarGtk::~BrowserActionsToolbarGtk() { hbox_.Destroy(); } +int BrowserActionsToolbarGtk::GetCurrentTabId() { + TabContents* selected_tab = browser_->GetSelectedTabContents(); + if (!selected_tab) + return -1; + + return selected_tab->controller().session_id().id(); +} + +void BrowserActionsToolbarGtk::Update() { + for (ExtensionButtonMap::iterator iter = extension_button_map_.begin(); + iter != extension_button_map_.end(); ++iter) { + iter->second->UpdateState(); + } +} + void BrowserActionsToolbarGtk::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { @@ -219,27 +249,21 @@ void BrowserActionsToolbarGtk::CreateAllButtons() { if (!extension_service) // The |extension_service| can be NULL in Incognito. return; - // Get all browser actions, including those with popups. - std::vector<ExtensionAction*> browser_actions = - extension_service->GetBrowserActions(true); - - for (size_t i = 0; i < browser_actions.size(); ++i) { + for (size_t i = 0; i < extension_service->extensions()->size(); ++i) { Extension* extension = extension_service->GetExtensionById( - browser_actions[i]->extension_id()); + extension_service->extensions()->at(i)->id()); CreateButtonForExtension(extension); } } void BrowserActionsToolbarGtk::CreateButtonForExtension(Extension* extension) { - // Only show extensions with browser actions and that have an icon. - if (!extension->browser_action() || - extension->browser_action()->icon_paths().empty()) { + // Only show extensions with browser actions. + if (!extension->browser_action()) return; - } RemoveButtonForExtension(extension); linked_ptr<BrowserActionButton> button( - new BrowserActionButton(browser_, extension)); + new BrowserActionButton(this, extension)); gtk_box_pack_end(GTK_BOX(hbox_.get()), button->widget(), FALSE, FALSE, 0); gtk_widget_show(button->widget()); extension_button_map_[extension->id()] = button; diff --git a/chrome/browser/gtk/browser_actions_toolbar_gtk.h b/chrome/browser/gtk/browser_actions_toolbar_gtk.h index 4210889..015b5c9 100644 --- a/chrome/browser/gtk/browser_actions_toolbar_gtk.h +++ b/chrome/browser/gtk/browser_actions_toolbar_gtk.h @@ -29,6 +29,14 @@ class BrowserActionsToolbarGtk : public NotificationObserver { int button_count() { return extension_button_map_.size(); } + Browser* browser() { return browser_; } + + // Returns the currently selected tab ID, or -1 if there is none. + int GetCurrentTabId(); + + // Update the display of all buttons. + void Update(); + // NotificationObserver implementation. void Observe(NotificationType type, const NotificationSource& source, @@ -63,7 +71,9 @@ class BrowserActionsToolbarGtk : public NotificationObserver { // Map from extension ID to BrowserActionButton, which is a wrapper for // a chrome button and related functionality. There should be one entry // for every extension that has a browser action. - std::map<std::string, linked_ptr<BrowserActionButton> > extension_button_map_; + typedef std::map<std::string, linked_ptr<BrowserActionButton> > + ExtensionButtonMap; + ExtensionButtonMap extension_button_map_; DISALLOW_COPY_AND_ASSIGN(BrowserActionsToolbarGtk); }; diff --git a/chrome/browser/gtk/browser_toolbar_gtk.cc b/chrome/browser/gtk/browser_toolbar_gtk.cc index fa5d34e..59ebd22 100644 --- a/chrome/browser/gtk/browser_toolbar_gtk.cc +++ b/chrome/browser/gtk/browser_toolbar_gtk.cc @@ -406,6 +406,9 @@ void BrowserToolbarGtk::SetProfile(Profile* profile) { void BrowserToolbarGtk::UpdateTabContents(TabContents* contents, bool should_restore_state) { location_bar_->Update(should_restore_state ? contents : NULL); + + if (actions_toolbar_.get()) + actions_toolbar_->Update(); } gfx::Rect BrowserToolbarGtk::GetLocationStackBounds() const { diff --git a/chrome/browser/gtk/location_bar_view_gtk.cc b/chrome/browser/gtk/location_bar_view_gtk.cc index 4027d4e..8aec081 100644 --- a/chrome/browser/gtk/location_bar_view_gtk.cc +++ b/chrome/browser/gtk/location_bar_view_gtk.cc @@ -825,6 +825,9 @@ gboolean LocationBarViewGtk::PageActionViewGtk::OnExposeEvent( gfx::CanvasPaint canvas(event, false); gfx::Rect bounding_rect(widget->allocation); - state->PaintBadge(&canvas, bounding_rect); + ExtensionActionState::PaintBadge(&canvas, bounding_rect, + state->badge_text(), + state->badge_text_color(), + state->badge_background_color()); return FALSE; } diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc index e33df336..657bee4 100644 --- a/chrome/browser/tab_contents/tab_contents.cc +++ b/chrome/browser/tab_contents/tab_contents.cc @@ -24,6 +24,7 @@ #include "chrome/browser/download/download_manager.h" #include "chrome/browser/download/download_request_manager.h" #include "chrome/browser/external_protocol_handler.h" +#include "chrome/browser/extensions/extensions_service.h" #include "chrome/browser/favicon_service.h" #include "chrome/browser/form_field_history_manager.h" #include "chrome/browser/gears_integration.h" @@ -1410,12 +1411,27 @@ void TabContents::DidNavigateMainFramePostCommit( fav_icon_helper_.FetchFavIcon(details.entry->url()); // Disable all page actions, unless this is an in-page navigation. - if (!page_actions_.empty()) { - url_canon::Replacements<char> replacements; - replacements.ClearRef(); - if (params.url.ReplaceComponents(replacements) != - params.referrer.ReplaceComponents(replacements)) { + url_canon::Replacements<char> replacements; + replacements.ClearRef(); + if (params.url.ReplaceComponents(replacements) != + params.referrer.ReplaceComponents(replacements)) { + if (!page_actions_.empty()) page_actions_.clear(); + + ExtensionsService* service = profile()->GetExtensionsService(); + if (service) { + for (size_t i = 0; i < service->extensions()->size(); ++i) { + ExtensionAction2* action = + service->extensions()->at(i)->browser_action(); + if (!action) + continue; + + action->ClearAllValuesForTab(controller().session_id().id()); + NotificationService::current()->Notify( + NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, + Source<ExtensionAction2>(action), + NotificationService::NoDetails()); + } } } diff --git a/chrome/browser/views/browser_actions_container.cc b/chrome/browser/views/browser_actions_container.cc index d21984b..f3f02f0 100644 --- a/chrome/browser/views/browser_actions_container.cc +++ b/chrome/browser/views/browser_actions_container.cc @@ -16,6 +16,7 @@ #include "chrome/browser/views/extensions/extension_popup.h" #include "chrome/browser/views/toolbar_view.h" #include "chrome/common/extensions/extension_action.h" +#include "chrome/common/extensions/extension_action2.h" #include "chrome/common/notification_source.h" #include "chrome/common/notification_type.h" #include "grit/app_resources.h" @@ -48,34 +49,20 @@ static const int kMinimumNumberOfVisibleBrowserActions = 2; //////////////////////////////////////////////////////////////////////////////// // BrowserActionButton -BrowserActionButton::BrowserActionButton( - ExtensionAction* browser_action, Extension* extension, - BrowserActionsContainer* panel) +BrowserActionButton::BrowserActionButton(Extension* extension, + BrowserActionsContainer* panel) : MenuButton(this, L"", NULL, false), - browser_action_(browser_action), - browser_action_state_(extension->browser_action_state()), + browser_action_(extension->browser_action()), + extension_(extension), tracker_(NULL), panel_(panel) { set_alignment(TextButton::ALIGN_CENTER); - // Load the images this view needs asynchronously on the file thread. We'll - // get a call back into OnImageLoaded if the image loads successfully. If not, - // the ImageView will have no image and will not appear in the browser chrome. - if (!browser_action->icon_paths().empty()) { - const std::vector<std::string>& icon_paths = browser_action->icon_paths(); - browser_action_icons_.resize(icon_paths.size()); - tracker_ = new ImageLoadingTracker(this, icon_paths.size()); - for (std::vector<std::string>::const_iterator iter = icon_paths.begin(); - iter != icon_paths.end(); ++iter) { - tracker_->PostLoadImageTask( - extension->GetResource(*iter), - gfx::Size(Extension::kBrowserActionIconMaxSize, - Extension::kBrowserActionIconMaxSize)); - } - } + // No UpdateState() here because View heirarchy not setup yet. Our parent + // should call UpdateState() after creation. registrar_.Add(this, NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, - Source<ExtensionAction>(browser_action_)); + Source<ExtensionAction2>(browser_action_)); } BrowserActionButton::~BrowserActionButton() { @@ -95,29 +82,39 @@ void BrowserActionButton::ButtonPressed( panel_->OnBrowserActionExecuted(this); } +void BrowserActionButton::LoadImage() { + // Load the default image from the browser action asynchronously on the file + // thread. We'll get a call back into OnImageLoaded if the image loads + // successfully. + std::string relative_path = browser_action()->GetDefaultIconPath(); + if (relative_path.empty()) + return; + + tracker_ = new ImageLoadingTracker(this, 1); + tracker_->PostLoadImageTask( + extension()->GetResource(relative_path), + gfx::Size(Extension::kBrowserActionIconMaxSize, + Extension::kBrowserActionIconMaxSize)); +} + void BrowserActionButton::OnImageLoaded(SkBitmap* image, size_t index) { - DCHECK(index < browser_action_icons_.size()); - browser_action_icons_[index] = image ? *image : SkBitmap(); - if (index == browser_action_icons_.size() - 1) { - OnStateUpdated(); - tracker_ = NULL; // The tracker object will delete itself when we return. - } + SetIcon(*image); + tracker_ = NULL; // The tracker object will delete itself when we return. + GetParent()->SchedulePaint(); } -void BrowserActionButton::OnStateUpdated() { - SkBitmap* image = browser_action_state_->icon(); - if (!image) { - if (static_cast<size_t>(browser_action_state_->icon_index()) < - browser_action_icons_.size()) { - image = &browser_action_icons_[browser_action_state_->icon_index()]; - } - } +void BrowserActionButton::UpdateState() { + int tab_id = panel_->GetCurrentTabId(); + if (tab_id < 0) + return; - if (image) - SetIcon(*image); + SkBitmap image = browser_action()->GetIcon(tab_id); + if (image.isNull()) + LoadImage(); + else + SetIcon(image); - SetTooltipText(ASCIIToWide(browser_action_state_->title())); - panel_->OnBrowserActionVisibilityChanged(); + SetTooltipText(ASCIIToWide(browser_action()->GetTitle(tab_id))); GetParent()->SchedulePaint(); } @@ -125,14 +122,14 @@ void BrowserActionButton::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED) { - OnStateUpdated(); + UpdateState(); } else { NOTREACHED() << L"Received unexpected notification"; } } bool BrowserActionButton::IsPopup() { - return browser_action_->is_popup(); + return browser_action_->has_popup(); } bool BrowserActionButton::Activate() { @@ -195,11 +192,12 @@ void BrowserActionButton::PopupDidHide() { //////////////////////////////////////////////////////////////////////////////// // BrowserActionView -BrowserActionView::BrowserActionView(ExtensionAction* browser_action, - Extension* extension, - BrowserActionsContainer* panel) { - button_ = new BrowserActionButton(browser_action, extension, panel); +BrowserActionView::BrowserActionView(Extension* extension, + BrowserActionsContainer* panel) + : panel_(panel) { + button_ = new BrowserActionButton(extension, panel); AddChildView(button_); + button_->UpdateState(); } void BrowserActionView::Layout() { @@ -208,8 +206,16 @@ void BrowserActionView::Layout() { void BrowserActionView::PaintChildren(gfx::Canvas* canvas) { View::PaintChildren(canvas); - button_->browser_action_state()->PaintBadge(canvas, - gfx::Rect(width(), height())); + ExtensionAction2* action = button()->browser_action(); + int tab_id = panel_->GetCurrentTabId(); + if (tab_id < 0) + return; + + ExtensionActionState::PaintBadge( + canvas, gfx::Rect(width(), height()), + action->GetBadgeText(tab_id), + action->GetBadgeTextColor(tab_id), + action->GetBadgeBackgroundColor(tab_id)); } @@ -224,6 +230,9 @@ BrowserActionsContainer::BrowserActionsContainer( popup_button_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)) { ExtensionsService* extension_service = profile->GetExtensionsService(); + if (!extension_service) // The |extension_service| can be NULL in Incognito. + return; + registrar_.Add(this, NotificationType::EXTENSION_LOADED, Source<ExtensionsService>(extension_service)); registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, @@ -233,7 +242,9 @@ BrowserActionsContainer::BrowserActionsContainer( registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE, Source<Profile>(profile_)); - RefreshBrowserActionViews(); + for (size_t i = 0; i < extension_service->extensions()->size(); ++i) + AddBrowserAction(extension_service->extensions()->at(i)); + SetID(VIEW_ID_BROWSER_ACTION_TOOLBAR); } @@ -242,26 +253,50 @@ BrowserActionsContainer::~BrowserActionsContainer() { DeleteBrowserActionViews(); } +int BrowserActionsContainer::GetCurrentTabId() { + TabContents* tab_contents = toolbar_->browser()->GetSelectedTabContents(); + if (!tab_contents) + return -1; + + return tab_contents->controller().session_id().id(); +} + void BrowserActionsContainer::RefreshBrowserActionViews() { - ExtensionsService* extension_service = profile_->GetExtensionsService(); - if (!extension_service) // The |extension_service| can be NULL in Incognito. + for (size_t i = 0; i < browser_action_views_.size(); ++i) + browser_action_views_[i]->button()->UpdateState(); +} + +void BrowserActionsContainer::AddBrowserAction(Extension* extension) { +#if defined(DEBUG) + for (size_t i = 0; i < browser_action_views_.size(); ++i) { + DCHECK(browser_action_views_[i]->button()->extension() != extension) << + "Asked to add a browser action view for an extension that already " + "exists."; + } +#endif + if (!extension->browser_action()) return; - // Get all browser actions, including those with popups. - std::vector<ExtensionAction*> browser_actions; - browser_actions = extension_service->GetBrowserActions(true); + BrowserActionView* view = new BrowserActionView(extension, this); + browser_action_views_.push_back(view); + AddChildView(view); +} - DeleteBrowserActionViews(); - for (size_t i = 0; i < browser_actions.size(); ++i) { - Extension* extension = extension_service->GetExtensionById( - browser_actions[i]->extension_id()); - DCHECK(extension); - - BrowserActionView* view = - new BrowserActionView(browser_actions[i], extension, this); - browser_action_views_.push_back(view); - AddChildView(view); +void BrowserActionsContainer::RemoveBrowserAction(Extension* extension) { + if (!extension->browser_action()) + return; + + for (std::vector<BrowserActionView*>::iterator iter = + browser_action_views_.begin(); iter != browser_action_views_.end(); + ++iter) { + if ((*iter)->button()->extension() == extension) { + RemoveChildView(*iter); + browser_action_views_.erase(iter); + return; + } } + + NOTREACHED() << "Asked to remove a browser action view that doesn't exist."; } void BrowserActionsContainer::DeleteBrowserActionViews() { @@ -306,7 +341,7 @@ void BrowserActionsContainer::TestExecuteBrowserAction(int index) { void BrowserActionsContainer::OnBrowserActionExecuted( BrowserActionButton* button) { - const ExtensionAction& browser_action = button->browser_action(); + ExtensionAction2* browser_action = button->browser_action(); // Popups just display. No notification to the extension. // TODO(erikkay): should there be? @@ -326,7 +361,7 @@ void BrowserActionsContainer::OnBrowserActionExecuted( gfx::Rect rect = button->bounds(); rect.set_x(origin.x()); rect.set_y(origin.y()); - popup_ = ExtensionPopup::Show(browser_action.popup_url(), + popup_ = ExtensionPopup::Show(browser_action->popup_url(), toolbar_->browser(), rect); popup_->set_delegate(this); @@ -337,7 +372,7 @@ void BrowserActionsContainer::OnBrowserActionExecuted( // Otherwise, we send the action to the extension. ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( - profile_, browser_action.extension_id(), toolbar_->browser()); + profile_, browser_action->extension_id(), toolbar_->browser()); } gfx::Size BrowserActionsContainer::GetPreferredSize() { @@ -367,20 +402,26 @@ void BrowserActionsContainer::Layout() { void BrowserActionsContainer::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { - if (type == NotificationType::EXTENSION_LOADED || - type == NotificationType::EXTENSION_UNLOADED || - type == NotificationType::EXTENSION_UNLOADED_DISABLED) { - RefreshBrowserActionViews(); - - // All these actions may change visibility of BrowserActions. - OnBrowserActionVisibilityChanged(); - } else if (type == NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE) { - if (Details<ExtensionHost>(popup_->host()) != details) - return; - - HidePopup(); - } else { - NOTREACHED() << L"Received unexpected notification"; + switch (type.value) { + case NotificationType::EXTENSION_LOADED: + AddBrowserAction(Details<Extension>(details).ptr()); + OnBrowserActionVisibilityChanged(); + break; + + case NotificationType::EXTENSION_UNLOADED: + case NotificationType::EXTENSION_UNLOADED_DISABLED: + RemoveBrowserAction(Details<Extension>(details).ptr()); + OnBrowserActionVisibilityChanged(); + break; + + case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE: + if (Details<ExtensionHost>(popup_->host()) != details) + return; + + HidePopup(); + + default: + NOTREACHED() << L"Unexpected notification"; } } diff --git a/chrome/browser/views/browser_actions_container.h b/chrome/browser/views/browser_actions_container.h index e365bcd..756de0a 100644 --- a/chrome/browser/views/browser_actions_container.h +++ b/chrome/browser/views/browser_actions_container.h @@ -17,8 +17,7 @@ class BrowserActionsContainer; class Extension; -class ExtensionAction; -class ExtensionActionState; +class ExtensionAction2; class ExtensionPopup; class Profile; class ToolbarView; @@ -27,20 +26,21 @@ class ToolbarView; // BrowserActionButton // The BrowserActionButton is a specialization of the MenuButton class. -// It acts on a ExtensionAction, in this case a BrowserAction and handles +// It acts on a ExtensionAction2, in this case a BrowserAction and handles // loading the image for the button asynchronously on the file thread to class BrowserActionButton : public views::MenuButton, public views::ButtonListener, public ImageLoadingTracker::Observer, public NotificationObserver { public: - BrowserActionButton(ExtensionAction* browser_action, - Extension* extension, - BrowserActionsContainer* panel); + BrowserActionButton(Extension* extension, BrowserActionsContainer* panel); ~BrowserActionButton(); - const ExtensionAction& browser_action() const { return *browser_action_; } - ExtensionActionState* browser_action_state() { return browser_action_state_; } + ExtensionAction2* browser_action() const { return browser_action_; } + Extension* extension() { return extension_; } + + // Called to update the display to match the browser action's state. + void UpdateState(); // Overriden from views::View. Return a 0-inset so the icon can draw all the // way to the edge of the view if it wants. @@ -76,15 +76,15 @@ class BrowserActionButton : public views::MenuButton, virtual void PopupDidHide(); private: - // Called to update the display to match the browser action's state. - void OnStateUpdated(); + // If the image from the browser action needs to be loaded, load it. + void LoadImage(); - // The browser action this view represents. The ExtensionAction is not owned + // The browser action this view represents. The ExtensionAction2 is not owned // by this class. - ExtensionAction* browser_action_; + ExtensionAction2* browser_action_; - // The state of our browser action. Not owned by this class. - ExtensionActionState* browser_action_state_; + // The extension associated with the browser action we're displaying. + Extension* extension_; // The icons representing different states for the browser action. std::vector<SkBitmap> browser_action_icons_; @@ -110,9 +110,7 @@ class BrowserActionButton : public views::MenuButton, class BrowserActionView : public views::View { public: - BrowserActionView(ExtensionAction* browser_action, Extension* extension, - BrowserActionsContainer* panel); - + BrowserActionView(Extension* extension, BrowserActionsContainer* panel); BrowserActionButton* button() { return button_; } private: @@ -121,6 +119,9 @@ class BrowserActionView : public views::View { // Override PaintChildren so that we can paint the badge on top of children. virtual void PaintChildren(gfx::Canvas* canvas); + // The container for this view. + BrowserActionsContainer* panel_; + // The button this view contains. BrowserActionButton* button_; }; @@ -143,6 +144,9 @@ class BrowserActionsContainer : public views::View, // Get the number of browser actions being displayed. int num_browser_actions() { return browser_action_views_.size(); } + // Returns the current tab's ID, or -1 if there is no current tab. + int GetCurrentTabId(); + // Get a particular browser action view. BrowserActionView* GetBrowserActionViewAt(int index) { return browser_action_views_[index]; @@ -192,6 +196,14 @@ class BrowserActionsContainer : public views::View, ExtensionPopup* TestGetPopup() { return popup_; } private: + // Adds a browser action view for the extension if it needs one. DCHECK if + // it has already been added. + void AddBrowserAction(Extension* extension); + + // Removes the browser action view for an extension if it has one. DCHECK if + // no such view. + void RemoveBrowserAction(Extension* extension); + // The vector of browser actions (icons/image buttons for each action). std::vector<BrowserActionView*> browser_action_views_; diff --git a/chrome/browser/views/location_bar_view.cc b/chrome/browser/views/location_bar_view.cc index ff6034b..70ce478 100644 --- a/chrome/browser/views/location_bar_view.cc +++ b/chrome/browser/views/location_bar_view.cc @@ -109,8 +109,12 @@ void LocationBarView::PageActionWithBadgeView::PaintChildren( gfx::Canvas* canvas) { View::PaintChildren(canvas); const ExtensionActionState* state = image_view_->GetPageActionState(); - if (state) - state->PaintBadge(canvas, gfx::Rect(width(), height())); + if (state) { + ExtensionActionState::PaintBadge(canvas, gfx::Rect(width(), height()), + state->badge_text(), + state->badge_text_color(), + state->badge_background_color()); + } } void LocationBarView::PageActionWithBadgeView::UpdateVisibility( @@ -1230,8 +1234,12 @@ void LocationBarView::PageActionImageView::Paint(gfx::Canvas* canvas) { const ExtensionActionState* state = contents->GetPageActionState(page_action_); - if (state) - state->PaintBadge(canvas, gfx::Rect(width(), height())); + if (state) { + ExtensionActionState::PaintBadge(canvas, gfx::Rect(width(), height()), + state->badge_text(), + state->badge_text_color(), + state->badge_background_color()); + } } // PageActionImageView---------------------------------------------------------- diff --git a/chrome/browser/views/toolbar_view.cc b/chrome/browser/views/toolbar_view.cc index f5732c16..8853710 100644 --- a/chrome/browser/views/toolbar_view.cc +++ b/chrome/browser/views/toolbar_view.cc @@ -218,6 +218,9 @@ void ToolbarView::SetProfile(Profile* profile) { void ToolbarView::Update(TabContents* tab, bool should_restore_state) { if (location_bar_) location_bar_->Update(should_restore_state ? tab : NULL); + + if (browser_actions_) + browser_actions_->RefreshBrowserActionViews(); } int ToolbarView::GetNextAccessibleViewIndex(int view_index, bool nav_left) { diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 18bb465..75a3490 100755 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -545,6 +545,8 @@ 'common/extensions/extension_error_utils.h', 'common/extensions/extension_action.cc', 'common/extensions/extension_action.h', + 'common/extensions/extension_action2.cc', + 'common/extensions/extension_action2.h', 'common/extensions/extension_message_bundle.cc', 'common/extensions/extension_message_bundle.h', 'common/extensions/extension_resource.cc', @@ -4618,6 +4620,7 @@ 'common/common_param_traits_unittest.cc', 'common/extensions/extension_resource_unittest.cc', 'common/extensions/extension_unittest.cc', + 'common/extensions/extension_action2_unittest.cc', 'common/extensions/extension_message_bundle_unittest.cc', 'common/extensions/update_manifest_unittest.cc', 'common/extensions/url_pattern_unittest.cc', diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json index aca58f6..18da839 100755 --- a/chrome/common/extensions/api/extension_api.json +++ b/chrome/common/extensions/api/extension_api.json @@ -922,6 +922,11 @@ "description": "Pixel data for an image. Must be an ImageData object (for example, from a canvas element).", "optional": true }, + "path": { + "type": "string", + "description": "Relative path to an image in the extension to show in the browser action.", + "optional": true + }, "iconIndex": { "type": "integer", "minimum": 0, @@ -963,6 +968,11 @@ "title": { "type": "string", "description": "The string the browser action should display when moused over." + }, + "tabId": { + "type": "integer", + "optional": true, + "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed." } } } @@ -982,16 +992,15 @@ "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element).", "optional": true }, - "iconIndex": { - "type": "integer", - "minimum": 0, - "description": "<b>Deprecated.</b> The zero-based index into the <b>icons</b> vector specified in the manifest.", - "optional": true - }, "path": { "type": "string", "description": "Relative path to an image in the extension to show in the browser action.", "optional": true + }, + "tabId": { + "type": "integer", + "optional": true, + "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed." } } } @@ -1009,6 +1018,11 @@ "text": { "type": "string", "description": "Any number of characters can be passed, but only about four can fit in the space." + }, + "tabId": { + "type": "integer", + "optional": true, + "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed." } } } @@ -1033,6 +1047,11 @@ }, "minItems": 4, "maxItems": 4 + }, + "tabId": { + "type": "integer", + "optional": true, + "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed." } } } diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index 17cd0dc..202a2dc 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -385,6 +385,95 @@ ExtensionAction* Extension::LoadExtensionActionHelper( return result.release(); } +ExtensionAction2* Extension::LoadExtensionAction2Helper( + const DictionaryValue* extension_action, std::string* error) { + scoped_ptr<ExtensionAction2> result(new ExtensionAction2()); + result->set_extension_id(id()); + + // TODO(EXTENSIONS_DEPRECATED): icons list is obsolete. + ListValue* icons = NULL; + if (extension_action->HasKey(keys::kPageActionIcons) && + extension_action->GetList(keys::kPageActionIcons, &icons)) { + for (ListValue::const_iterator iter = icons->begin(); + iter != icons->end(); ++iter) { + std::string path; + if (!(*iter)->GetAsString(&path) || path.empty()) { + *error = errors::kInvalidPageActionIconPath; + return NULL; + } + + result->icon_paths()->push_back(path); + result->SetDefaultIcon(path); + } + } + + // TODO(EXTENSIONS_DEPRECATED): Read the page action |id| (optional). + std::string id; + if (extension_action->HasKey(keys::kPageActionId)) { + if (!extension_action->GetString(keys::kPageActionId, &id)) { + *error = errors::kInvalidPageActionId; + return NULL; + } + result->set_id(id); + } + + std::string default_icon; + // Read the page action |default_icon| (optional). + if (extension_action->HasKey(keys::kPageActionDefaultIcon)) { + if (!extension_action->GetString(keys::kPageActionDefaultIcon, + &default_icon) || + default_icon.empty()) { + *error = errors::kInvalidPageActionIconPath; + return NULL; + } + result->SetDefaultIcon(default_icon); + } + + // Read the page action |default_title|. + std::string title; + if (!extension_action->GetString(keys::kName, &title) && + !extension_action->GetString(keys::kPageActionDefaultTitle, &title)) { + *error = errors::kInvalidPageActionDefaultTitle; + return NULL; + } + result->SetTitle(ExtensionAction2::kDefaultTabId, title); + + // Read the action's |popup| (optional). + DictionaryValue* popup = NULL; + std::string url_str; + if (extension_action->HasKey(keys::kPageActionPopup) && + !extension_action->GetDictionary(keys::kPageActionPopup, &popup) && + !extension_action->GetString(keys::kPageActionPopup, &url_str)) { + *error = errors::kInvalidPageActionPopup; + return NULL; + } + if (popup) { + // TODO(EXTENSIONS_DEPRECATED): popup is a string only + if (!popup->GetString(keys::kPageActionPopupPath, &url_str)) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kInvalidPageActionPopupPath, "<missing>"); + return NULL; + } + GURL url = GetResourceURL(url_str); + if (!url.is_valid()) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kInvalidPageActionPopupPath, url_str); + return NULL; + } + result->set_popup_url(url); + } else if (!url_str.empty()) { + GURL url = GetResourceURL(url_str); + if (!url.is_valid()) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kInvalidPageActionPopupPath, url_str); + return NULL; + } + result->set_popup_url(url); + } + + return result.release(); +} + bool Extension::ContainsNonThemeKeys(const DictionaryValue& source) { // Generate a map of allowable keys static std::map<std::wstring, bool> theme_keys; @@ -975,13 +1064,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_id, } browser_action_.reset( - LoadExtensionActionHelper(browser_action_value, error, - ExtensionAction::BROWSER_ACTION)); + LoadExtensionAction2Helper(browser_action_value, error)); if (!browser_action_.get()) return false; // Failed to parse browser action definition. - - browser_action_state_.reset( - new ExtensionActionState(browser_action_->title(), 0)); } // Initialize the permissions (optional). @@ -1103,9 +1188,9 @@ std::set<FilePath> Extension::GetBrowserImages() { // browser action icons if (browser_action_.get()) { - const std::vector<std::string>& icon_paths = browser_action_->icon_paths(); - for (std::vector<std::string>::const_iterator iter = icon_paths.begin(); - iter != icon_paths.end(); ++iter) { + std::vector<std::string>* icon_paths = browser_action_->icon_paths(); + for (std::vector<std::string>::iterator iter = icon_paths->begin(); + iter != icon_paths->end(); ++iter) { image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(*iter))); } } diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index 38da546..4a9fcd9 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -16,6 +16,7 @@ #include "base/version.h" #include "chrome/browser/extensions/user_script_master.h" #include "chrome/common/extensions/extension_action.h" +#include "chrome/common/extensions/extension_action2.h" #include "chrome/common/extensions/extension_message_bundle.h" #include "chrome/common/extensions/extension_resource.h" #include "chrome/common/extensions/user_script.h" @@ -191,10 +192,7 @@ class Extension { const std::string& description() const { return description_; } const UserScriptList& content_scripts() const { return content_scripts_; } ExtensionAction* page_action() const { return page_action_.get(); } - ExtensionAction* browser_action() const { return browser_action_.get(); } - ExtensionActionState* browser_action_state() { - return browser_action_state_.get(); - } + ExtensionAction2* browser_action() const { return browser_action_.get(); } const std::vector<PrivacyBlacklistInfo>& privacy_blacklists() const { return privacy_blacklists_; } @@ -288,6 +286,12 @@ class Extension { std::string* error, ExtensionAction::ExtensionActionType action_type); + // Helper method to load an ExtensionAction2 from the page_action or + // browser_action entries in the manifest. + // TODO(aa): ExtensionAction2 should replace ExtensionAction completely. + ExtensionAction2* LoadExtensionAction2Helper( + const DictionaryValue* extension_action, std::string* error); + // Figures out if a source contains keys not associated with themes - we // don't want to allow scripts and such to be bundled with themes. bool ContainsNonThemeKeys(const DictionaryValue& source); @@ -325,10 +329,7 @@ class Extension { scoped_ptr<ExtensionAction> page_action_; // The extension's browser action, if any. - scoped_ptr<ExtensionAction> browser_action_; - - // The state of the browser action. Valid iff browser_action_ is non-NULL. - scoped_ptr<ExtensionActionState> browser_action_state_; + scoped_ptr<ExtensionAction2> browser_action_; // Optional list of privacy blacklistrom. std::vector<PrivacyBlacklistInfo> privacy_blacklists_; diff --git a/chrome/common/extensions/extension_action.cc b/chrome/common/extensions/extension_action.cc index de15f17..246961f 100644 --- a/chrome/common/extensions/extension_action.cc +++ b/chrome/common/extensions/extension_action.cc @@ -20,11 +20,19 @@ ExtensionAction::~ExtensionAction() { } void ExtensionActionState::PaintBadge(gfx::Canvas* canvas, - const gfx::Rect& bounds) const { - const std::string& text = badge_text(); + const gfx::Rect& bounds, + const std::string& text, + SkColor text_color, + SkColor background_color) { if (text.empty()) return; + if (SkColorGetA(text_color) == 0x00) + text_color = SK_ColorWHITE; + + if (SkColorGetA(background_color) == 0x00) + background_color = SkColorSetARGB(255, 218, 0, 24); // default badge color + // Different platforms need slightly different constants to look good. #if defined(OS_LINUX) const int kTextSize = 9; @@ -49,7 +57,7 @@ void ExtensionActionState::PaintBadge(gfx::Canvas* canvas, SkTypeface* typeface = SkTypeface::CreateFromName("Arial", SkTypeface::kBold); SkPaint text_paint; text_paint.setAntiAlias(true); - text_paint.setColor(badge_text_color()); + text_paint.setColor(text_color); text_paint.setFakeBoldText(true); text_paint.setTextAlign(SkPaint::kLeft_Align); text_paint.setTextSize(SkIntToScalar(kTextSize)); @@ -81,7 +89,7 @@ void ExtensionActionState::PaintBadge(gfx::Canvas* canvas, SkPaint rect_paint; rect_paint.setStyle(SkPaint::kFill_Style); rect_paint.setAntiAlias(true); - rect_paint.setColor(badge_background_color()); + rect_paint.setColor(background_color); canvas->drawRoundRect(rect, SkIntToScalar(2), SkIntToScalar(2), rect_paint); // Overlay the gradient. It is stretchy, so we do this in three parts. diff --git a/chrome/common/extensions/extension_action.h b/chrome/common/extensions/extension_action.h index b819edb..6eb66c1 100644 --- a/chrome/common/extensions/extension_action.h +++ b/chrome/common/extensions/extension_action.h @@ -82,6 +82,10 @@ typedef std::map<std::string, ExtensionAction*> ExtensionActionMap; // values of the ExtensionAction. class ExtensionActionState { public: + static void PaintBadge(gfx::Canvas* canvas, const gfx::Rect& bounds, + const std::string& text, SkColor text_color, + SkColor background_color); + ExtensionActionState(std::string title, int icon_index) : hidden_(false), title_(title), icon_index_(icon_index), badge_background_color_(SkColorSetARGB(255, 218, 0, 24)), @@ -119,8 +123,6 @@ class ExtensionActionState { bool hidden() const { return hidden_; } void set_hidden(bool hidden) { hidden_ = hidden; } - void PaintBadge(gfx::Canvas* canvas, const gfx::Rect& bounds) const; - private: // True if the action is in the hidden state. bool hidden_; diff --git a/chrome/common/extensions/extension_action2.cc b/chrome/common/extensions/extension_action2.cc new file mode 100755 index 0000000..653de95 --- /dev/null +++ b/chrome/common/extensions/extension_action2.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2009 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_action2.h" + +#include "base/logging.h" + +const int ExtensionAction2::kDefaultTabId = -1; + +void ExtensionAction2::SetDefaultIcon(const std::string& path) { + default_icon_path_ = path; + icon_.erase(kDefaultTabId); +} + +void ExtensionAction2::SetDefaultIcon(int icon_index) { + if (static_cast<size_t>(icon_index) >= icon_paths_.size()) { + NOTREACHED(); + return; + } + + SetDefaultIcon(icon_paths_[icon_index]); +} + +void ExtensionAction2::ClearAllValuesForTab(int tab_id) { + title_.erase(tab_id); + icon_.erase(tab_id); + badge_text_.erase(tab_id); + badge_background_color_.erase(tab_id); + badge_text_color_.erase(tab_id); +} diff --git a/chrome/common/extensions/extension_action2.h b/chrome/common/extensions/extension_action2.h new file mode 100755 index 0000000..ae4cf34 --- /dev/null +++ b/chrome/common/extensions/extension_action2.h @@ -0,0 +1,163 @@ +// Copyright (c) 2009 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_ACTION2_H_ +#define CHROME_COMMON_EXTENSIONS_EXTENSION_ACTION2_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "googleurl/src/gurl.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColor.h" + +// ExtensionAction2 encapsulates the state of a browser or page action. +// Instances can have both global and per-tab state. If a property does not have +// a per-tab value, the global value is used instead. +// +// TODO(aa): This should replace ExtensionAction and ExtensionActionState. +class ExtensionAction2 { + public: + // Use this ID to indicate the default state for properties that take a tab_id + // parameter. + static const int kDefaultTabId; + + // extension id + std::string extension_id() const { return extension_id_; } + void set_extension_id(const std::string& extension_id) { + extension_id_ = extension_id; + } + + // popup details + const GURL& popup_url() const { return popup_url_; } + void set_popup_url(const GURL& url) { popup_url_ = url; } + bool has_popup() const { return !popup_url_.is_empty(); } + + // title + void SetTitle(int tab_id, const std::string& title) { + SetValue(&title_, tab_id, title); + } + std::string GetTitle(int tab_id) { return GetValue(&title_, tab_id); } + + // Icons are a bit different because the default value can be set to either a + // bitmap or a path. However, conceptually, there is only one default icon. + // Setting the default icon using a path clears the bitmap and vice-versa. + // + // To get the default icon, first check for the bitmap. If it is null, check + // for the path. + + // icon bitmap + void SetIcon(int tab_id, const SkBitmap& bitmap) { + SetValue(&icon_, tab_id, bitmap); + if (tab_id == kDefaultTabId) + default_icon_path_.clear(); + } + SkBitmap GetIcon(int tab_id) { return GetValue(&icon_, tab_id); } + + // icon path (relative to extension_id()'s root) + // For legacy code, we also support setting the path as an index into + // icon_paths(). + void SetDefaultIcon(const std::string& path); + void SetDefaultIcon(int icon_index); + std::string GetDefaultIconPath() { + return default_icon_path_; + } + + // badge text + void SetBadgeText(int tab_id, const std::string& text) { + SetValue(&badge_text_, tab_id, text); + } + std::string GetBadgeText(int tab_id) { return GetValue(&badge_text_, tab_id); } + + // badge text color + void SetBadgeTextColor(int tab_id, const SkColor& text_color) { + SetValue(&badge_text_color_, tab_id, text_color); + } + SkColor GetBadgeTextColor(int tab_id) { + return GetValue(&badge_text_color_, tab_id); + } + + // badge background color + void SetBadgeBackgroundColor(int tab_id, const SkColor& color) { + SetValue(&badge_background_color_, tab_id, color); + } + SkColor GetBadgeBackgroundColor(int tab_id) { + return GetValue(&badge_background_color_, tab_id); + } + + // Remove all tab-specific state. + void ClearAllValuesForTab(int tab_id); + + //--------------------------------------------------------------------------- + // Legacy support + + std::string id() const { return id_; } + void set_id(const std::string& id) { id_ = id; } + + std::vector<std::string>* icon_paths() { return &icon_paths_; } + + private: + template <class T> + struct ValueTraits { + static T CreateEmpty() { + return T(); + } + }; + + template<class T> + void SetValue(std::map<int, T>* map, int tab_id, T val) { + (*map)[tab_id] = val; + } + + template<class T> + T GetValue(std::map<int, T>* map, int tab_id) { + typename std::map<int, T>::iterator iter = map->find(tab_id); + if (iter != map->end()) { + return iter->second; + } else { + iter = map->find(kDefaultTabId); + return iter != map->end() ? iter->second : ValueTraits<T>::CreateEmpty(); + } + } + + // The id for the extension this action belongs to (as defined in the + // extension manifest). + std::string extension_id_; + + // Each of these data items can have both a global state (stored with the key + // kDefaultTabId), or tab-specific state (stored with the tab_id as the key). + std::map<int, std::string> title_; + std::map<int, SkBitmap> icon_; + std::map<int, std::string> badge_text_; + std::map<int, SkColor> badge_background_color_; + std::map<int, SkColor> badge_text_color_; + + std::string default_icon_path_; + + // If the action has a popup, it has a URL and a height. + GURL popup_url_; + + //--------------------------------------------------------------------------- + // Legacy support + + // The id for the ExtensionAction2, for example: "RssPageAction". This is + // needed for compat with an older version of the page actions API. + std::string id_; + + // A list of paths to icons this action might show. This is needed to support + // the setIcon({iconIndex:...} method. + std::vector<std::string> icon_paths_; +}; + +template <> +struct ExtensionAction2::ValueTraits<SkColor> { + static SkColor CreateEmpty() { + return 0x00000000; + } +}; + +#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_ACTION2_H_ diff --git a/chrome/common/extensions/extension_action2_unittest.cc b/chrome/common/extensions/extension_action2_unittest.cc new file mode 100644 index 0000000..7f1d78b --- /dev/null +++ b/chrome/common/extensions/extension_action2_unittest.cc @@ -0,0 +1,127 @@ +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/extensions/extension_action2.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "webkit/glue/image_decoder.h" + +static SkBitmap LoadIcon(const std::string& filename) { + FilePath path; + PathService::Get(chrome::DIR_TEST_DATA, &path); + path = path.AppendASCII("extensions").AppendASCII(filename); + + std::string file_contents; + file_util::ReadFileToString(path, &file_contents); + const unsigned char* data = + reinterpret_cast<const unsigned char*>(file_contents.data()); + + SkBitmap bitmap; + webkit_glue::ImageDecoder decoder; + bitmap = decoder.Decode(data, file_contents.length()); + + return bitmap; +} + +static bool BitmapsAreEqual(const SkBitmap& bitmap1, const SkBitmap& bitmap2) { + void* addr1 = NULL; + void* addr2 = NULL; + + bitmap1.lockPixels(); + addr1 = bitmap1.getAddr32(0, 0); + bitmap1.unlockPixels(); + + bitmap2.lockPixels(); + addr2 = bitmap2.getAddr32(0, 0); + bitmap2.unlockPixels(); + + return 0 == memcmp(addr1, addr2, bitmap1.getSize()); +} + +TEST(ExtensionAction2Test, TabSpecificState) { + ExtensionAction2 action; + + // title + ASSERT_EQ("", action.GetTitle(1)); + action.SetTitle(ExtensionAction2::kDefaultTabId, "foo"); + ASSERT_EQ("foo", action.GetTitle(1)); + ASSERT_EQ("foo", action.GetTitle(100)); + action.SetTitle(100, "bar"); + ASSERT_EQ("foo", action.GetTitle(1)); + ASSERT_EQ("bar", action.GetTitle(100)); + action.SetTitle(ExtensionAction2::kDefaultTabId, "baz"); + ASSERT_EQ("baz", action.GetTitle(1)); + action.ClearAllValuesForTab(100); + ASSERT_EQ("baz", action.GetTitle(100)); + + // icon + SkBitmap icon1 = LoadIcon("icon1.png"); + SkBitmap icon2 = LoadIcon("icon2.png"); + ASSERT_TRUE(action.GetIcon(1).isNull()); + action.SetIcon(ExtensionAction2::kDefaultTabId, icon1); + ASSERT_TRUE(BitmapsAreEqual(icon1, action.GetIcon(100))); + action.SetIcon(100, icon2); + ASSERT_TRUE(BitmapsAreEqual(icon1, action.GetIcon(1))); + ASSERT_TRUE(BitmapsAreEqual(icon2, action.GetIcon(100))); + + // badge text + ASSERT_EQ("", action.GetBadgeText(1)); + action.SetBadgeText(ExtensionAction2::kDefaultTabId, "foo"); + ASSERT_EQ("foo", action.GetBadgeText(1)); + ASSERT_EQ("foo", action.GetBadgeText(100)); + action.SetBadgeText(100, "bar"); + ASSERT_EQ("foo", action.GetBadgeText(1)); + ASSERT_EQ("bar", action.GetBadgeText(100)); + action.SetBadgeText(ExtensionAction2::kDefaultTabId, "baz"); + ASSERT_EQ("baz", action.GetBadgeText(1)); + action.ClearAllValuesForTab(100); + ASSERT_EQ("baz", action.GetBadgeText(100)); + + // badge text color + ASSERT_EQ(0x00000000u, action.GetBadgeTextColor(1)); + action.SetBadgeTextColor(ExtensionAction2::kDefaultTabId, 0xFFFF0000); + ASSERT_EQ(0xFFFF0000u, action.GetBadgeTextColor(1)); + ASSERT_EQ(0xFFFF0000u, action.GetBadgeTextColor(100)); + action.SetBadgeTextColor(100, 0xFF00FF00); + ASSERT_EQ(0xFFFF0000u, action.GetBadgeTextColor(1)); + ASSERT_EQ(0xFF00FF00u, action.GetBadgeTextColor(100)); + action.SetBadgeTextColor(ExtensionAction2::kDefaultTabId, 0xFF0000FF); + ASSERT_EQ(0xFF0000FFu, action.GetBadgeTextColor(1)); + action.ClearAllValuesForTab(100); + ASSERT_EQ(0xFF0000FFu, action.GetBadgeTextColor(100)); + + // badge background color + ASSERT_EQ(0x00000000u, action.GetBadgeBackgroundColor(1)); + action.SetBadgeBackgroundColor(ExtensionAction2::kDefaultTabId, 0xFFFF0000); + ASSERT_EQ(0xFFFF0000u, action.GetBadgeBackgroundColor(1)); + ASSERT_EQ(0xFFFF0000u, action.GetBadgeBackgroundColor(100)); + action.SetBadgeBackgroundColor(100, 0xFF00FF00); + ASSERT_EQ(0xFFFF0000u, action.GetBadgeBackgroundColor(1)); + ASSERT_EQ(0xFF00FF00u, action.GetBadgeBackgroundColor(100)); + action.SetBadgeBackgroundColor(ExtensionAction2::kDefaultTabId, 0xFF0000FF); + ASSERT_EQ(0xFF0000FFu, action.GetBadgeBackgroundColor(1)); + action.ClearAllValuesForTab(100); + ASSERT_EQ(0xFF0000FFu, action.GetBadgeBackgroundColor(100)); +} + +TEST(ExtensionAction2Test, IconOddCases) { + ExtensionAction2 action; + + action.SetIcon(ExtensionAction2::kDefaultTabId, LoadIcon("icon1.png")); + action.SetDefaultIcon("foo.png"); + ASSERT_TRUE(action.GetIcon(1).isNull()); + ASSERT_EQ("foo.png", action.GetDefaultIconPath()); + + action.icon_paths()->push_back("a.png"); + action.icon_paths()->push_back("b.png"); + action.SetDefaultIcon(1); + ASSERT_TRUE(action.GetIcon(1).isNull()); + ASSERT_EQ("b.png", action.GetDefaultIconPath()); + + action.SetIcon(100, LoadIcon("icon1.png")); + ASSERT_TRUE(!action.GetIcon(100).isNull()); + action.SetIcon(ExtensionAction2::kDefaultTabId, LoadIcon("icon1.png")); + ASSERT_TRUE(!action.GetIcon(1).isNull()); + ASSERT_EQ("", action.GetDefaultIconPath()); +} diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc index a9360eb..428281e 100644 --- a/chrome/renderer/extensions/extension_process_bindings.cc +++ b/chrome/renderer/extensions/extension_process_bindings.cc @@ -416,7 +416,6 @@ class ExtensionImpl : public ExtensionBase { // before sending the request to the browser. static v8::Handle<v8::Value> SetExtensionActionIcon(const v8::Arguments& args) { v8::Local<v8::Object> details = args[1]->ToObject(); - int tab_id = details->Get(v8::String::New("tabId"))->Int32Value(); v8::Local<v8::Object> image_data = details->Get(v8::String::New("imageData"))->ToObject(); v8::Local<v8::Object> data = @@ -453,7 +452,11 @@ class ExtensionImpl : public ExtensionBase { DictionaryValue* dict = new DictionaryValue(); dict->Set(L"imageData", bitmap_value); - dict->SetInteger(L"tabId", tab_id); + + if (details->Has(v8::String::New("tabId"))) { + dict->SetInteger(L"tabId", + details->Get(v8::String::New("tabId"))->Int32Value()); + } return StartRequestCommon(args, dict); } diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js index 3974867..fbe96d4 100644 --- a/chrome/renderer/resources/extension_process_bindings.js +++ b/chrome/renderer/resources/extension_process_bindings.js @@ -345,7 +345,7 @@ var chrome = chrome || {}; return GetL10nMessage(message_name, placeholders); } - var canvas_context; + var canvas; function setIconCommon(details, name, parameters) { if ("iconIndex" in details) { sendRequest(name, [details], parameters); @@ -364,13 +364,13 @@ var chrome = chrome || {}; } sendCustomRequest(SetExtensionActionIcon, name, [details], parameters); } else if ("path" in details) { - if (!canvas_context) { + if (!canvas) { var canvas = document.createElement("canvas"); canvas.width = 19; canvas.height = 19; - canvas_context = canvas.getContext('2d'); } + var canvas_context = canvas.getContext('2d'); var img = new Image(); var self = this; img.onerror = function() { diff --git a/chrome/test/data/extensions/api_test/browser_action/update.html b/chrome/test/data/extensions/api_test/browser_action/update.html index 6d69bd3..a0a347c 100644 --- a/chrome/test/data/extensions/api_test/browser_action/update.html +++ b/chrome/test/data/extensions/api_test/browser_action/update.html @@ -4,7 +4,7 @@ // Test that we can change various properties of the browser action. // The C++ verifies. chrome.browserAction.setTitle({title: "Modified"}); - chrome.browserAction.setIcon({iconIndex: 1}); + chrome.browserAction.setIcon({path: "icon2.png"}); chrome.browserAction.setBadgeText({text: "badge"}); chrome.browserAction.setBadgeBackgroundColor({color: [255,255,255,255]}); diff --git a/chrome/test/data/extensions/api_test/browser_action_tab_specific_state/background.html b/chrome/test/data/extensions/api_test/browser_action_tab_specific_state/background.html new file mode 100644 index 0000000..7393c5d --- /dev/null +++ b/chrome/test/data/extensions/api_test/browser_action_tab_specific_state/background.html @@ -0,0 +1,37 @@ +<html> +<head> +<script> + var min = 1; + var max = 5; + var current = min; + + // Called when the user clicks on the browser action. + chrome.browserAction.onClicked.addListener(function(tab) { + current++; + if (current > max) + current = min; + + chrome.browserAction.setIcon({ + path: "icon" + current + ".png", + tabId: tab.id + }); + chrome.browserAction.setTitle({ + title: "Showing icon " + current, + tabId: tab.id + }); + chrome.browserAction.setBadgeText({ + text: String(current), + tabId: tab.id + }); + chrome.browserAction.setBadgeBackgroundColor({ + color: [50*current,0,0,255], + tabId: tab.id + }); + + chrome.test.notifyPass(); + }); + + chrome.test.notifyPass(); +</script> +</head> +</html> diff --git a/chrome/test/data/extensions/api_test/browser_action_tab_specific_state/manifest.json b/chrome/test/data/extensions/api_test/browser_action_tab_specific_state/manifest.json new file mode 100755 index 0000000..2e11ba8 --- /dev/null +++ b/chrome/test/data/extensions/api_test/browser_action_tab_specific_state/manifest.json @@ -0,0 +1,12 @@ +{ + "name": "Tab color", + "version": "1.0", + "background_page": "background.html", + "permissions": [ + "tabs", "http://*/*" + ], + "browser_action": { + "default_icon": "icon1.png", + "default_title": "hi!" + } +}
\ No newline at end of file |