| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/permissions/model/permissions_tab_helper.h" |
| |
| #import "base/timer/timer.h" |
| #import "ios/chrome/browser/infobars/model/infobar_ios.h" |
| #import "ios/chrome/browser/infobars/model/infobar_manager_impl.h" |
| #import "ios/chrome/browser/infobars/model/overlays/infobar_overlay_request_inserter.h" |
| #import "ios/chrome/browser/infobars/model/overlays/infobar_overlay_util.h" |
| #import "ios/chrome/browser/overlays/model/public/infobar_banner/infobar_banner_placeholder_request_config.h" |
| #import "ios/chrome/browser/overlays/model/public/overlay_callback_manager.h" |
| #import "ios/chrome/browser/overlays/model/public/overlay_request_queue.h" |
| #import "ios/chrome/browser/overlays/model/public/overlay_request_queue_util.h" |
| #import "ios/chrome/browser/overlays/model/public/overlay_response.h" |
| #import "ios/chrome/browser/overlays/model/public/web_content_area/permissions_dialog_overlay.h" |
| #import "ios/chrome/browser/permissions/model/permissions_infobar_delegate.h" |
| |
| namespace { |
| |
| constexpr base::TimeDelta kTimeout = base::Milliseconds(250); |
| |
| // Completion callback for permissions alert overlay. |
| void HandlePermissionDialogResponse( |
| web::WebStatePermissionDecisionHandler handler, |
| OverlayResponse* response) { |
| PermissionsDialogResponse* dialog_response = |
| response ? response->GetInfo<PermissionsDialogResponse>() : nullptr; |
| web::PermissionDecision decision = |
| dialog_response && dialog_response->capture_allow() |
| ? web::PermissionDecisionGrant |
| : web::PermissionDecisionDeny; |
| handler(decision); |
| } |
| |
| } // namespace |
| |
| PermissionsTabHelper::PermissionsTabHelper(web::WebState* web_state) |
| : web_state_(web_state) { |
| DCHECK(web_state); |
| permissions_to_state_ = [web_state->GetStatesForAllPermissions() mutableCopy]; |
| banner_queue_ = OverlayRequestQueue::FromWebState( |
| web_state, OverlayModality::kInfobarBanner); |
| inserter_ = InfobarOverlayRequestInserter::FromWebState(web_state); |
| web_state_->AddObserver(this); |
| } |
| |
| PermissionsTabHelper::~PermissionsTabHelper() {} |
| |
| void PermissionsTabHelper:: |
| PresentPermissionsDecisionDialogWithCompletionHandler( |
| NSArray<NSNumber*>* permissions, |
| web::WebStatePermissionDecisionHandler handler) { |
| std::unique_ptr<OverlayRequest> request = |
| OverlayRequest::CreateWithConfig<PermissionsDialogRequest>( |
| web_state_->GetVisibleURL(), permissions); |
| request->GetCallbackManager()->AddCompletionCallback( |
| base::BindOnce(&HandlePermissionDialogResponse, handler)); |
| OverlayRequestQueue::FromWebState(web_state_, |
| OverlayModality::kWebContentArea) |
| ->AddRequest(std::move(request)); |
| } |
| |
| void PermissionsTabHelper::PermissionStateChanged(web::WebState* web_state, |
| web::Permission permission) { |
| DCHECK_EQ(web_state_, web_state); |
| web::PermissionState new_state = |
| web_state_->GetStateForPermission(permission); |
| // Removes infobar if no permission is accessible. |
| if (new_state == web::PermissionStateNotAccessible) { |
| permissions_to_state_[@(permission)] = @(new_state); |
| BOOL shouldRemoveInfobar = YES; |
| for (NSNumber* permission_number in permissions_to_state_) { |
| if (permissions_to_state_[permission_number].unsignedIntegerValue > |
| web::PermissionStateNotAccessible) { |
| shouldRemoveInfobar = NO; |
| break; |
| } |
| } |
| if (infobar_ != nullptr) { |
| if (shouldRemoveInfobar) { |
| infobars::InfoBarManager* infobar_manager = |
| InfoBarManagerImpl::FromWebState(web_state_); |
| infobar_manager->RemoveInfoBar(infobar_); |
| } else { |
| UpdateIsInfoBarAccepted(); |
| } |
| } |
| return; |
| } |
| // Adds/replaces infobar if previous state was "NotAccessible". |
| if (permissions_to_state_[@(permission)].unsignedIntegerValue == |
| web::PermissionStateNotAccessible) { |
| // Delays infobar creation in case that multiple permissions are enabled |
| // simultaneously to show one infobar with a message that says so, instead |
| // of showing multiple infobar banners back to back. |
| if (!timer_.IsRunning()) { |
| recently_accessible_permissions_ = [NSMutableArray array]; |
| timer_.Start(FROM_HERE, kTimeout, this, |
| &PermissionsTabHelper::ShowInfoBar); |
| } |
| [recently_accessible_permissions_ addObject:@(permission)]; |
| } |
| permissions_to_state_[@(permission)] = @(new_state); |
| if (infobar_ != nullptr) { |
| UpdateIsInfoBarAccepted(); |
| } |
| } |
| |
| void PermissionsTabHelper::WebStateDestroyed(web::WebState* web_state) { |
| DCHECK_EQ(web_state_, web_state); |
| DCHECK(banner_queue_); |
| web_state_->RemoveObserver(this); |
| |
| web_state_ = nullptr; |
| banner_queue_ = nullptr; |
| inserter_ = nullptr; |
| } |
| |
| void PermissionsTabHelper::OnInfoBarRemoved(infobars::InfoBar* infobar, |
| bool animate) { |
| if (infobar == infobar_) { |
| infobar_manager_scoped_observation_.Reset(); |
| infobar_ = nullptr; |
| } |
| } |
| |
| void PermissionsTabHelper::OnManagerWillBeDestroyed( |
| infobars::InfoBarManager* manager) { |
| DCHECK(infobar_manager_scoped_observation_.IsObservingSource(manager)); |
| infobar_manager_scoped_observation_.Reset(); |
| } |
| |
| void PermissionsTabHelper::ShowInfoBar() { |
| infobars::InfoBarManager* infobar_manager = |
| InfoBarManagerImpl::FromWebState(web_state_); |
| if (!infobar_manager_scoped_observation_.IsObservingSource(infobar_manager)) { |
| infobar_manager_scoped_observation_.Observe(infobar_manager); |
| } |
| |
| std::unique_ptr<PermissionsInfobarDelegate> delegate( |
| std::make_unique<PermissionsInfobarDelegate>( |
| recently_accessible_permissions_, web_state_)); |
| |
| BOOL first_activation = infobar_ == nullptr; |
| std::unique_ptr<infobars::InfoBar> infobar = std::make_unique<InfoBarIOS>( |
| InfobarType::kInfobarTypePermissions, std::move(delegate)); |
| infobar_ = infobar_manager->AddInfoBar(std::move(infobar), true); |
| |
| UpdateIsInfoBarAccepted(); |
| // Infobar replacement does not display the banner a second time, hence here |
| // we use overlay inserter to manually show it again. |
| if (!first_activation) { |
| size_t index = 0; |
| bool request_found = GetInfobarOverlayRequestIndex( |
| banner_queue_, static_cast<InfoBarIOS*>(infobar_), &index); |
| if (request_found) { // The new banner is already shown. |
| return; |
| } |
| InsertParams params(static_cast<InfoBarIOS*>(infobar_)); |
| params.overlay_type = InfobarOverlayType::kBanner; |
| params.insertion_index = banner_queue_->size(); |
| params.source = InfobarOverlayInsertionSource::kInfoBarDelegate; |
| inserter_->InsertOverlayRequest(params); |
| } |
| } |
| |
| void PermissionsTabHelper::UpdateIsInfoBarAccepted() { |
| if (infobar_ == nullptr) { |
| return; |
| } |
| BOOL accepted = NO; |
| for (NSNumber* permission in permissions_to_state_) { |
| if (permissions_to_state_[permission].unsignedIntegerValue == |
| web::PermissionStateAllowed) { |
| accepted = YES; |
| break; |
| } |
| } |
| static_cast<InfoBarIOS*>(infobar_)->set_accepted(accepted); |
| } |