OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/settings/block_popups_collection_view_controller.
h" |
| 6 |
| 7 #include "base/ios/weak_nsobject.h" |
| 8 #include "base/logging.h" |
| 9 #import "base/mac/foundation_util.h" |
| 10 #include "base/mac/scoped_nsobject.h" |
| 11 #include "base/strings/sys_string_conversions.h" |
| 12 #include "base/values.h" |
| 13 #include "components/content_settings/core/browser/host_content_settings_map.h" |
| 14 #include "components/content_settings/core/common/content_settings_pattern.h" |
| 15 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 16 #include "ios/chrome/browser/content_settings/host_content_settings_map_factory.
h" |
| 17 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_switch_item
.h" |
| 18 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h
" |
| 19 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h" |
| 20 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" |
| 21 #import "ios/chrome/browser/ui/settings/utils/content_setting_backed_boolean.h" |
| 22 #include "ios/chrome/grit/ios_strings.h" |
| 23 #import "ios/third_party/material_components_ios/src/components/Palettes/src/Mat
erialPalettes.h" |
| 24 #include "ui/base/l10n/l10n_util.h" |
| 25 #include "ui/base/l10n/l10n_util_mac.h" |
| 26 |
| 27 namespace { |
| 28 |
| 29 typedef NS_ENUM(NSInteger, SectionIdentifier) { |
| 30 SectionIdentifierMainSwitch = kSectionIdentifierEnumZero, |
| 31 SectionIdentifierExceptions, |
| 32 }; |
| 33 |
| 34 typedef NS_ENUM(NSInteger, ItemType) { |
| 35 ItemTypeMainSwitch = kItemTypeEnumZero, |
| 36 ItemTypeHeader, |
| 37 ItemTypeException, |
| 38 }; |
| 39 |
| 40 } // namespace |
| 41 |
| 42 @interface BlockPopupsCollectionViewController ()<BooleanObserver> { |
| 43 ios::ChromeBrowserState* _browserState; // weak |
| 44 |
| 45 // List of url patterns that are allowed to display popups. |
| 46 base::ListValue _exceptions; |
| 47 |
| 48 // The observable boolean that binds to the "Disable Popups" setting state. |
| 49 base::scoped_nsobject<ContentSettingBackedBoolean> _disablePopupsSetting; |
| 50 |
| 51 // The item related to the switch for the "Disable Popups" setting. |
| 52 base::scoped_nsobject<CollectionViewSwitchItem> _blockPopupsItem; |
| 53 } |
| 54 |
| 55 // Fetch the urls that can display popups and add them to |_exceptions|. |
| 56 - (void)populateExceptionsList; |
| 57 |
| 58 // Returns YES if popups are currently blocked by default, NO otherwise. |
| 59 - (BOOL)popupsCurrentlyBlocked; |
| 60 |
| 61 @end |
| 62 |
| 63 @implementation BlockPopupsCollectionViewController |
| 64 |
| 65 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState { |
| 66 DCHECK(browserState); |
| 67 self = [super initWithStyle:CollectionViewControllerStyleAppBar]; |
| 68 if (self) { |
| 69 _browserState = browserState; |
| 70 HostContentSettingsMap* settingsMap = |
| 71 ios::HostContentSettingsMapFactory::GetForBrowserState(_browserState); |
| 72 _disablePopupsSetting.reset([[ContentSettingBackedBoolean alloc] |
| 73 initWithHostContentSettingsMap:settingsMap |
| 74 settingID:CONTENT_SETTINGS_TYPE_POPUPS |
| 75 inverted:YES]); |
| 76 [_disablePopupsSetting setObserver:self]; |
| 77 self.title = l10n_util::GetNSString(IDS_IOS_BLOCK_POPUPS); |
| 78 self.collectionViewAccessibilityIdentifier = |
| 79 @"block_popups_settings_view_controller"; |
| 80 |
| 81 [self populateExceptionsList]; |
| 82 [self updateEditButton]; |
| 83 [self loadModel]; |
| 84 } |
| 85 return self; |
| 86 } |
| 87 |
| 88 - (void)dealloc { |
| 89 [_disablePopupsSetting setObserver:nil]; |
| 90 [super dealloc]; |
| 91 } |
| 92 |
| 93 #pragma mark - SettingsRootCollectionViewController |
| 94 |
| 95 - (void)loadModel { |
| 96 [super loadModel]; |
| 97 |
| 98 CollectionViewModel* model = self.collectionViewModel; |
| 99 |
| 100 // Block popups switch. |
| 101 [model addSectionWithIdentifier:SectionIdentifierMainSwitch]; |
| 102 |
| 103 _blockPopupsItem.reset( |
| 104 [[CollectionViewSwitchItem alloc] initWithType:ItemTypeMainSwitch]); |
| 105 _blockPopupsItem.get().text = l10n_util::GetNSString(IDS_IOS_BLOCK_POPUPS); |
| 106 _blockPopupsItem.get().on = [_disablePopupsSetting value]; |
| 107 _blockPopupsItem.get().accessibilityIdentifier = |
| 108 @"blockPopupsContentView_switch"; |
| 109 [model addItem:_blockPopupsItem |
| 110 toSectionWithIdentifier:SectionIdentifierMainSwitch]; |
| 111 |
| 112 if ([self popupsCurrentlyBlocked] && _exceptions.GetSize()) { |
| 113 [self populateExceptionsItems]; |
| 114 } |
| 115 } |
| 116 |
| 117 - (BOOL)shouldShowEditButton { |
| 118 return [self popupsCurrentlyBlocked]; |
| 119 } |
| 120 |
| 121 - (BOOL)editButtonEnabled { |
| 122 return _exceptions.GetSize() > 0; |
| 123 } |
| 124 |
| 125 #pragma mark - MDCCollectionViewEditingDelegate |
| 126 |
| 127 - (BOOL)collectionViewAllowsEditing:(UICollectionView*)collectionView { |
| 128 return YES; |
| 129 } |
| 130 |
| 131 #pragma mark - UICollectionViewDataSource |
| 132 |
| 133 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView |
| 134 cellForItemAtIndexPath:(NSIndexPath*)indexPath { |
| 135 UICollectionViewCell* cell = |
| 136 [super collectionView:collectionView cellForItemAtIndexPath:indexPath]; |
| 137 |
| 138 if ([self.collectionViewModel itemTypeForIndexPath:indexPath] == |
| 139 ItemTypeMainSwitch) { |
| 140 CollectionViewSwitchCell* switchCell = |
| 141 base::mac::ObjCCastStrict<CollectionViewSwitchCell>(cell); |
| 142 [switchCell.switchView addTarget:self |
| 143 action:@selector(blockPopupsSwitchChanged:) |
| 144 forControlEvents:UIControlEventValueChanged]; |
| 145 } |
| 146 return cell; |
| 147 } |
| 148 |
| 149 - (UICollectionReusableView*)collectionView:(UICollectionView*)collectionView |
| 150 viewForSupplementaryElementOfKind:(NSString*)kind |
| 151 atIndexPath:(NSIndexPath*)indexPath { |
| 152 UICollectionReusableView* view = [super collectionView:collectionView |
| 153 viewForSupplementaryElementOfKind:kind |
| 154 atIndexPath:indexPath]; |
| 155 MDCCollectionViewTextCell* textCell = |
| 156 base::mac::ObjCCast<MDCCollectionViewTextCell>(view); |
| 157 if (textCell) { |
| 158 textCell.textLabel.textColor = [[MDCPalette greyPalette] tint500]; |
| 159 } |
| 160 return view; |
| 161 }; |
| 162 |
| 163 #pragma mark - MDCCollectionViewEditingDelegate |
| 164 |
| 165 - (BOOL)collectionView:(UICollectionView*)collectionView |
| 166 canEditItemAtIndexPath:(NSIndexPath*)indexPath { |
| 167 // Any item in SectionIdentifierExceptions is editable. |
| 168 return [self.collectionViewModel |
| 169 sectionIdentifierForSection:indexPath.section] == |
| 170 SectionIdentifierExceptions; |
| 171 } |
| 172 |
| 173 - (void)collectionView:(UICollectionView*)collectionView |
| 174 willDeleteItemsAtIndexPaths:(NSArray*)indexPaths { |
| 175 for (NSIndexPath* indexPath in indexPaths) { |
| 176 size_t urlIndex = indexPath.item; |
| 177 std::string urlToRemove; |
| 178 _exceptions.GetString(urlIndex, &urlToRemove); |
| 179 |
| 180 // Remove the exception for the site by resetting its popup setting to the |
| 181 // default. |
| 182 ios::HostContentSettingsMapFactory::GetForBrowserState(_browserState) |
| 183 ->SetContentSettingCustomScope( |
| 184 ContentSettingsPattern::FromString(urlToRemove), |
| 185 ContentSettingsPattern::Wildcard(), CONTENT_SETTINGS_TYPE_POPUPS, |
| 186 std::string(), CONTENT_SETTING_DEFAULT); |
| 187 |
| 188 // Remove the site from |_exceptions|. |
| 189 _exceptions.Remove(urlIndex, NULL); |
| 190 } |
| 191 |
| 192 // Must call super at the end of the child implementation. |
| 193 [super collectionView:collectionView willDeleteItemsAtIndexPaths:indexPaths]; |
| 194 } |
| 195 |
| 196 - (void)collectionView:(UICollectionView*)collectionView |
| 197 didDeleteItemsAtIndexPaths:(NSArray*)indexPaths { |
| 198 // The only editable section is the block popups exceptions section. |
| 199 if ([self.collectionViewModel |
| 200 hasSectionForSectionIdentifier:SectionIdentifierExceptions]) { |
| 201 NSInteger exceptionsSectionIndex = [self.collectionViewModel |
| 202 sectionForSectionIdentifier:SectionIdentifierExceptions]; |
| 203 if ([collectionView numberOfItemsInSection:exceptionsSectionIndex] == 0) { |
| 204 base::WeakNSObject<BlockPopupsCollectionViewController> weakSelf(self); |
| 205 [self.collectionView performBatchUpdates:^{ |
| 206 base::scoped_nsobject<BlockPopupsCollectionViewController> strongSelf( |
| 207 [weakSelf retain]); |
| 208 if (!strongSelf) { |
| 209 return; |
| 210 } |
| 211 NSInteger section = [strongSelf.get().collectionViewModel |
| 212 sectionForSectionIdentifier:SectionIdentifierExceptions]; |
| 213 [strongSelf.get().collectionViewModel |
| 214 removeSectionWithIdentifier:SectionIdentifierExceptions]; |
| 215 [strongSelf.get().collectionView |
| 216 deleteSections:[NSIndexSet indexSetWithIndex:section]]; |
| 217 } |
| 218 completion:nil]; |
| 219 } |
| 220 } |
| 221 } |
| 222 |
| 223 #pragma mark MDCCollectionViewStylingDelegate |
| 224 |
| 225 - (BOOL)collectionView:(UICollectionView*)collectionView |
| 226 hidesInkViewAtIndexPath:(NSIndexPath*)indexPath { |
| 227 NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath]; |
| 228 switch (type) { |
| 229 case ItemTypeMainSwitch: |
| 230 return YES; |
| 231 default: |
| 232 return NO; |
| 233 } |
| 234 } |
| 235 |
| 236 #pragma mark - BooleanObserver |
| 237 |
| 238 - (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean { |
| 239 DCHECK_EQ(observableBoolean, _disablePopupsSetting.get()); |
| 240 |
| 241 // Update the item. |
| 242 _blockPopupsItem.get().on = [_disablePopupsSetting value]; |
| 243 |
| 244 // Update the cell. |
| 245 [self reconfigureCellsForItems:@[ _blockPopupsItem ] |
| 246 inSectionWithIdentifier:SectionIdentifierMainSwitch]; |
| 247 |
| 248 // Update the rest of the UI. |
| 249 [self.editor setEditing:NO]; |
| 250 [self updateEditButton]; |
| 251 [self layoutSections:[_disablePopupsSetting value]]; |
| 252 } |
| 253 |
| 254 #pragma mark - Actions |
| 255 |
| 256 - (void)blockPopupsSwitchChanged:(UISwitch*)switchView { |
| 257 // Update the setting. |
| 258 [_disablePopupsSetting setValue:switchView.on]; |
| 259 |
| 260 // Update the item. |
| 261 _blockPopupsItem.get().on = [_disablePopupsSetting value]; |
| 262 |
| 263 // Update the rest of the UI. |
| 264 [self.editor setEditing:NO]; |
| 265 [self updateEditButton]; |
| 266 [self layoutSections:switchView.on]; |
| 267 } |
| 268 |
| 269 #pragma mark - Private |
| 270 |
| 271 - (BOOL)popupsCurrentlyBlocked { |
| 272 return [_disablePopupsSetting value]; |
| 273 } |
| 274 |
| 275 - (void)populateExceptionsList { |
| 276 // The body of this method was mostly copied from |
| 277 // chrome/browser/ui/webui/options/content_settings_handler.cc and simplified |
| 278 // to only deal with urls/patterns that allow popups. |
| 279 ContentSettingsForOneType entries; |
| 280 ios::HostContentSettingsMapFactory::GetForBrowserState(_browserState) |
| 281 ->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_POPUPS, std::string(), |
| 282 &entries); |
| 283 for (size_t i = 0; i < entries.size(); ++i) { |
| 284 // Skip default settings from extensions and policy, and the default content |
| 285 // settings; all of them will affect the default setting UI. |
| 286 if (entries[i].primary_pattern == ContentSettingsPattern::Wildcard() && |
| 287 entries[i].secondary_pattern == ContentSettingsPattern::Wildcard() && |
| 288 entries[i].source != "preference") { |
| 289 continue; |
| 290 } |
| 291 // The content settings UI does not support secondary content settings |
| 292 // pattern yet. For content settings set through the content settings UI the |
| 293 // secondary pattern is by default a wildcard pattern. Hence users are not |
| 294 // able to modify content settings with a secondary pattern other than the |
| 295 // wildcard pattern. So only show settings that the user is able to modify. |
| 296 if (entries[i].secondary_pattern == ContentSettingsPattern::Wildcard() && |
| 297 entries[i].setting == CONTENT_SETTING_ALLOW) { |
| 298 _exceptions.Append( |
| 299 new base::StringValue(entries[i].primary_pattern.ToString())); |
| 300 } else { |
| 301 LOG(ERROR) << "Secondary content settings patterns are not " |
| 302 << "supported by the content settings UI"; |
| 303 } |
| 304 } |
| 305 } |
| 306 |
| 307 - (void)populateExceptionsItems { |
| 308 CollectionViewModel* model = self.collectionViewModel; |
| 309 [model addSectionWithIdentifier:SectionIdentifierExceptions]; |
| 310 |
| 311 CollectionViewTextItem* header = [ |
| 312 [[CollectionViewTextItem alloc] initWithType:ItemTypeHeader] autorelease]; |
| 313 header.text = l10n_util::GetNSString(IDS_IOS_POPUPS_ALLOWED); |
| 314 [model setHeader:header forSectionWithIdentifier:SectionIdentifierExceptions]; |
| 315 |
| 316 for (size_t i = 0; i < _exceptions.GetSize(); ++i) { |
| 317 std::string allowed_url; |
| 318 _exceptions.GetString(i, &allowed_url); |
| 319 CollectionViewTextItem* item = [[[CollectionViewTextItem alloc] |
| 320 initWithType:ItemTypeException] autorelease]; |
| 321 item.text = base::SysUTF8ToNSString(allowed_url); |
| 322 [model addItem:item toSectionWithIdentifier:SectionIdentifierExceptions]; |
| 323 } |
| 324 } |
| 325 |
| 326 - (void)layoutSections:(BOOL)blockPopupsIsOn { |
| 327 BOOL hasExceptions = _exceptions.GetSize(); |
| 328 BOOL exceptionsListShown = [self.collectionViewModel |
| 329 hasSectionForSectionIdentifier:SectionIdentifierExceptions]; |
| 330 |
| 331 if (blockPopupsIsOn && !exceptionsListShown && hasExceptions) { |
| 332 // Animate in the list of exceptions. Animation looks much better if the |
| 333 // section is added at once, rather than row-by-row as each object is added. |
| 334 base::WeakNSObject<BlockPopupsCollectionViewController> weakSelf(self); |
| 335 [self.collectionView performBatchUpdates:^{ |
| 336 base::scoped_nsobject<BlockPopupsCollectionViewController> strongSelf( |
| 337 [weakSelf retain]); |
| 338 if (!strongSelf) |
| 339 return; |
| 340 [strongSelf populateExceptionsItems]; |
| 341 NSUInteger index = [[strongSelf collectionViewModel] |
| 342 sectionForSectionIdentifier:SectionIdentifierExceptions]; |
| 343 [[strongSelf collectionView] |
| 344 insertSections:[NSIndexSet indexSetWithIndex:index]]; |
| 345 } |
| 346 completion:nil]; |
| 347 } else if (!blockPopupsIsOn && exceptionsListShown) { |
| 348 // Make sure the exception section is not shown. |
| 349 base::WeakNSObject<BlockPopupsCollectionViewController> weakSelf(self); |
| 350 [self.collectionView performBatchUpdates:^{ |
| 351 base::scoped_nsobject<BlockPopupsCollectionViewController> strongSelf( |
| 352 [weakSelf retain]); |
| 353 if (!strongSelf) |
| 354 return; |
| 355 NSUInteger index = [[strongSelf collectionViewModel] |
| 356 sectionForSectionIdentifier:SectionIdentifierExceptions]; |
| 357 [[strongSelf collectionViewModel] |
| 358 removeSectionWithIdentifier:SectionIdentifierExceptions]; |
| 359 [[strongSelf collectionView] |
| 360 deleteSections:[NSIndexSet indexSetWithIndex:index]]; |
| 361 } |
| 362 completion:nil]; |
| 363 } |
| 364 } |
| 365 |
| 366 @end |
OLD | NEW |