summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa
diff options
context:
space:
mode:
authorthakis@chromium.org <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-06 02:06:15 +0000
committerthakis@chromium.org <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-06 02:06:15 +0000
commit90e9d489b71ce25252d2a85412d3d82af8a58c17 (patch)
tree85f67dda821a170a116a5c79260094a195bcf6d0 /chrome/browser/cocoa
parentfd170c4d9a633faad9c6b93193539114bfcfeada (diff)
downloadchromium_src-90e9d489b71ce25252d2a85412d3d82af8a58c17.zip
chromium_src-90e9d489b71ce25252d2a85412d3d82af8a58c17.tar.gz
chromium_src-90e9d489b71ce25252d2a85412d3d82af8a58c17.tar.bz2
Mac: Fix another crash in content exceptions dialog.
Also add a couple unit tests. Also fix another potential crash and other problems uncovered by the tests. BUG=37208 TEST=See bug. Also, unittests. Review URL: http://codereview.chromium.org/668046 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@40810 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r--chrome/browser/cocoa/content_exceptions_window_controller.h8
-rw-r--r--chrome/browser/cocoa/content_exceptions_window_controller.mm91
-rw-r--r--chrome/browser/cocoa/content_exceptions_window_controller_unittest.mm194
3 files changed, 265 insertions, 28 deletions
diff --git a/chrome/browser/cocoa/content_exceptions_window_controller.h b/chrome/browser/cocoa/content_exceptions_window_controller.h
index e7eab5e..a42510e0 100644
--- a/chrome/browser/cocoa/content_exceptions_window_controller.h
+++ b/chrome/browser/cocoa/content_exceptions_window_controller.h
@@ -40,7 +40,8 @@ class UpdatingContentSettingsObserver;
// is used to suppress updates at bad times.
BOOL updatesEnabled_;
- // This is non-NULL only while a new element is being added.
+ // This is non-NULL only while a new element is being added and its host
+ // is being edited.
scoped_ptr<HostContentSettingsMap::HostSettingPair> newException_;
}
@@ -52,3 +53,8 @@ class UpdatingContentSettingsObserver;
- (IBAction)removeAllExceptions:(id)sender;
@end
+
+@interface ContentExceptionsWindowController(VisibleForTesting)
+- (void)cancel:(id)sender;
+- (BOOL)editingNewException;
+@end
diff --git a/chrome/browser/cocoa/content_exceptions_window_controller.mm b/chrome/browser/cocoa/content_exceptions_window_controller.mm
index 741a907..4c4c237 100644
--- a/chrome/browser/cocoa/content_exceptions_window_controller.mm
+++ b/chrome/browser/cocoa/content_exceptions_window_controller.mm
@@ -19,6 +19,8 @@
@interface ContentExceptionsWindowController(Private)
- (id)initWithType:(ContentSettingsType)settingsType
settingsMap:(HostContentSettingsMap*)settingsMap;
+- (void)updateRow:(NSInteger)row
+ withEntry:(const HostContentSettingsMap::HostSettingPair&)entry;
- (void)adjustEditingButtons;
- (void)modelDidChange;
- (size_t)menuItemCount;
@@ -160,6 +162,7 @@ static ContentExceptionsWindowController*
model_.reset(new ContentExceptionsTableModel(settingsMap_, settingsType_));
showAsk_ = settingsType_ == CONTENT_SETTINGS_TYPE_COOKIES;
tableObserver_.reset(new UpdatingContentSettingsObserver(self));
+ updatesEnabled_ = YES;
// TODO(thakis): autoremember window rect.
// TODO(thakis): sorting support.
@@ -207,6 +210,10 @@ static ContentExceptionsWindowController*
[self autorelease];
}
+- (BOOL)editingNewException {
+ return newException_.get() != NULL;
+}
+
// Let esc close the window.
- (void)cancel:(id)sender {
if ([tableView_ currentEditor] != nil) {
@@ -248,15 +255,21 @@ static ContentExceptionsWindowController*
}
- (IBAction)addException:(id)sender {
+ if (newException_.get()) {
+ // The invariant is that |newException_| is non-NULL exactly if the host of
+ // a new exception is currently being edited - so there's nothing to do in
+ // that case.
+ return;
+ }
newException_.reset(new HostContentSettingsMap::HostSettingPair);
newException_->first = "example.com";
newException_->second = CONTENT_SETTING_BLOCK;
[tableView_ reloadData];
- [self adjustEditingButtons];
+ [self adjustEditingButtons];
int index = model_->RowCount();
- [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:index]
- byExtendingSelection:NO];
+ NSIndexSet* selectedSet = [NSIndexSet indexSetWithIndex:index];
+ [tableView_ selectRowIndexes:selectedSet byExtendingSelection:NO];
[tableView_ editColumn:0 row:index withEvent:nil select:YES];
}
@@ -313,10 +326,40 @@ static ContentExceptionsWindowController*
return result;
}
+// Updates exception at |row| to contain the data in |entry|.
+- (void)updateRow:(NSInteger)row
+ withEntry:(const HostContentSettingsMap::HostSettingPair&)entry {
+ // TODO(thakis): This apparently moves an edited row to the back of the list.
+ // It's what windows and linux do, but it's kinda sucky. Fix.
+ // http://crbug.com/36904
+ updatesEnabled_ = NO;
+ if (row < model_->RowCount())
+ model_->RemoveException(row);
+ model_->AddException(entry.first, entry.second);
+ updatesEnabled_ = YES;
+ [self modelDidChange];
+
+ // For now, at least re-select the edited element.
+ int newIndex = model_->IndexOfExceptionByHost(entry.first);
+ DCHECK(newIndex != -1);
+ [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex]
+ byExtendingSelection:NO];
+}
+
- (void) tableView:(NSTableView*)tv
setObjectValue:(id)object
forTableColumn:(NSTableColumn*)tableColumn
row:(NSInteger)row {
+ // |-remove:| and |-removeAll:| both call |tableView_ deselectAll:|, which
+ // calls this method if a cell is currently being edited. Do not commit edits
+ // of rows that are about to be deleted.
+ if (!updatesEnabled_) {
+ // If this method gets called, the host filed of the new exception can no
+ // longer be being edited. Reset |newException_| to keep the invariant true.
+ newException_.reset();
+ return;
+ }
+
// Get model object.
bool isNewRow = newException_.get() && row >= model_->RowCount();
const HostContentSettingsMap::HostSettingPair* originalEntry =
@@ -334,32 +377,26 @@ static ContentExceptionsWindowController*
}
// Commit modification, if any.
- // TODO(thakis): This apparently moves an edited row to the back of the list.
- // It's what windows and linux do, but it's kinda sucky. Fix.
- // http://crbug.com/36904
- if (entry != *originalEntry) {
- updatesEnabled_ = NO;
- if (!isNewRow) {
- model_->RemoveException(row);
- } else {
- newException_.reset();
- if (![identifier isEqualToString:@"hostname"]) {
- [tableView_ reloadData];
- [self adjustEditingButtons];
- return; // Commit new rows only when the hostname has been set.
- }
- }
-
- model_->AddException(entry.first, entry.second);
- updatesEnabled_ = YES;
- [self modelDidChange];
-
- // For now, at least re-select the edited element.
+ if (isNewRow) {
+ newException_.reset();
+ if (![identifier isEqualToString:@"hostname"]) {
+ [tableView_ reloadData];
+ [self adjustEditingButtons];
+ return; // Commit new rows only when the hostname has been set.
+ }
int newIndex = model_->IndexOfExceptionByHost(entry.first);
- DCHECK(newIndex != -1);
- [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex]
- byExtendingSelection:NO];
+ if (newIndex != -1) {
+ // The new host was already in the table. Focus existing row instead of
+ // overwriting it with a new one.
+ [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex]
+ byExtendingSelection:NO];
+ [tableView_ reloadData];
+ [self adjustEditingButtons];
+ return;
+ }
}
+ if (entry != *originalEntry || isNewRow)
+ [self updateRow:row withEntry:entry];
}
diff --git a/chrome/browser/cocoa/content_exceptions_window_controller_unittest.mm b/chrome/browser/cocoa/content_exceptions_window_controller_unittest.mm
index d321dc2..8e142c6c 100644
--- a/chrome/browser/cocoa/content_exceptions_window_controller_unittest.mm
+++ b/chrome/browser/cocoa/content_exceptions_window_controller_unittest.mm
@@ -17,6 +17,36 @@
namespace {
+void ProcessEvents() {
+ for (;;) {
+ base::ScopedNSAutoreleasePool pool;
+ NSEvent* next_event = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:nil
+ inMode:NSDefaultRunLoopMode
+ dequeue:YES];
+ if (!next_event) break;
+ [NSApp sendEvent:next_event];
+ }
+}
+
+void SendKeyEvents(NSString* characters) {
+ for (NSUInteger i = 0; i < [characters length]; ++i) {
+ unichar character = [characters characterAtIndex:i];
+ NSString* charAsString = [NSString stringWithCharacters:&character length:1];
+ NSEvent* event = [NSEvent keyEventWithType:NSKeyDown
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:0.0
+ windowNumber:0
+ context:nil
+ characters:charAsString
+ charactersIgnoringModifiers:charAsString
+ isARepeat:NO
+ keyCode:0];
+ [NSApp sendEvent:event];
+ }
+}
+
class ContentExceptionsWindowControllerTest : public CocoaTest {
public:
virtual void SetUp() {
@@ -25,6 +55,37 @@ class ContentExceptionsWindowControllerTest : public CocoaTest {
settingsMap_ = new HostContentSettingsMap(profile);
}
+ ContentExceptionsWindowController* GetController(ContentSettingsType type) {
+ return [ContentExceptionsWindowController showForType:type
+ settingsMap:settingsMap_.get()];
+
+ }
+
+ void ClickAdd(ContentExceptionsWindowController* controller) {
+ [controller addException:nil];
+ ProcessEvents();
+ }
+
+ void ClickRemove(ContentExceptionsWindowController* controller) {
+ [controller removeException:nil];
+ ProcessEvents();
+ }
+
+ void ClickRemoveAll(ContentExceptionsWindowController* controller) {
+ [controller removeAllExceptions:nil];
+ ProcessEvents();
+ }
+
+ void EnterText(NSString* str) {
+ SendKeyEvents(str);
+ ProcessEvents();
+ }
+
+ void HitEscape(ContentExceptionsWindowController* controller) {
+ [controller cancel:nil];
+ ProcessEvents();
+ }
+
protected:
BrowserTestHelper browser_helper_;
scoped_refptr<HostContentSettingsMap> settingsMap_;
@@ -38,4 +99,137 @@ TEST_F(ContentExceptionsWindowControllerTest, Construction) {
[controller close]; // Should autorelease.
}
+// Regression test for http://crbug.com/37137
+TEST_F(ContentExceptionsWindowControllerTest, AddRemove) {
+ ContentExceptionsWindowController* controller =
+ GetController(CONTENT_SETTINGS_TYPE_PLUGINS);
+
+ HostContentSettingsMap::SettingsForOneType settings;
+
+ ClickAdd(controller);
+ settingsMap_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS,
+ &settings);
+ EXPECT_EQ(0u, settings.size());
+
+ ClickRemove(controller);
+
+ EXPECT_FALSE([controller editingNewException]);
+ [controller close];
+
+ settingsMap_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS,
+ &settings);
+ EXPECT_EQ(0u, settings.size());
+}
+
+// Regression test for http://crbug.com/37137
+TEST_F(ContentExceptionsWindowControllerTest, AddRemoveAll) {
+ ContentExceptionsWindowController* controller =
+ GetController(CONTENT_SETTINGS_TYPE_PLUGINS);
+ ClickAdd(controller);
+ ClickRemoveAll(controller);
+
+ EXPECT_FALSE([controller editingNewException]);
+ [controller close];
+
+ HostContentSettingsMap::SettingsForOneType settings;
+ settingsMap_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS,
+ &settings);
+ EXPECT_EQ(0u, settings.size());
+}
+
+TEST_F(ContentExceptionsWindowControllerTest, Add) {
+ ContentExceptionsWindowController* controller =
+ GetController(CONTENT_SETTINGS_TYPE_PLUGINS);
+
+ ClickAdd(controller);
+ EnterText(@"addedhost\n");
+
+ EXPECT_FALSE([controller editingNewException]);
+ [controller close];
+
+ HostContentSettingsMap::SettingsForOneType settings;
+ settingsMap_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS,
+ &settings);
+ EXPECT_EQ(1u, settings.size());
+ EXPECT_EQ("addedhost", settings[0].first);
+}
+
+TEST_F(ContentExceptionsWindowControllerTest, AddEscDoesNotAdd) {
+ ContentExceptionsWindowController* controller =
+ GetController(CONTENT_SETTINGS_TYPE_PLUGINS);
+
+ ClickAdd(controller);
+ EnterText(@"addedhost"); // but do not press enter
+ HitEscape(controller);
+
+ HostContentSettingsMap::SettingsForOneType settings;
+ settingsMap_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS,
+ &settings);
+ EXPECT_EQ(0u, settings.size());
+ EXPECT_FALSE([controller editingNewException]);
+
+ [controller close];
+}
+
+// Regression test for http://crbug.com/37208
+TEST_F(ContentExceptionsWindowControllerTest, AddEditAddAdd) {
+ ContentExceptionsWindowController* controller =
+ GetController(CONTENT_SETTINGS_TYPE_PLUGINS);
+
+ ClickAdd(controller);
+ EnterText(@"testtesttest"); // but do not press enter
+ ClickAdd(controller);
+ ClickAdd(controller);
+
+ EXPECT_TRUE([controller editingNewException]);
+ [controller close];
+
+ HostContentSettingsMap::SettingsForOneType settings;
+ settingsMap_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS,
+ &settings);
+ EXPECT_EQ(0u, settings.size());
+}
+
+TEST_F(ContentExceptionsWindowControllerTest, AddExistingEditAdd) {
+ settingsMap_->SetContentSetting(
+ "myhost", CONTENT_SETTINGS_TYPE_PLUGINS, CONTENT_SETTING_BLOCK);
+
+ ContentExceptionsWindowController* controller =
+ GetController(CONTENT_SETTINGS_TYPE_PLUGINS);
+
+ ClickAdd(controller);
+ EnterText(@"myhost"); // but do not press enter
+ ClickAdd(controller);
+
+ EXPECT_TRUE([controller editingNewException]);
+ [controller close];
+
+
+ HostContentSettingsMap::SettingsForOneType settings;
+ settingsMap_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS,
+ &settings);
+ EXPECT_EQ(1u, settings.size());
+}
+
+TEST_F(ContentExceptionsWindowControllerTest, AddExistingDoesNotOverwrite) {
+ settingsMap_->SetContentSetting(
+ "myhost", CONTENT_SETTINGS_TYPE_COOKIES, CONTENT_SETTING_ASK);
+
+ ContentExceptionsWindowController* controller =
+ GetController(CONTENT_SETTINGS_TYPE_COOKIES);
+
+ ClickAdd(controller);
+ EnterText(@"myhost\n");
+
+ EXPECT_FALSE([controller editingNewException]);
+ [controller close];
+
+ HostContentSettingsMap::SettingsForOneType settings;
+ settingsMap_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_COOKIES,
+ &settings);
+ EXPECT_EQ(1u, settings.size());
+ EXPECT_EQ(CONTENT_SETTING_ASK, settings[0].second);
+}
+
+
} // namespace