diff options
author | bauerb@chromium.org <bauerb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-15 15:38:44 +0000 |
---|---|---|
committer | bauerb@chromium.org <bauerb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-15 15:38:44 +0000 |
commit | 0cd07f946c353e33d0b9474d383ab06612e40bd8 (patch) | |
tree | c3b05021da46c9f489d5b359f53a3e3f36f02ae7 /chrome/browser | |
parent | e19f6b1bb2e8d09ae7471f10d56c834a250182c5 (diff) | |
download | chromium_src-0cd07f946c353e33d0b9474d383ab06612e40bd8.zip chromium_src-0cd07f946c353e33d0b9474d383ab06612e40bd8.tar.gz chromium_src-0cd07f946c353e33d0b9474d383ab06612e40bd8.tar.bz2 |
[Mac] Add per-plugin exceptions to content settings.
Screenshot: http://www.dropmocks.com/mXMd
I'm adding a subclass of NSArrayController, TableModelArrayController, that binds to a RemoveRowsTableModel that can use groups and displays them using group rows in an NSTableView. This cleans up SimpleContentExceptionsWindowController a lot, and the class could also be used for other table models that use groups (keyword editor and autofill).
XIB changes: In SimpleContentExceptionsWindow.xib, bind table view to TableModelArrayController instead of using the dataSource outlet. Buttons call actions on TableModelArrayController, and table view delegate also points to it.
BUG=39252
TEST=SimpleContentExceptionsWindowControllerTest.*:TableModelArrayControllerTest.*:PluginExceptionsTableModelTest.*
Review URL: http://codereview.chromium.org/3327016
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@59501 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
11 files changed, 593 insertions, 157 deletions
diff --git a/chrome/browser/cocoa/content_settings_dialog_controller.mm b/chrome/browser/cocoa/content_settings_dialog_controller.mm index 54112e9..fdfa5a4 100644 --- a/chrome/browser/cocoa/content_settings_dialog_controller.mm +++ b/chrome/browser/cocoa/content_settings_dialog_controller.mm @@ -21,6 +21,7 @@ #import "chrome/browser/host_content_settings_map.h" #import "chrome/browser/notifications/desktop_notification_service.h" #import "chrome/browser/notifications/notification_exceptions_table_model.h" +#include "chrome/browser/plugin_exceptions_table_model.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profile.h" #include "chrome/common/chrome_switches.h" @@ -344,7 +345,21 @@ class PrefObserverDisabler { } - (IBAction)showPluginsExceptions:(id)sender { - [self showExceptionsForType:CONTENT_SETTINGS_TYPE_PLUGINS]; + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableResourceContentSettings)) { + HostContentSettingsMap* settingsMap = profile_->GetHostContentSettingsMap(); + HostContentSettingsMap* offTheRecordSettingsMap = + profile_->HasOffTheRecordProfile() ? + profile_->GetOffTheRecordProfile()->GetHostContentSettingsMap() : + NULL; + PluginExceptionsTableModel* model = + new PluginExceptionsTableModel(settingsMap, offTheRecordSettingsMap); + model->LoadSettings(); + [[SimpleContentExceptionsWindowController controllerWithTableModel:model] + attachSheetTo:[self window]]; + } else { + [self showExceptionsForType:CONTENT_SETTINGS_TYPE_PLUGINS]; + } } - (IBAction)showPopupsExceptions:(id)sender { diff --git a/chrome/browser/cocoa/simple_content_exceptions_window_controller.h b/chrome/browser/cocoa/simple_content_exceptions_window_controller.h index aa1cfcf..0e29f68 100644 --- a/chrome/browser/cocoa/simple_content_exceptions_window_controller.h +++ b/chrome/browser/cocoa/simple_content_exceptions_window_controller.h @@ -6,26 +6,25 @@ #include "base/cocoa_protocols_mac.h" #include "base/scoped_ptr.h" +#import "chrome/browser/cocoa/table_model_array_controller.h" #include "chrome/browser/remove_rows_table_model.h" class RemoveRowsObserverBridge; // Controller for the geolocation exception dialog. @interface SimpleContentExceptionsWindowController : NSWindowController - <NSWindowDelegate, - NSTableViewDataSource, - NSTableViewDelegate> { + <NSWindowDelegate> { @private IBOutlet NSTableView* tableView_; IBOutlet NSButton* removeButton_; IBOutlet NSButton* removeAllButton_; IBOutlet NSButton* doneButton_; + IBOutlet TableModelArrayController* arrayController_; scoped_ptr<RemoveRowsTableModel> model_; - scoped_ptr<RemoveRowsObserverBridge> tableObserver_; } -// Shows or makes frontmost the geolocation exceptions window. +// Shows or makes frontmost the exceptions window. // Changes made by the user in the window are persisted in |model|. // Takes ownership of |model|. + (id)controllerWithTableModel:(RemoveRowsTableModel*)model; @@ -36,7 +35,4 @@ class RemoveRowsObserverBridge; - (void)attachSheetTo:(NSWindow*)window; - (IBAction)closeSheet:(id)sender; -- (IBAction)removeRow:(id)sender; -- (IBAction)removeAll:(id)sender; - @end diff --git a/chrome/browser/cocoa/simple_content_exceptions_window_controller.mm b/chrome/browser/cocoa/simple_content_exceptions_window_controller.mm index 7be52a9..a054b54 100644 --- a/chrome/browser/cocoa/simple_content_exceptions_window_controller.mm +++ b/chrome/browser/cocoa/simple_content_exceptions_window_controller.mm @@ -4,8 +4,6 @@ #import "chrome/browser/cocoa/simple_content_exceptions_window_controller.h" -#include <set> - #include "app/l10n_util_mac.h" #include "app/table_model_observer.h" #import "base/mac_util.h" @@ -16,35 +14,8 @@ @interface SimpleContentExceptionsWindowController (Private) - (id)initWithTableModel:(RemoveRowsTableModel*)model; -- (void)selectedRows:(RemoveRowsTableModel::Rows*)rows; -- (void)adjustEditingButtons; -- (void)modelDidChange; @end -// Observer for a RemoveRowsTableModel. -class RemoveRowsObserverBridge : public TableModelObserver { - public: - RemoveRowsObserverBridge(SimpleContentExceptionsWindowController* controller) - : controller_(controller) {} - virtual ~RemoveRowsObserverBridge() {} - - virtual void OnModelChanged() { - [controller_ modelDidChange]; - } - virtual void OnItemsChanged(int start, int length) { - [controller_ modelDidChange]; - } - virtual void OnItemsAdded(int start, int length) { - [controller_ modelDidChange]; - } - virtual void OnItemsRemoved(int start, int length) { - [controller_ modelDidChange]; - } - - private: - SimpleContentExceptionsWindowController* controller_; // weak -}; - namespace { const CGFloat kButtonBarHeight = 35.0; @@ -69,8 +40,6 @@ SimpleContentExceptionsWindowController* g_exceptionWindow = nil; ofType:@"nib"]; if ((self = [super initWithWindowNibPath:nibpath owner:self])) { model_.reset(model); - tableObserver_.reset(new RemoveRowsObserverBridge(self)); - model_->SetObserver(tableObserver_.get()); // TODO(thakis): autoremember window rect. // TODO(thakis): sorting support. @@ -82,15 +51,19 @@ SimpleContentExceptionsWindowController* g_exceptionWindow = nil; DCHECK([self window]); DCHECK_EQ(self, [[self window] delegate]); DCHECK(tableView_); - DCHECK_EQ(self, [tableView_ dataSource]); - DCHECK_EQ(self, [tableView_ delegate]); + DCHECK(arrayController_); CGFloat minWidth = [[removeButton_ superview] bounds].size.width + [[doneButton_ superview] bounds].size.width; [[self window] setMinSize:NSMakeSize(minWidth, [[self window] minSize].height)]; - - [self adjustEditingButtons]; + NSDictionary* columns = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:IDS_EXCEPTIONS_HOSTNAME_HEADER], @"hostname", + [NSNumber numberWithInt:IDS_EXCEPTIONS_ACTION_HEADER], @"action", + nil]; + [arrayController_ bindToTableModel:model_.get() + withColumns:columns + groupTitleColumn:@"hostname"]; } - (void)setMinWidth:(CGFloat)minWidth { @@ -104,9 +77,6 @@ SimpleContentExceptionsWindowController* g_exceptionWindow = nil; } - (void)windowWillClose:(NSNotification*)notification { - // Without this, some of the unit tests fail on 10.6: - [tableView_ setDataSource:nil]; - g_exceptionWindow = nil; [self autorelease]; } @@ -124,7 +94,7 @@ SimpleContentExceptionsWindowController* g_exceptionWindow = nil; case NSDeleteFunctionKey: // Delete deletes. if ([[tableView_ selectedRowIndexes] count] > 0) - [self removeRow:self]; + [arrayController_ remove:event]; return; } } @@ -150,68 +120,5 @@ SimpleContentExceptionsWindowController* g_exceptionWindow = nil; [NSApp endSheet:[self window]]; } -- (IBAction)removeRow:(id)sender { - RemoveRowsTableModel::Rows rows; - [self selectedRows:&rows]; - model_->RemoveRows(rows); -} - -- (IBAction)removeAll:(id)sender { - model_->RemoveAll(); -} - -// Table View Data Source ----------------------------------------------------- - -- (NSInteger)numberOfRowsInTableView:(NSTableView*)table { - return model_->RowCount(); -} - -- (id)tableView:(NSTableView*)tv - objectValueForTableColumn:(NSTableColumn*)tableColumn - row:(NSInteger)row { - NSObject* result = nil; - NSString* identifier = [tableColumn identifier]; - if ([identifier isEqualToString:@"hostname"]) { - std::wstring host = model_->GetText(row, IDS_EXCEPTIONS_HOSTNAME_HEADER); - result = base::SysWideToNSString(host); - } else if ([identifier isEqualToString:@"action"]) { - std::wstring action = model_->GetText(row, IDS_EXCEPTIONS_ACTION_HEADER); - result = base::SysWideToNSString(action); - } else { - NOTREACHED(); - } - return result; -} - -// Table View Delegate -------------------------------------------------------- - -// When the selection in the table view changes, we need to adjust buttons. -- (void)tableViewSelectionDidChange:(NSNotification*)notification { - [self adjustEditingButtons]; -} - -// Private -------------------------------------------------------------------- - -// Returns the selected rows. -- (void)selectedRows:(RemoveRowsTableModel::Rows*)rows { - NSIndexSet* selection = [tableView_ selectedRowIndexes]; - for (NSUInteger index = [selection lastIndex]; index != NSNotFound; - index = [selection indexLessThanIndex:index]) - rows->insert(index); -} - -// This method appropriately sets the enabled states on the table's editing -// buttons. -- (void)adjustEditingButtons { - RemoveRowsTableModel::Rows rows; - [self selectedRows:&rows]; - [removeButton_ setEnabled:model_->CanRemoveRows(rows)]; - [removeAllButton_ setEnabled:([tableView_ numberOfRows] > 0)]; -} - -- (void)modelDidChange { - [tableView_ reloadData]; - [self adjustEditingButtons]; -} @end diff --git a/chrome/browser/cocoa/simple_content_exceptions_window_controller_unittest.mm b/chrome/browser/cocoa/simple_content_exceptions_window_controller_unittest.mm index 34e96a5..05b025c 100644 --- a/chrome/browser/cocoa/simple_content_exceptions_window_controller_unittest.mm +++ b/chrome/browser/cocoa/simple_content_exceptions_window_controller_unittest.mm @@ -11,9 +11,26 @@ #include "chrome/browser/cocoa/browser_test_helper.h" #include "chrome/browser/cocoa/cocoa_test_helper.h" #include "chrome/browser/geolocation/geolocation_exceptions_table_model.h" +#include "chrome/browser/host_content_settings_map.h" +#include "chrome/browser/plugin_exceptions_table_model.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" +@interface SimpleContentExceptionsWindowController (Testing) + +@property(readonly, nonatomic) TableModelArrayController* arrayController; + +@end + +@implementation SimpleContentExceptionsWindowController (Testing) + +- (TableModelArrayController*)arrayController { + return arrayController_; +} + +@end + + namespace { class SimpleContentExceptionsWindowControllerTest : public CocoaTest { @@ -21,12 +38,13 @@ class SimpleContentExceptionsWindowControllerTest : public CocoaTest { virtual void SetUp() { CocoaTest::SetUp(); TestingProfile* profile = browser_helper_.profile(); - settingsMap_ = new GeolocationContentSettingsMap(profile); + geolocation_settings_ = new GeolocationContentSettingsMap(profile); + content_settings_ = new HostContentSettingsMap(profile); } SimpleContentExceptionsWindowController* GetController() { - GeolocationExceptionsTableModel* model = // Freed by window controller. - new GeolocationExceptionsTableModel(settingsMap_.get()); + GeolocationExceptionsTableModel* model = // Freed by window controller. + new GeolocationExceptionsTableModel(geolocation_settings_.get()); id controller = [SimpleContentExceptionsWindowController controllerWithTableModel:model]; [controller showWindow:nil]; @@ -34,17 +52,27 @@ class SimpleContentExceptionsWindowControllerTest : public CocoaTest { } void ClickRemoveAll(SimpleContentExceptionsWindowController* controller) { - [controller removeAll:nil]; + [controller.arrayController removeAll:nil]; } protected: BrowserTestHelper browser_helper_; - scoped_refptr<GeolocationContentSettingsMap> settingsMap_; + scoped_refptr<GeolocationContentSettingsMap> geolocation_settings_; + scoped_refptr<HostContentSettingsMap> content_settings_; }; TEST_F(SimpleContentExceptionsWindowControllerTest, Construction) { GeolocationExceptionsTableModel* model = // Freed by window controller. - new GeolocationExceptionsTableModel(settingsMap_.get()); + new GeolocationExceptionsTableModel(geolocation_settings_.get()); + SimpleContentExceptionsWindowController* controller = + [SimpleContentExceptionsWindowController controllerWithTableModel:model]; + [controller showWindow:nil]; + [controller close]; // Should autorelease. +} + +TEST_F(SimpleContentExceptionsWindowControllerTest, ShowPluginExceptions) { + PluginExceptionsTableModel* model = // Freed by window controller. + new PluginExceptionsTableModel(content_settings_.get(), NULL); SimpleContentExceptionsWindowController* controller = [SimpleContentExceptionsWindowController controllerWithTableModel:model]; [controller showWindow:nil]; @@ -52,7 +80,7 @@ TEST_F(SimpleContentExceptionsWindowControllerTest, Construction) { } TEST_F(SimpleContentExceptionsWindowControllerTest, AddExistingEditAdd) { - settingsMap_->SetContentSetting( + geolocation_settings_->SetContentSetting( GURL("http://myhost"), GURL(), CONTENT_SETTING_BLOCK); SimpleContentExceptionsWindowController* controller = GetController(); @@ -60,7 +88,7 @@ TEST_F(SimpleContentExceptionsWindowControllerTest, AddExistingEditAdd) { [controller close]; - EXPECT_EQ(0u, settingsMap_->GetAllOriginsSettings().size()); + EXPECT_EQ(0u, geolocation_settings_->GetAllOriginsSettings().size()); } } // namespace diff --git a/chrome/browser/cocoa/table_model_array_controller.h b/chrome/browser/cocoa/table_model_array_controller.h new file mode 100644 index 0000000..a3579868 --- /dev/null +++ b/chrome/browser/cocoa/table_model_array_controller.h @@ -0,0 +1,54 @@ +// 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_COCOA_TABLE_MODEL_ARRAY_CONTROLLER_H_ +#define CHROME_BROWSER_COCOA_TABLE_MODEL_ARRAY_CONTROLLER_H_ +#pragma once + +#import <Cocoa/Cocoa.h> + +#include "app/table_model_observer.h" +#include "base/cocoa_protocols_mac.h" +#include "base/scoped_nsobject.h" +#include "base/scoped_ptr.h" + +class RemoveRowsObserverBridge; +class RemoveRowsTableModel; +@class TableModelArrayController; + +// This class functions as an adapter from a RemoveRowsTableModel to a Cocoa +// NSArrayController, to be used with bindings. +// It maps the CanRemoveRows method to its canRemove property, and exposes +// RemoveRows and RemoveAll as actions (remove: and removeAll:). +// If the table model has groups, these are inserted into the list of arranged +// objects as group rows. +// The designated initializer is the same as for NSArrayController, +// initWithContent:, but usually this class is instantiated from a nib file. +// Clicking on a group row selects all rows belonging to that group, like it +// does in a Windows table_view. +// In order to show group rows, this class must be the delegate of the +// NSTableView. +@interface TableModelArrayController : NSArrayController<NSTableViewDelegate> { + @private + RemoveRowsTableModel* model_; // weak + scoped_ptr<RemoveRowsObserverBridge> tableObserver_; + scoped_nsobject<NSDictionary> columns_; + scoped_nsobject<NSString> groupTitle_; +} + +// Bind this controller to the given model. +// |columns| is a dictionary mapping table column bindings to NSNumbers +// containing the column identifier in the TableModel. +// |groupTitleColumn| is the column in the table that should display the group +// title for a group row, usually the first column. If the model doesn't have +// groups, it can be nil. +- (void)bindToTableModel:(RemoveRowsTableModel*)model + withColumns:(NSDictionary*)columns + groupTitleColumn:(NSString*)groupTitleColumn; + +- (IBAction)removeAll:(id)sender; + +@end + +#endif // CHROME_BROWSER_COCOA_TABLE_MODEL_ARRAY_CONTROLLER_H_ diff --git a/chrome/browser/cocoa/table_model_array_controller.mm b/chrome/browser/cocoa/table_model_array_controller.mm new file mode 100644 index 0000000..e88e6e1 --- /dev/null +++ b/chrome/browser/cocoa/table_model_array_controller.mm @@ -0,0 +1,245 @@ +// 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. + +#import "chrome/browser/cocoa/table_model_array_controller.h" + +#include "app/table_model.h" +#include "base/sys_string_conversions.h" +#include "chrome/browser/remove_rows_table_model.h" + +@interface TableModelArrayController (PrivateMethods) + +- (NSUInteger)offsetForGroupID:(int)groupID; +- (NSUInteger)offsetForGroupID:(int)groupID startingOffset:(NSUInteger)offset; +- (NSIndexSet*)controllerRowsForModelRowsInRange:(NSRange)range; +- (void)setModelRows:(RemoveRowsTableModel::Rows*)modelRows + fromControllerRows:(NSIndexSet*)rows; +- (void)modelDidChange; +- (void)modelDidAddItemsInRange:(NSRange)range; +- (void)modelDidRemoveItemsInRange:(NSRange)range; +- (NSDictionary*)columnValuesForRow:(NSInteger)row; + +@end + +// Observer for a RemoveRowsTableModel. +class RemoveRowsObserverBridge : public TableModelObserver { + public: + RemoveRowsObserverBridge(TableModelArrayController* controller) + : controller_(controller) {} + virtual ~RemoveRowsObserverBridge() {} + + // TableModelObserver methods + virtual void OnModelChanged(); + virtual void OnItemsChanged(int start, int length); + virtual void OnItemsAdded(int start, int length); + virtual void OnItemsRemoved(int start, int length); + + private: + TableModelArrayController* controller_; // weak +}; + +void RemoveRowsObserverBridge::OnModelChanged() { + [controller_ modelDidChange]; +} + +void RemoveRowsObserverBridge::OnItemsChanged(int start, int length) { + OnItemsRemoved(start, length); + OnItemsAdded(start, length); +} + +void RemoveRowsObserverBridge::OnItemsAdded(int start, int length) { + [controller_ modelDidAddItemsInRange:NSMakeRange(start, length)]; +} + +void RemoveRowsObserverBridge::OnItemsRemoved(int start, int length) { + [controller_ modelDidRemoveItemsInRange:NSMakeRange(start, length)]; +} + +@implementation TableModelArrayController + +static NSString* const kIsGroupRow = @"_is_group_row"; +static NSString* const kGroupID = @"_group_id"; + +- (void)bindToTableModel:(RemoveRowsTableModel*)model + withColumns:(NSDictionary*)columns + groupTitleColumn:(NSString*)groupTitleColumn { + model_ = model; + tableObserver_.reset(new RemoveRowsObserverBridge(self)); + columns_.reset([columns copy]); + groupTitle_.reset([groupTitleColumn copy]); + model_->SetObserver(tableObserver_.get()); + [self modelDidChange]; +} + +- (void)modelDidChange { + NSIndexSet* indexes = [NSIndexSet indexSetWithIndexesInRange: + NSMakeRange(0, [[self arrangedObjects] count])]; + [self removeObjectsAtArrangedObjectIndexes:indexes]; + if (model_->HasGroups()) { + const TableModel::Groups& groups = model_->GetGroups(); + DCHECK(groupTitle_.get()); + for (TableModel::Groups::const_iterator it = groups.begin(); + it != groups.end(); ++it) { + NSDictionary* group = [NSDictionary dictionaryWithObjectsAndKeys: + base::SysWideToNSString(it->title), groupTitle_.get(), + [NSNumber numberWithBool:YES], kIsGroupRow, + nil]; + [self addObject:group]; + } + } + [self modelDidAddItemsInRange:NSMakeRange(0, model_->RowCount())]; +} + +- (NSUInteger)offsetForGroupID:(int)groupID startingOffset:(NSUInteger)offset { + const TableModel::Groups& groups = model_->GetGroups(); + DCHECK_GT(offset, 0u); + for (NSUInteger i = offset - 1; i < groups.size(); ++i) { + if (groups[i].id == groupID) + return i + 1; + } + NOTREACHED(); + return NSNotFound; +} + +- (NSUInteger)offsetForGroupID:(int)groupID { + return [self offsetForGroupID:groupID startingOffset:1]; +} + +- (int)groupIDForControllerRow:(NSUInteger)row { + NSDictionary* values = [[self arrangedObjects] objectAtIndex:row]; + return [[values objectForKey:kGroupID] intValue]; +} + +- (void)setModelRows:(RemoveRowsTableModel::Rows*)modelRows + fromControllerRows:(NSIndexSet*)rows { + if ([rows count] == 0) + return; + + if (!model_->HasGroups()) { + for (NSUInteger i = [rows firstIndex]; + i != NSNotFound; + i = [rows indexGreaterThanIndex:i]) { + modelRows->insert(i); + } + return; + } + + NSUInteger offset = 1; + for (NSUInteger i = [rows firstIndex]; + i != NSNotFound; + i = [rows indexGreaterThanIndex:i]) { + int group = [self groupIDForControllerRow:i]; + offset = [self offsetForGroupID:group startingOffset:offset]; + modelRows->insert(i - offset); + } +} + +- (NSIndexSet*)controllerRowsForModelRowsInRange:(NSRange)range { + if (!model_->HasGroups()) + return [NSIndexSet indexSetWithIndexesInRange:range]; + NSMutableIndexSet* indexes = [NSMutableIndexSet indexSet]; + NSUInteger offset = 1; + for (NSUInteger i = range.location; i < NSMaxRange(range); ++i) { + int group = model_->GetGroupID(i); + offset = [self offsetForGroupID:group startingOffset:offset]; + [indexes addIndex:i + offset]; + } + return indexes; +} + +- (void)modelDidAddItemsInRange:(NSRange)range { + NSMutableArray* rows = [NSMutableArray arrayWithCapacity:range.length]; + for (NSUInteger i=range.location; i<NSMaxRange(range); ++i) + [rows addObject:[self columnValuesForRow:i]]; + [self insertObjects:rows + atArrangedObjectIndexes:[self controllerRowsForModelRowsInRange:range]]; +} + +- (void)modelDidRemoveItemsInRange:(NSRange)range { + NSMutableIndexSet* indexes = + [NSMutableIndexSet indexSetWithIndexesInRange:range]; + if (model_->HasGroups()) { + // When this method is called, the model has already removed items, so + // accessing items in the model from |range.location| on may not be possible + // anymore. Therefore we use the item right before that, if it exists. + NSUInteger offset = 0; + if (range.location > 0) { + int last_group = model_->GetGroupID(range.location - 1); + offset = [self offsetForGroupID:last_group]; + } + [indexes shiftIndexesStartingAtIndex:0 by:offset]; + for (NSUInteger row = range.location + offset; + row < NSMaxRange(range) + offset; + ++row) { + if ([self tableView:nil isGroupRow:row]) { + // Skip over group rows. + [indexes shiftIndexesStartingAtIndex:row by:1]; + offset++; + } + } + } + [self removeObjectsAtArrangedObjectIndexes:indexes]; +} + +- (NSDictionary*)columnValuesForRow:(NSInteger)row { + NSMutableDictionary* dict = [NSMutableDictionary dictionary]; + if (model_->HasGroups()) { + [dict setObject:[NSNumber numberWithInt:model_->GetGroupID(row)] + forKey:kGroupID]; + } + for (NSString* identifier in columns_.get()) { + int column_id = [[columns_ objectForKey:identifier] intValue]; + std::wstring text = model_->GetText(row, column_id); + [dict setObject:base::SysWideToNSString(text) forKey:identifier]; + } + return dict; +} + +// Overridden from NSArrayController ----------------------------------------- + +- (BOOL)canRemove { + if (!model_) + return NO; + RemoveRowsTableModel::Rows rows; + [self setModelRows:&rows fromControllerRows:[self selectionIndexes]]; + return model_->CanRemoveRows(rows); +} + +- (IBAction)remove:(id)sender { + RemoveRowsTableModel::Rows rows; + [self setModelRows:&rows fromControllerRows:[self selectionIndexes]]; + model_->RemoveRows(rows); +} + +// Table View Delegate -------------------------------------------------------- + +- (BOOL)tableView:(NSTableView*)tv isGroupRow:(NSInteger)row { + NSDictionary* values = [[self arrangedObjects] objectAtIndex:row]; + return [[values objectForKey:kIsGroupRow] boolValue]; +} + +- (NSIndexSet*)tableView:(NSTableView*)tableView + selectionIndexesForProposedSelection:(NSIndexSet*)proposedIndexes { + NSMutableIndexSet* indexes = [proposedIndexes mutableCopy]; + for (NSUInteger i = [proposedIndexes firstIndex]; + i != NSNotFound; + i = [proposedIndexes indexGreaterThanIndex:i]) { + if ([self tableView:tableView isGroupRow:i]) { + [indexes removeIndex:i]; + NSUInteger row = i + 1; + while (row < [[self arrangedObjects] count] && + ![self tableView:tableView isGroupRow:row]) + [indexes addIndex:row++]; + } + } + return indexes; +} + +// Actions -------------------------------------------------------------------- + +- (IBAction)removeAll:(id)sender { + model_->RemoveAll(); +} + +@end diff --git a/chrome/browser/cocoa/table_model_array_controller_unittest.mm b/chrome/browser/cocoa/table_model_array_controller_unittest.mm new file mode 100644 index 0000000..4f32358 --- /dev/null +++ b/chrome/browser/cocoa/table_model_array_controller_unittest.mm @@ -0,0 +1,168 @@ +// 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. + +#import "chrome/browser/cocoa/table_model_array_controller.h" + +#include "base/auto_reset.h" +#include "base/command_line.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/cocoa/browser_test_helper.h" +#import "chrome/browser/cocoa/cocoa_test_helper.h" +#include "chrome/browser/mock_plugin_exceptions_table_model.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/testing_profile.h" +#include "grit/generated_resources.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gtest_mac.h" +#include "webkit/glue/plugins/webplugininfo.h" + +namespace { + +class TableModelArrayControllerTest : public CocoaTest { + public: + TableModelArrayControllerTest() + : command_line_(CommandLine::ForCurrentProcess(), + *CommandLine::ForCurrentProcess()) {} + + virtual void SetUp() { + CocoaTest::SetUp(); + + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableResourceContentSettings); + + TestingProfile* profile = browser_helper_.profile(); + 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); + + model_.reset(new MockPluginExceptionsTableModel(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); + WebPluginInfo blurp_plugin; + blurp_plugin.path = FilePath(FILE_PATH_LITERAL("blurp")); + blurp_plugin.name = ASCIIToUTF16("BlurpPlugin"); + blurp_plugin.enabled = true; + plugins.push_back(blurp_plugin); + + model_->set_plugins(plugins); + model_->LoadSettings(); + + id content = [NSMutableArray array]; + controller_.reset( + [[TableModelArrayController alloc] initWithContent:content]); + NSDictionary* columns = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:IDS_EXCEPTIONS_HOSTNAME_HEADER], @"title", + [NSNumber numberWithInt:IDS_EXCEPTIONS_ACTION_HEADER], @"action", + nil]; + [controller_.get() bindToTableModel:model_.get() + withColumns:columns + groupTitleColumn:@"title"]; + } + + protected: + BrowserTestHelper browser_helper_; + scoped_ptr<MockPluginExceptionsTableModel> model_; + scoped_nsobject<TableModelArrayController> controller_; + + private: + AutoReset<CommandLine> command_line_; +}; + +TEST_F(TableModelArrayControllerTest, CheckTitles) { + NSArray* titles = [[controller_.get() arrangedObjects] valueForKey:@"title"]; + EXPECT_NSEQ(@"(\n" + @" FooPlugin,\n" + @" \"[*.]example.com\",\n" + @" BarPlugin,\n" + @" \"[*.]example.com\",\n" + @" \"[*.]moose.org\"\n" + @")", + [titles description]); +} + +TEST_F(TableModelArrayControllerTest, RemoveRows) { + NSArrayController* controller = controller_.get(); + [controller setSelectionIndex:1]; + [controller remove:nil]; + NSArray* titles = [[controller arrangedObjects] valueForKey:@"title"]; + EXPECT_NSEQ(@"(\n" + @" BarPlugin,\n" + @" \"[*.]example.com\",\n" + @" \"[*.]moose.org\"\n" + @")", + [titles description]); + + [controller setSelectionIndex:2]; + [controller remove:nil]; + titles = [[controller arrangedObjects] valueForKey:@"title"]; + EXPECT_NSEQ(@"(\n" + @" BarPlugin,\n" + @" \"[*.]example.com\"\n" + @")", + [titles description]); +} + +TEST_F(TableModelArrayControllerTest, RemoveAll) { + [controller_.get() removeAll:nil]; + EXPECT_EQ(0u, [[controller_.get() arrangedObjects] count]); +} + +TEST_F(TableModelArrayControllerTest, AddException) { + TestingProfile* profile = browser_helper_.profile(); + HostContentSettingsMap* map = profile->GetHostContentSettingsMap(); + HostContentSettingsMap::Pattern example_com("[*.]example.com"); + map->SetContentSetting(example_com, + CONTENT_SETTINGS_TYPE_PLUGINS, + "blurp", + CONTENT_SETTING_BLOCK); + + NSArrayController* controller = controller_.get(); + NSArray* titles = [[controller arrangedObjects] valueForKey:@"title"]; + EXPECT_NSEQ(@"(\n" + @" FooPlugin,\n" + @" \"[*.]example.com\",\n" + @" BarPlugin,\n" + @" \"[*.]example.com\",\n" + @" \"[*.]moose.org\",\n" + @" BlurpPlugin,\n" + @" \"[*.]example.com\"\n" + @")", + [titles description]); + NSMutableIndexSet* indexes = [NSMutableIndexSet indexSetWithIndex:1]; + [indexes addIndex:6]; + [controller setSelectionIndexes:indexes]; + [controller remove:nil]; + titles = [[controller arrangedObjects] valueForKey:@"title"]; + EXPECT_NSEQ(@"(\n" + @" BarPlugin,\n" + @" \"[*.]example.com\",\n" + @" \"[*.]moose.org\"\n" + @")", + [titles description]); +} + +} // namespace diff --git a/chrome/browser/mock_plugin_exceptions_table_model.cc b/chrome/browser/mock_plugin_exceptions_table_model.cc new file mode 100644 index 0000000..d74dfa0 --- /dev/null +++ b/chrome/browser/mock_plugin_exceptions_table_model.cc @@ -0,0 +1,17 @@ +// 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/mock_plugin_exceptions_table_model.h" + +#include "webkit/glue/plugins/webplugininfo.h" + +void MockPluginExceptionsTableModel::set_plugins( + const std::vector<WebPluginInfo>& plugins) { + plugins_ = plugins; +} + +void MockPluginExceptionsTableModel::GetPlugins( + std::vector<WebPluginInfo>* plugins) { + *plugins = plugins_; +} diff --git a/chrome/browser/mock_plugin_exceptions_table_model.h b/chrome/browser/mock_plugin_exceptions_table_model.h new file mode 100644 index 0000000..4cedcd3 --- /dev/null +++ b/chrome/browser/mock_plugin_exceptions_table_model.h @@ -0,0 +1,29 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MOCK_PLUGIN_EXCEPTIONS_TABLE_MODEL_H_ +#define CHROME_BROWSER_MOCK_PLUGIN_EXCEPTIONS_TABLE_MODEL_H_ +#pragma once + +#include <vector> + +#include "chrome/browser/plugin_exceptions_table_model.h" + +class MockPluginExceptionsTableModel : public PluginExceptionsTableModel { + public: + MockPluginExceptionsTableModel(HostContentSettingsMap* map, + HostContentSettingsMap* otr_map) + : PluginExceptionsTableModel(map, otr_map) {} + virtual ~MockPluginExceptionsTableModel() {} + + void set_plugins(const std::vector<WebPluginInfo>& plugins); + + protected: + virtual void GetPlugins(std::vector<WebPluginInfo>* plugins); + + private: + std::vector<WebPluginInfo> plugins_; +}; + +#endif // CHROME_BROWSER_MOCK_PLUGIN_EXCEPTIONS_TABLE_MODEL_H_ diff --git a/chrome/browser/plugin_exceptions_table_model.cc b/chrome/browser/plugin_exceptions_table_model.cc index f610432..18d76a6 100644 --- a/chrome/browser/plugin_exceptions_table_model.cc +++ b/chrome/browser/plugin_exceptions_table_model.cc @@ -34,6 +34,7 @@ void PluginExceptionsTableModel::RemoveRows(const Rows& rows) { // Iterate in reverse over the rows to get the indexes right. for (Rows::const_reverse_iterator it = rows.rbegin(); it != rows.rend(); ++it) { + DCHECK_LT(*it, settings_.size()); SettingsEntry& entry = settings_[*it]; HostContentSettingsMap* map = entry.is_otr ? otr_map_ : map_; map->SetContentSetting(entry.pattern, @@ -59,16 +60,14 @@ void PluginExceptionsTableModel::RemoveRows(const Rows& rows) { } 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); - } + 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); + observer_->OnModelChanged(); } int PluginExceptionsTableModel::RowCount() { diff --git a/chrome/browser/plugin_exceptions_table_model_unittest.cc b/chrome/browser/plugin_exceptions_table_model_unittest.cc index 7720287..69db0a1 100644 --- a/chrome/browser/plugin_exceptions_table_model_unittest.cc +++ b/chrome/browser/plugin_exceptions_table_model_unittest.cc @@ -3,9 +3,10 @@ // found in the LICENSE file. #include "app/table_model_observer.h" +#include "base/auto_reset.h" #include "base/command_line.h" #include "base/utf_string_conversions.h" -#include "chrome/browser/plugin_exceptions_table_model.h" +#include "chrome/browser/mock_plugin_exceptions_table_model.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "chrome/test/testing_pref_service.h" @@ -26,31 +27,12 @@ class MockTableModelObserver : public TableModelObserver { 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()) {} + command_line_(CommandLine::ForCurrentProcess(), + *CommandLine::ForCurrentProcess()) {} virtual void SetUp() { CommandLine::ForCurrentProcess()->AppendSwitch( @@ -75,7 +57,7 @@ class PluginExceptionsTableModelTest : public testing::Test { "bar", CONTENT_SETTING_ALLOW); - table_model_.reset(new TestingPluginExceptionsTableModel(map, NULL)); + table_model_.reset(new MockPluginExceptionsTableModel(map, NULL)); std::vector<WebPluginInfo> plugins; WebPluginInfo foo_plugin; @@ -93,10 +75,6 @@ class PluginExceptionsTableModelTest : public testing::Test { table_model_->ReloadSettings(); } - virtual void TearDown() { - *CommandLine::ForCurrentProcess() = command_line_; - } - protected: void CheckInvariants() { typedef std::deque<PluginExceptionsTableModel::SettingsEntry> Entries; @@ -136,10 +114,10 @@ class PluginExceptionsTableModelTest : public testing::Test { ChromeThread ui_thread_; scoped_ptr<TestingProfile> profile_; - scoped_ptr<TestingPluginExceptionsTableModel> table_model_; + scoped_ptr<MockPluginExceptionsTableModel> table_model_; private: - CommandLine command_line_; + AutoReset<CommandLine> command_line_; }; TEST_F(PluginExceptionsTableModelTest, Basic) { @@ -179,7 +157,7 @@ TEST_F(PluginExceptionsTableModelTest, RemoveAllRows) { MockTableModelObserver observer; table_model_->SetObserver(&observer); - EXPECT_CALL(observer, OnItemsRemoved(0, 3)); + EXPECT_CALL(observer, OnModelChanged()); table_model_->RemoveAll(); EXPECT_EQ(0, table_model_->RowCount()); EXPECT_EQ(0, static_cast<int>(table_model_->GetGroups().size())); |