diff options
author | bauerb@chromium.org <bauerb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-09 13:40:40 +0000 |
---|---|---|
committer | bauerb@chromium.org <bauerb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-09 13:40:40 +0000 |
commit | 56f1a6ba817c77388d9df86c0f23db4dde7e0934 (patch) | |
tree | bd8c938c53f69f2ae1e104cf90cad49511e639b3 | |
parent | 763e638fc941c4295feb961ea1c9b127de28de6a (diff) | |
download | chromium_src-56f1a6ba817c77388d9df86c0f23db4dde7e0934.zip chromium_src-56f1a6ba817c77388d9df86c0f23db4dde7e0934.tar.gz chromium_src-56f1a6ba817c77388d9df86c0f23db4dde7e0934.tar.bz2 |
[Win] Add per-plugin exceptions to content settings.
Screenshot: http://imgur.com/yxEzO.png
BUG=39252
TEST=PluginExceptionsTableModel.*
Review URL: http://codereview.chromium.org/3307014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@58929 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/host_content_settings_map.cc | 4 | ||||
-rw-r--r-- | chrome/browser/plugin_exceptions_table_model.cc | 197 | ||||
-rw-r--r-- | chrome/browser/plugin_exceptions_table_model.h | 78 | ||||
-rw-r--r-- | chrome/browser/plugin_exceptions_table_model_unittest.cc | 190 | ||||
-rw-r--r-- | chrome/browser/views/options/content_filter_page_view.cc | 24 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | views/controls/table/table_view.cc | 38 | ||||
-rw-r--r-- | views/controls/table/table_view.h | 3 |
9 files changed, 516 insertions, 21 deletions
diff --git a/chrome/browser/host_content_settings_map.cc b/chrome/browser/host_content_settings_map.cc index 9c01798..c018219 100644 --- a/chrome/browser/host_content_settings_map.cc +++ b/chrome/browser/host_content_settings_map.cc @@ -489,8 +489,8 @@ void HostContentSettingsMap::SetContentSetting( ContentSetting setting) { DCHECK(kTypeNames[content_type] != NULL); // Don't call this for Geolocation. DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); - DCHECK(RequiresResourceIdentifier(content_type) != - resource_identifier.empty()); + DCHECK_NE(RequiresResourceIdentifier(content_type), + resource_identifier.empty()); bool early_exit = false; std::string pattern_str(pattern.AsString()); diff --git a/chrome/browser/plugin_exceptions_table_model.cc b/chrome/browser/plugin_exceptions_table_model.cc new file mode 100644 index 0000000..f610432 --- /dev/null +++ b/chrome/browser/plugin_exceptions_table_model.cc @@ -0,0 +1,197 @@ +// 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/browser/plugin_exceptions_table_model.h" + +#include "app/l10n_util.h" +#include "app/table_model_observer.h" +#include "base/auto_reset.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/notification_service.h" +#include "grit/generated_resources.h" +#include "webkit/glue/plugins/plugin_list.h" + +PluginExceptionsTableModel::PluginExceptionsTableModel( + HostContentSettingsMap* content_settings_map, + HostContentSettingsMap* otr_content_settings_map) + : map_(content_settings_map), + otr_map_(otr_content_settings_map), + updates_disabled_(false), + observer_(NULL) { + registrar_.Add(this, NotificationType::CONTENT_SETTINGS_CHANGED, + NotificationService::AllSources()); +} + +bool PluginExceptionsTableModel::CanRemoveRows(const Rows& rows) const { + return !rows.empty(); +} + +void PluginExceptionsTableModel::RemoveRows(const Rows& rows) { + AutoReset<bool> tmp(&updates_disabled_, true); + bool reload_all = false; + // Iterate in reverse over the rows to get the indexes right. + for (Rows::const_reverse_iterator it = rows.rbegin(); + it != rows.rend(); ++it) { + SettingsEntry& entry = settings_[*it]; + HostContentSettingsMap* map = entry.is_otr ? otr_map_ : map_; + map->SetContentSetting(entry.pattern, + CONTENT_SETTINGS_TYPE_PLUGINS, + resources_[entry.plugin_id], + CONTENT_SETTING_DEFAULT); + row_counts_[entry.plugin_id]--; + // If we remove the last exception for a plugin, recreate all groups to get + // correct IDs. + if (row_counts_[entry.plugin_id] == 0) + reload_all = true; + settings_.erase(settings_.begin() + *it); + } + if (reload_all) { + // This also notifies the observer. + ReloadSettings(); + } else if (observer_) { + for (Rows::const_reverse_iterator it = rows.rbegin(); + it != rows.rend(); ++it) { + observer_->OnItemsRemoved(*it, 1); + } + } +} + +void PluginExceptionsTableModel::RemoveAll() { + int old_row_count = RowCount(); + { + AutoReset<bool> tmp(&updates_disabled_, true); + map_->ClearSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS); + if (otr_map_) + otr_map_->ClearSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS); + } + ClearSettings(); + if (observer_) + observer_->OnItemsRemoved(0, old_row_count); +} + +int PluginExceptionsTableModel::RowCount() { + return settings_.size(); +} + +std::wstring PluginExceptionsTableModel::GetText(int row, int column_id) { + DCHECK_GE(row, 0); + DCHECK_LT(row, static_cast<int>(settings_.size())); + SettingsEntry& entry = settings_[row]; + switch (column_id) { + case IDS_EXCEPTIONS_PATTERN_HEADER: + case IDS_EXCEPTIONS_HOSTNAME_HEADER: + return UTF8ToWide(entry.pattern.AsString()); + + case IDS_EXCEPTIONS_ACTION_HEADER: + switch (entry.setting) { + case CONTENT_SETTING_ALLOW: + return l10n_util::GetString(IDS_EXCEPTIONS_ALLOW_BUTTON); + case CONTENT_SETTING_BLOCK: + return l10n_util::GetString(IDS_EXCEPTIONS_BLOCK_BUTTON); + default: + NOTREACHED(); + } + break; + + default: + NOTREACHED(); + } + + return std::wstring(); +} + +void PluginExceptionsTableModel::SetObserver(TableModelObserver* observer) { + observer_ = observer; +} + +TableModel::Groups PluginExceptionsTableModel::GetGroups() { + return groups_; +} + +int PluginExceptionsTableModel::GetGroupID(int row) { + DCHECK_LT(row, static_cast<int>(settings_.size())); + return settings_[row].plugin_id; +} + +void PluginExceptionsTableModel::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (!updates_disabled_) + ReloadSettings(); +} + +void PluginExceptionsTableModel::ClearSettings() { + settings_.clear(); + groups_.clear(); + row_counts_.clear(); + resources_.clear(); +} + +void PluginExceptionsTableModel::GetPlugins( + std::vector<WebPluginInfo>* plugins) { + NPAPI::PluginList::Singleton()->GetPlugins(false, plugins); +} + +void PluginExceptionsTableModel::LoadSettings() { + int group_id = 0; + std::vector<WebPluginInfo> plugins; + GetPlugins(&plugins); + for (std::vector<WebPluginInfo>::iterator it = plugins.begin(); + it != plugins.end(); ++it) { +#if defined OS_POSIX + std::string plugin = it->path.value(); +#elif defined OS_WIN + std::string plugin = base::SysWideToUTF8(it->path.value()); +#endif + HostContentSettingsMap::SettingsForOneType settings; + map_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS, + plugin, + &settings); + HostContentSettingsMap::SettingsForOneType otr_settings; + if (otr_map_) { + otr_map_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS, + plugin, + &otr_settings); + } + std::wstring title = UTF16ToWide(it->name); + for (HostContentSettingsMap::SettingsForOneType::iterator setting_it = + settings.begin(); setting_it != settings.end(); ++setting_it) { + SettingsEntry entry = { + setting_it->first, + group_id, + setting_it->second, + false + }; + settings_.push_back(entry); + } + for (HostContentSettingsMap::SettingsForOneType::iterator setting_it = + otr_settings.begin(); + setting_it != otr_settings.end(); ++setting_it) { + SettingsEntry entry = { + setting_it->first, + group_id, + setting_it->second, + true + }; + settings_.push_back(entry); + } + int num_plugins = settings.size() + otr_settings.size(); + if (num_plugins > 0) { + Group group = { title, group_id++ }; + groups_.push_back(group); + resources_.push_back(plugin); + row_counts_.push_back(num_plugins); + } + } +} + +void PluginExceptionsTableModel::ReloadSettings() { + ClearSettings(); + LoadSettings(); + + if (observer_) + observer_->OnModelChanged(); +} + diff --git a/chrome/browser/plugin_exceptions_table_model.h b/chrome/browser/plugin_exceptions_table_model.h new file mode 100644 index 0000000..8732a0d --- /dev/null +++ b/chrome/browser/plugin_exceptions_table_model.h @@ -0,0 +1,78 @@ +// 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_BROWSER_PLUGIN_EXCEPTIONS_TABLE_MODEL_H_ +#define CHROME_BROWSER_PLUGIN_EXCEPTIONS_TABLE_MODEL_H_ +#pragma once + +#include <deque> + +#include "chrome/browser/remove_rows_table_model.h" +#include "chrome/browser/host_content_settings_map.h" +#include "chrome/common/notification_observer.h" + +struct WebPluginInfo; + +class PluginExceptionsTableModel : public RemoveRowsTableModel, + public NotificationObserver { + public: + PluginExceptionsTableModel(HostContentSettingsMap* content_settings_map, + HostContentSettingsMap* otr_content_settings_map); + virtual ~PluginExceptionsTableModel() {} + + // Load plugin exceptions from the HostContentSettingsMaps. You should call + // this method after creating a new PluginExceptionsTableModel. + void LoadSettings(); + + // RemoveRowsTableModel methods: + virtual bool CanRemoveRows(const Rows& rows) const; + virtual void RemoveRows(const Rows& rows); + virtual void RemoveAll(); + + // TableModel methods: + virtual int RowCount(); + virtual std::wstring GetText(int row, int column_id); + virtual void SetObserver(TableModelObserver* observer); + virtual bool HasGroups() { return true; } + virtual Groups GetGroups(); + virtual int GetGroupID(int row); + + // NotificationObserver methods: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + protected: + // Subclasses can override this method for testing. + virtual void GetPlugins(std::vector<WebPluginInfo>* plugins); + + private: + friend class PluginExceptionsTableModelTest; + + struct SettingsEntry { + HostContentSettingsMap::Pattern pattern; + int plugin_id; + ContentSetting setting; + bool is_otr; + }; + + void ClearSettings(); + void ReloadSettings(); + + HostContentSettingsMap* map_; + HostContentSettingsMap* otr_map_; + + std::deque<SettingsEntry> settings_; + std::deque<int> row_counts_; + std::deque<std::string> resources_; + TableModel::Groups groups_; + + NotificationRegistrar registrar_; + bool updates_disabled_; + TableModelObserver* observer_; + + DISALLOW_COPY_AND_ASSIGN(PluginExceptionsTableModel); +}; + +#endif // CHROME_BROWSER_PLUGIN_EXCEPTIONS_TABLE_MODEL_H_ diff --git a/chrome/browser/plugin_exceptions_table_model_unittest.cc b/chrome/browser/plugin_exceptions_table_model_unittest.cc new file mode 100644 index 0000000..09eac8a --- /dev/null +++ b/chrome/browser/plugin_exceptions_table_model_unittest.cc @@ -0,0 +1,190 @@ +// 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 "app/table_model_observer.h" +#include "base/command_line.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/plugin_exceptions_table_model.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/testing_pref_service.h" +#include "chrome/test/testing_profile.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/plugins/webplugininfo.h" + +namespace { + +class MockTableModelObserver : public TableModelObserver { + public: + MOCK_METHOD0(OnModelChanged, void()); + MOCK_METHOD2(OnItemsChanged, void(int start, int length)); + MOCK_METHOD2(OnItemsAdded, void(int start, int length)); + MOCK_METHOD2(OnItemsRemoved, void(int start, int length)); +}; + +class TestingPluginExceptionsTableModel : public PluginExceptionsTableModel { + public: + TestingPluginExceptionsTableModel(HostContentSettingsMap* map, + HostContentSettingsMap* otr_map) + : PluginExceptionsTableModel(map, otr_map) {} + virtual ~TestingPluginExceptionsTableModel() {} + + void set_plugins(const std::vector<WebPluginInfo>& plugins) { + plugins_ = plugins; + } + + protected: + virtual void GetPlugins(std::vector<WebPluginInfo>* plugins) { + *plugins = plugins_; + } + + private: + std::vector<WebPluginInfo> plugins_; +}; + +} + +class PluginExceptionsTableModelTest : public testing::Test { + public: + PluginExceptionsTableModelTest() + : ui_thread_(ChromeThread::UI, &message_loop_), + command_line_(*CommandLine::ForCurrentProcess()) {} + + virtual void SetUp() { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableResourceContentSettings); + + profile_.reset(new TestingProfile()); + + HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); + + HostContentSettingsMap::Pattern example_com("[*.]example.com"); + HostContentSettingsMap::Pattern moose_org("[*.]moose.org"); + map->SetContentSetting(example_com, + CONTENT_SETTINGS_TYPE_PLUGINS, + "foo", + CONTENT_SETTING_ALLOW); + map->SetContentSetting(moose_org, + CONTENT_SETTINGS_TYPE_PLUGINS, + "bar", + CONTENT_SETTING_BLOCK); + map->SetContentSetting(example_com, + CONTENT_SETTINGS_TYPE_PLUGINS, + "bar", + CONTENT_SETTING_ALLOW); + + table_model_.reset(new TestingPluginExceptionsTableModel( + map, + NULL)); + + std::vector<WebPluginInfo> plugins; + WebPluginInfo foo_plugin; + foo_plugin.path = FilePath(FILE_PATH_LITERAL("foo")); + foo_plugin.name = ASCIIToUTF16("FooPlugin"); + foo_plugin.enabled = true; + plugins.push_back(foo_plugin); + WebPluginInfo bar_plugin; + bar_plugin.path = FilePath(FILE_PATH_LITERAL("bar")); + bar_plugin.name = ASCIIToUTF16("BarPlugin"); + bar_plugin.enabled = true; + plugins.push_back(bar_plugin); + + table_model_->set_plugins(plugins); + table_model_->ReloadSettings(); + } + + virtual void TearDown() { + *CommandLine::ForCurrentProcess() = command_line_; + } + + protected: + void CheckInvariants() { + typedef std::deque<PluginExceptionsTableModel::SettingsEntry> Entries; + Entries& settings = table_model_->settings_; + std::deque<int>& row_counts = table_model_->row_counts_; + std::deque<std::string>& resources = table_model_->resources_; + TableModel::Groups& groups = table_model_->groups_; + + EXPECT_EQ(groups.size(), row_counts.size()); + EXPECT_EQ(groups.size(), resources.size()); + + int last_plugin = 0; + int count = 0; + for (Entries::const_iterator it = settings.begin(); + it != settings.end(); ++it) { + // Plugin IDs are indices into |groups|. + EXPECT_GE(it->plugin_id, 0); + EXPECT_LT(it->plugin_id, static_cast<int>(groups.size())); + if (it->plugin_id == last_plugin) { + count++; + } else { + // Plugin IDs are ascending one by one. + EXPECT_EQ(last_plugin+1, it->plugin_id); + + // Consecutive runs of plugins with id |x| are |row_counts[x]| long. + EXPECT_EQ(count, row_counts[last_plugin]); + count = 1; + last_plugin = it->plugin_id; + } + } + if (row_counts.size() > 0) + EXPECT_EQ(count, row_counts[last_plugin]); + } + + protected: + MessageLoop message_loop_; + ChromeThread ui_thread_; + + scoped_ptr<TestingProfile> profile_; + scoped_ptr<TestingPluginExceptionsTableModel> table_model_; + + private: + CommandLine command_line_; +}; + +TEST_F(PluginExceptionsTableModelTest, Basic) { + EXPECT_EQ(3, table_model_->RowCount()); + CheckInvariants(); +} + +TEST_F(PluginExceptionsTableModelTest, RemoveOneRow) { + MockTableModelObserver observer; + table_model_->SetObserver(&observer); + + EXPECT_CALL(observer, OnItemsRemoved(1, 1)); + RemoveRowsTableModel::Rows rows; + rows.insert(1); + table_model_->RemoveRows(rows); + EXPECT_EQ(2, table_model_->RowCount()); + EXPECT_EQ(2, static_cast<int>(table_model_->GetGroups().size())); + CheckInvariants(); + table_model_->SetObserver(NULL); +} + +TEST_F(PluginExceptionsTableModelTest, RemoveLastRowInGroup) { + MockTableModelObserver observer; + table_model_->SetObserver(&observer); + + EXPECT_CALL(observer, OnModelChanged()); + RemoveRowsTableModel::Rows rows; + rows.insert(0); + table_model_->RemoveRows(rows); + EXPECT_EQ(2, table_model_->RowCount()); + EXPECT_EQ(1, static_cast<int>(table_model_->GetGroups().size())); + CheckInvariants(); + table_model_->SetObserver(NULL); +} + +TEST_F(PluginExceptionsTableModelTest, RemoveAllRows) { + MockTableModelObserver observer; + table_model_->SetObserver(&observer); + + EXPECT_CALL(observer, OnItemsRemoved(0, 3)); + table_model_->RemoveAll(); + EXPECT_EQ(0, table_model_->RowCount()); + EXPECT_EQ(0, static_cast<int>(table_model_->GetGroups().size())); + CheckInvariants(); + table_model_->SetObserver(NULL); +} diff --git a/chrome/browser/views/options/content_filter_page_view.cc b/chrome/browser/views/options/content_filter_page_view.cc index c017558..ac2ab38 100644 --- a/chrome/browser/views/options/content_filter_page_view.cc +++ b/chrome/browser/views/options/content_filter_page_view.cc @@ -10,6 +10,7 @@ #include "chrome/browser/geolocation/geolocation_exceptions_table_model.h" #include "chrome/browser/notifications/desktop_notification_service.h" #include "chrome/browser/notifications/notification_exceptions_table_model.h" +#include "chrome/browser/plugin_exceptions_table_model.h" #include "chrome/browser/profile.h" #include "chrome/browser/views/options/exceptions_view.h" #include "chrome/browser/views/options/simple_content_exceptions_view.h" @@ -190,12 +191,27 @@ void ContentFilterPageView::ButtonPressed(views::Button* sender, profile()->GetDesktopNotificationService()), IDS_NOTIFICATIONS_EXCEPTION_TITLE); } else { - ExceptionsView::ShowExceptionsWindow(GetWindow()->GetNativeWindow(), - profile()->GetHostContentSettingsMap(), + HostContentSettingsMap* settings = profile()->GetHostContentSettingsMap(); + HostContentSettingsMap* otr_settings = profile()->HasOffTheRecordProfile() ? profile()->GetOffTheRecordProfile()->GetHostContentSettingsMap() : - NULL, - content_type_); + NULL; + if (content_type_ == CONTENT_SETTINGS_TYPE_PLUGINS && + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableResourceContentSettings)) { + PluginExceptionsTableModel* model = + new PluginExceptionsTableModel(settings, otr_settings); + model->LoadSettings(); + SimpleContentExceptionsView::ShowExceptionsWindow( + GetWindow()->GetNativeWindow(), + model, + IDS_PLUGINS_EXCEPTION_TITLE); + } else { + ExceptionsView::ShowExceptionsWindow(GetWindow()->GetNativeWindow(), + settings, + otr_settings, + content_type_); + } } return; } diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 83ad314..dff0e57 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2193,6 +2193,8 @@ 'browser/platform_util_common_linux.cc', 'browser/platform_util_mac.mm', 'browser/platform_util_win.cc', + 'browser/plugin_exceptions_table_model.cc', + 'browser/plugin_exceptions_table_model.h', 'browser/plugin_installer.cc', 'browser/plugin_installer.h', 'browser/plugin_process_host.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index aefbfba..810908d 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1162,6 +1162,7 @@ 'browser/password_manager/password_store_default_unittest.cc', 'browser/password_manager/password_store_mac_unittest.cc', 'browser/password_manager/password_store_win_unittest.cc', + 'browser/plugin_exceptions_table_model_unittest.cc', 'browser/policy/config_dir_policy_provider_unittest.cc', 'browser/policy/configuration_policy_pref_store_unittest.cc', 'browser/policy/configuration_policy_provider_mac_unittest.cc', diff --git a/views/controls/table/table_view.cc b/views/controls/table/table_view.cc index e397ce9..42c137d 100644 --- a/views/controls/table/table_view.cc +++ b/views/controls/table/table_view.cc @@ -278,6 +278,8 @@ void TableView::OnModelChanged() { if (!list_view_) return; + UpdateGroups(); + int current_row_count = ListView_GetItemCount(list_view_); if (current_row_count > 0) OnItemsRemoved(0, current_row_count); @@ -805,21 +807,7 @@ HWND TableView::CreateNativeControl(HWND parent_container) { if (model_) model_->SetObserver(this); - // Add the groups. - if (model_ && model_->HasGroups()) { - ListView_EnableGroupView(list_view_, true); - - TableModel::Groups groups = model_->GetGroups(); - LVGROUP group = { 0 }; - group.cbSize = sizeof(LVGROUP); - group.mask = LVGF_HEADER | LVGF_ALIGN | LVGF_GROUPID; - group.uAlign = LVGA_HEADER_LEFT; - for (size_t i = 0; i < groups.size(); ++i) { - group.pszHeader = const_cast<wchar_t*>(groups[i].title.c_str()); - group.iGroupId = groups[i].id; - ListView_InsertGroup(list_view_, static_cast<int>(i), &group); - } - } + UpdateGroups(); // Set the # of rows. if (model_) @@ -1535,6 +1523,26 @@ void TableView::UpdateContentOffset() { content_offset_ = origin.y + header_bounds.bottom - header_bounds.top; } +void TableView::UpdateGroups() { + // Add the groups. + if (model_ && model_->HasGroups()) { + ListView_EnableGroupView(list_view_, true); + + ListView_RemoveAllGroups(list_view_); + + TableModel::Groups groups = model_->GetGroups(); + LVGROUP group = { 0 }; + group.cbSize = sizeof(LVGROUP); + group.mask = LVGF_HEADER | LVGF_ALIGN | LVGF_GROUPID; + group.uAlign = LVGA_HEADER_LEFT; + for (size_t i = 0; i < groups.size(); ++i) { + group.pszHeader = const_cast<wchar_t*>(groups[i].title.c_str()); + group.iGroupId = groups[i].id; + ListView_InsertGroup(list_view_, static_cast<int>(i), &group); + } + } +} + gfx::Rect TableView::GetAltTextBounds() { static const int kXOffset = 16; DCHECK(GetNativeControlHWND()); diff --git a/views/controls/table/table_view.h b/views/controls/table/table_view.h index 9444d96b..eb557da 100644 --- a/views/controls/table/table_view.h +++ b/views/controls/table/table_view.h @@ -402,6 +402,9 @@ class TableView : public NativeControl, // Updates content_offset_ from the position of the header. void UpdateContentOffset(); + // Reloads the groups from the model if there is one and it has groups. + void UpdateGroups(); + // Returns the bounds of the alt text. gfx::Rect GetAltTextBounds(); |