chromium/ios/web/web_state/permissions_inttest.mm

// 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 "base/memory/raw_ptr.h"
#import "base/run_loop.h"
#import "base/test/ios/wait_util.h"
#import "base/time/time.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/navigation/reload_type.h"
#import "ios/web/public/permissions/permissions.h"
#import "ios/web/public/test/fakes/fake_web_state_delegate.h"
#import "ios/web/public/test/navigation_test_util.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state.h"
#import "ios/web/public/web_state_observer.h"
#import "ios/web/test/fakes/crw_fake_wk_frame_info.h"
#import "ios/web/test/web_test_with_web_controller.h"
#import "ios/web/web_state/ui/crw_media_capture_permission_request.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/web_state_impl.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"

using base::test::ios::kWaitForPageLoadTimeout;
using base::test::ios::kWaitForUIElementTimeout;
using base::test::ios::SpinRunLoopWithMinDelay;
using base::test::ios::WaitUntilConditionOrTimeout;

namespace {

// Mocks WebStateObserver callbacks.
class WebStateObserverMock : public web::WebStateObserver {
 public:
  WebStateObserverMock() = default;

  WebStateObserverMock(const WebStateObserverMock&) = delete;
  WebStateObserverMock& operator=(const WebStateObserverMock&) = delete;

  MOCK_METHOD2(PermissionStateChanged, void(web::WebState*, web::Permission));
  void WebStateDestroyed(web::WebState* web_state) override {
    NOTREACHED_IN_MIGRATION();
  }
};

// Web client that simulates prerendering for testing purpose.
class TestPrerenderWebClient : public web::WebClient {
 public:
  TestPrerenderWebClient(web::WebTestWithWebState* test_case,
                         web::WebState* web_state)
      : test_case_(test_case), web_state_(web_state) {}

  // Like preload cancelling when attempting to show a prompt, this method
  // destroys the web state by closing the controller.
  void WillDisplayMediaCapturePermissionPrompt(
      web::WebState* web_state) const override {
    if (web_state == web_state_) {
      test_case_->DestroyWebState();
    }
  }

 private:
  raw_ptr<web::WebTestWithWebState> test_case_;
  raw_ptr<web::WebState> web_state_;
};

// Verifies that the current permission states matches expected.
ACTION_P3(VerifyPermissionState, web_state, permission, permission_state) {
  EXPECT_EQ(web_state, arg0);
  EXPECT_EQ(web_state->GetStateForPermission(permission), permission_state);
}

}  // namespace

namespace web {

// Tests fixture to test permissions handling for web state and its observer.
class PermissionsInttest : public WebTestWithWebController {
 public:
  void SetUp() override {
    WebTestWithWebController::SetUp();
    web_state()->AddObserver(&observer_);
    web_state()->SetDelegate(&delegate_);

    // Default setting; individual test cases can override.
    delegate_.SetShouldHandlePermissionDecision(true);

    // Set up test server.
    test_server_ = std::make_unique<net::EmbeddedTestServer>();
    test_server_->ServeFilesFromSourceDirectory(
        base::FilePath("ios/testing/data/http_server_files/permissions"));
    ASSERT_TRUE(test_server_->Start());
  }

  void TearDown() override {
    delegate_.ClearLastRequestedPermissions();
    if (web_state()) {
      web_state()->SetDelegate(nullptr);
      web_state()->RemoveObserver(&observer_);
    }
    WebTestWithWebController::TearDown();
  }

  // Returns whether the delegate has handled the request for expected
  // permissions.
  bool LastRequestedPermissionsMatchesPermissions(
      NSArray<NSNumber*>* expected) {
    NSArray<NSNumber*>* actual = delegate_.last_requested_permissions();
    if ([actual count] != [expected count]) {
      return false;
    }
    NSArray<NSNumber*>* expected_sorted =
        [expected sortedArrayUsingSelector:@selector(compare:)];
    NSArray<NSNumber*>* actual_sorted =
        [actual sortedArrayUsingSelector:@selector(compare:)];
    return [actual_sorted isEqualToArray:expected_sorted];
  }

 protected:
  std::unique_ptr<net::EmbeddedTestServer> test_server_;
  testing::NiceMock<WebStateObserverMock> observer_;
  web::FakeWebStateDelegate delegate_;
};

// Disabling tests on real devices as it would wait for a user to respond to
// "ios_web_inttests Would Like to Access the Camera" prompt. This is currently
// not supported by gtest. Related logic and behaviors would be tested on real
// devices in integration tests.
#if TARGET_OS_SIMULATOR
  
namespace {

// This is the timeout used to wait for the WKUIDelegate's decision handler
// block of the to respond to permissions requests. The web state's permission
// states would only update after the decision handler blocks responds, so all
// checks should be ran after this timeout.
const base::TimeDelta kWebViewDecisionHandlingTimeout = base::Milliseconds(100);

constexpr std::string_view kSecureUrl = "https://www.chromium.org";
constexpr std::string_view kInsecureUrl = "http://www.chromium.org";

} // namespace

// Tests that web state observer gets invoked for camera only when the website
// only requests for camera permissions and changed via web_state() setter
// API afterwards.
TEST_F(PermissionsInttest,
       TestsThatPermissionStateChangedObserverInvokedForCameraOnly) {
  // TODO(crbug.com/342245057): Camera access is broken in the simulator on iOS
  // 17.5.
  if (@available(iOS 17.5, *)) {
    GTEST_SKIP() << "Test disabled on iOS 17.5.";
  }
  EXPECT_CALL(observer_, PermissionStateChanged(web_state(), PermissionCamera))
      .Times(testing::Exactly(2))
      .WillOnce(VerifyPermissionState(web_state(), PermissionCamera,
                                      PermissionStateAllowed));
  EXPECT_CALL(observer_,
              PermissionStateChanged(web_state(), PermissionMicrophone))
      .Times(0);
  delegate_.SetPermissionDecision(PermissionDecisionGrant);

  // Initial load.
  test::LoadUrl(web_state(), test_server_->GetURL("/camera_only.html"));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return LastRequestedPermissionsMatchesPermissions(@[ @(PermissionCamera) ]);
  }));
  SpinRunLoopWithMinDelay(kWebViewDecisionHandlingTimeout);
  EXPECT_EQ(web_state()->GetStateForPermission(PermissionCamera),
            PermissionStateAllowed);

  // Update permission through web state API.
  web_state()->SetStateForPermission(PermissionStateBlocked, PermissionCamera);
  EXPECT_TRUE(
      WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, true, ^bool {
        return web_state()->GetStateForPermission(PermissionCamera) ==
               PermissionStateBlocked;
      }));
}

// Tests that web state observer gets invoked for microphone only when the
// website only requests for microphone permissions and changed via web_state()
// setter API afterwards.
TEST_F(PermissionsInttest,
       TestsThatPermissionStateChangedObserverInvokedForMicrophoneOnly) {
  EXPECT_CALL(observer_, PermissionStateChanged(web_state(), PermissionCamera))
      .Times(0);
  EXPECT_CALL(observer_,
              PermissionStateChanged(web_state(), PermissionMicrophone))
      .Times(testing::Exactly(2))
      .WillOnce(VerifyPermissionState(web_state(), PermissionMicrophone,
                                      PermissionStateAllowed));

  // Initial load.
  delegate_.SetPermissionDecision(PermissionDecisionGrant);
  test::LoadUrl(web_state(), test_server_->GetURL("/microphone_only.html"));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return LastRequestedPermissionsMatchesPermissions(
        @[ @(PermissionMicrophone) ]);
  }));
  SpinRunLoopWithMinDelay(kWebViewDecisionHandlingTimeout);
  EXPECT_EQ(web_state()->GetStateForPermission(PermissionMicrophone),
            PermissionStateAllowed);

  // Update permission through web state API.
  web_state()->SetStateForPermission(PermissionStateNotAccessible,
                                     PermissionMicrophone);
  EXPECT_TRUE(
      WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, true, ^bool {
        return web_state()->GetStateForPermission(PermissionMicrophone) ==
               PermissionStateNotAccessible;
      }));
}

// Tests that web state observer gets invoked for both camera and microphone,
// when both are requested by the web page and set via web_state() afterwards.
TEST_F(PermissionsInttest,
       TestsThatPermissionStateChangedObserverInvokedForCameraAndMicrophone) {
  // TODO(crbug.com/342245057): Camera access is broken in the simulator on iOS
  // 17.5.
  if (@available(iOS 17.5, *)) {
    GTEST_SKIP() << "Test disabled on iOS 17.5.";
  }
  EXPECT_CALL(observer_, PermissionStateChanged(web_state(), PermissionCamera))
      .Times(testing::Exactly(2))
      .WillOnce(VerifyPermissionState(web_state(), PermissionCamera,
                                      PermissionStateAllowed));
  EXPECT_CALL(observer_,
              PermissionStateChanged(web_state(), PermissionMicrophone))
      .Times(testing::Exactly(1))
      .WillOnce(VerifyPermissionState(web_state(), PermissionMicrophone,
                                      PermissionStateAllowed));

  // Initial load.
  delegate_.SetPermissionDecision(PermissionDecisionGrant);
  test::LoadUrl(web_state(),
                test_server_->GetURL("/camera_and_microphone.html"));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return LastRequestedPermissionsMatchesPermissions(
        @[ @(PermissionCamera), @(PermissionMicrophone) ]);
  }));
  SpinRunLoopWithMinDelay(kWebViewDecisionHandlingTimeout);
  EXPECT_EQ(web_state()->GetStateForPermission(PermissionCamera),
            PermissionStateAllowed);
  EXPECT_EQ(web_state()->GetStateForPermission(PermissionMicrophone),
            PermissionStateAllowed);

  // Update permission through web state API.
  web_state()->SetStateForPermission(PermissionStateBlocked, PermissionCamera);
  EXPECT_TRUE(
      WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, true, ^bool {
        return web_state()->GetStateForPermission(PermissionCamera) ==
               PermissionStateBlocked;
      }));
  EXPECT_FALSE(
      WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, true, ^bool {
        return web_state()->GetStateForPermission(PermissionMicrophone) !=
               PermissionStateAllowed;
      }));
}

// Tests that web state observer should not be invoked when permission is
// denied.
TEST_F(PermissionsInttest,
       TestsThatPermissionStateChangedObserverNotInvokedWhenPermissionDenied) {
  EXPECT_CALL(observer_, PermissionStateChanged(web_state(), PermissionCamera))
      .Times(0);

  delegate_.SetPermissionDecision(PermissionDecisionDeny);
  test::LoadUrl(web_state(), test_server_->GetURL("/camera_only.html"));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return LastRequestedPermissionsMatchesPermissions(@[ @(PermissionCamera) ]);
  }));
  SpinRunLoopWithMinDelay(kWebViewDecisionHandlingTimeout);
  EXPECT_EQ(web_state()->GetStateForPermission(PermissionCamera),
            PermissionStateNotAccessible);
  EXPECT_EQ([web_controller() ensureWebViewCreated].cameraCaptureState,
            WKMediaCaptureStateNone);
}

// Tests that permission could not be manually altered if it has never been
// granted by WKUIDelegate in the first place.
TEST_F(PermissionsInttest,
       TestsThatWebStateShouldNotAlterPermissionIfNotAccessible) {
  if (@available(iOS 17.0, *)) {
    // TODO(crbug.com/40921852): This crashes on iOS17, waiting for Apple fix.
    GTEST_SKIP() << "This crashes on iOS17, waiting for Apple fix.";
  }

  delegate_.SetPermissionDecision(PermissionDecisionDeny);
  test::LoadUrl(web_state(), test_server_->GetURL("/camera_only.html"));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return LastRequestedPermissionsMatchesPermissions(@[ @(PermissionCamera) ]);
  }));
  SpinRunLoopWithMinDelay(kWebViewDecisionHandlingTimeout);
  EXPECT_EQ(web_state()->GetStateForPermission(PermissionCamera),
            PermissionStateNotAccessible);

  // Update permission through web state API.
  web_state()->SetStateForPermission(PermissionStateAllowed, PermissionCamera);
  web_state()->SetStateForPermission(PermissionStateBlocked,
                                     PermissionMicrophone);
  // Neither permission should be changed.
  EXPECT_FALSE(
      WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
        // Camera permission asked but denied.
        BOOL camera_permission_changed =
            web_state()->GetStateForPermission(PermissionCamera) !=
            PermissionStateNotAccessible;
        // Microphone permission never asked.
        BOOL microphone_permission_changed =
            web_state()->GetStateForPermission(PermissionMicrophone) !=
            PermissionStateNotAccessible;

        return camera_permission_changed || microphone_permission_changed;
      }));
  EXPECT_EQ([web_controller() ensureWebViewCreated].cameraCaptureState,
            WKMediaCaptureStateNone);
  EXPECT_EQ([web_controller() ensureWebViewCreated].microphoneCaptureState,
            WKMediaCaptureStateNone);
}

// Tests that page reload resets permission states.
TEST_F(PermissionsInttest, TestsThatPageReloadResetsPermissionState) {
  if (@available(iOS 17.0, *)) {
    // TODO(crbug.com/40921852): This crashes on iOS17, waiting for Apple fix.
    GTEST_SKIP() << "This crashes on iOS17, waiting for Apple fix.";
  }

  // Initial load should allow permission.
  delegate_.SetPermissionDecision(PermissionDecisionGrant);
  test::LoadUrl(web_state(), test_server_->GetURL("/camera_only.html"));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return LastRequestedPermissionsMatchesPermissions(@[ @(PermissionCamera) ]);
  }));
  SpinRunLoopWithMinDelay(kWebViewDecisionHandlingTimeout);
  EXPECT_EQ(web_state()->GetStateForPermission(PermissionCamera),
            PermissionStateAllowed);

  // Reloading should reset permission.
  // Handler should be called again, and permission state should be
  // NotAccessible.
  delegate_.ClearLastRequestedPermissions();
  delegate_.SetPermissionDecision(PermissionDecisionDeny);
  web_state()->GetNavigationManager()->Reload(ReloadType::NORMAL,
                                              /*check_for_repost=*/false);
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return LastRequestedPermissionsMatchesPermissions(@[ @(PermissionCamera) ]);
  }));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return web_state()->GetStateForPermission(PermissionCamera) ==
           PermissionStateNotAccessible;
  }));
  EXPECT_EQ([web_controller() ensureWebViewCreated].cameraCaptureState,
            WKMediaCaptureStateNone);
}

// Tests that the web state does not preserve permission states between
// navigations.
TEST_F(PermissionsInttest, TestsThatWebStateDoesNotPreservePermissionState) {
  if (@available(iOS 17.0, *)) {
    // TODO(crbug.com/40921852): This crashes on iOS17, waiting for Apple fix.
    GTEST_SKIP() << "This crashes on iOS17, waiting for Apple fix.";
  }

  // Initial load should allow permission.
  delegate_.SetPermissionDecision(PermissionDecisionGrant);
  test::LoadUrl(web_state(), test_server_->GetURL("/camera_only.html"));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return web_state()->GetStateForPermission(PermissionCamera) ==
           PermissionStateAllowed;
  }));
  EXPECT_TRUE(
      LastRequestedPermissionsMatchesPermissions(@[ @(PermissionCamera) ]));

  // Navigating to another page should reset permission after leaving the tab
  // running for a while. Handler should be called again, permission state
  // should be NotAccessible and the observer should NOT be invoked.
  delegate_.ClearLastRequestedPermissions();
  delegate_.SetPermissionDecision(PermissionDecisionDeny);
  test::LoadUrl(web_state(),
                test_server_->GetURL("/camera_and_microphone.html"));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return !web_state()->IsLoading() &&
           LastRequestedPermissionsMatchesPermissions(
               @[ @(PermissionCamera), @(PermissionMicrophone) ]);
  }));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return web_state()->GetStateForPermission(PermissionCamera) ==
               PermissionStateNotAccessible &&
           web_state()->GetStateForPermission(PermissionMicrophone) ==
               PermissionStateNotAccessible;
  }));
  EXPECT_EQ([web_controller() ensureWebViewCreated].cameraCaptureState,
            WKMediaCaptureStateNone);
  EXPECT_EQ([web_controller() ensureWebViewCreated].microphoneCaptureState,
            WKMediaCaptureStateNone);
}

// Tests that hitting "go back" and "go forward" resets permission states for
// pages with existing accessible permission states.
TEST_F(PermissionsInttest,
       TestsThatMovingBackwardOrForwardResetsPermissionState) {
  if (@available(iOS 17.0, *)) {
    // TODO(crbug.com/40921852): This crashes on iOS17, waiting for Apple fix.
    GTEST_SKIP() << "This crashes on iOS17, waiting for Apple fix.";
  }

  // Initial load for both pages should allow permission.
  delegate_.SetPermissionDecision(PermissionDecisionGrant);
  test::LoadUrl(web_state(), test_server_->GetURL("/microphone_only.html"));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return web_state()->GetStateForPermission(PermissionMicrophone) ==
           PermissionStateAllowed;
  }));
  EXPECT_TRUE(
      LastRequestedPermissionsMatchesPermissions(@[ @(PermissionMicrophone) ]));

  test::LoadUrl(web_state(),
                test_server_->GetURL("/camera_and_microphone.html"));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return web_state()->GetStateForPermission(PermissionCamera) ==
               PermissionStateAllowed &&
           web_state()->GetStateForPermission(PermissionMicrophone) ==
               PermissionStateAllowed;
  }));
  EXPECT_TRUE(LastRequestedPermissionsMatchesPermissions(
      @[ @(PermissionCamera), @(PermissionMicrophone) ]));

  // Update permission through web state API. To cover more cases, block
  // microphone on the second page.
  web_state()->SetStateForPermission(PermissionStateBlocked,
                                     PermissionMicrophone);
  EXPECT_TRUE(
      WaitUntilConditionOrTimeout(kWebViewDecisionHandlingTimeout, true, ^bool {
        return [web_controller() ensureWebViewCreated].microphoneCaptureState ==
               WKMediaCaptureStateMuted;
      }));

  // Permissions should be reset when you go backward or forward.

  // Note: There's currently an existing WebKit bug that WKUIDelegate method
  // `requestMediaCapturePermissionForOrigin:` would not be invoked when the
  // user hits backward/forward; instead, iOS sets them automatically to
  // WKMediaCaptureStateNone. The two following lines of code should be
  // uncommented when this is fixed.

  // delegate_.SetPermissionDecision(PermissionDecisionDeny);
  // handler_.decision = WKPermissionDecisionDeny;
  web_state()->GetNavigationManager()->GoBack();
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return web_state()->GetStateForPermission(PermissionMicrophone) ==
           PermissionStateNotAccessible;
  }));
  EXPECT_EQ([web_controller() ensureWebViewCreated].microphoneCaptureState,
            WKMediaCaptureStateNone);

  web_state()->GetNavigationManager()->GoForward();
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return web_state()->GetStateForPermission(PermissionCamera) ==
               PermissionStateNotAccessible &&
           web_state()->GetStateForPermission(PermissionMicrophone) ==
               PermissionStateNotAccessible;
  }));
  EXPECT_EQ([web_controller() ensureWebViewCreated].cameraCaptureState,
            WKMediaCaptureStateNone);
  EXPECT_EQ([web_controller() ensureWebViewCreated].microphoneCaptureState,
            WKMediaCaptureStateNone);
}

// Tests that closing tab before media capture request is handled denies
// permission.
TEST_F(PermissionsInttest, TestsThatClosingTabBeforeDecisionDeniesPermission) {
  // Set the permission decision to PermissionDecisionGrant first to eliminate
  // false positive test result, where the permission is erroneously declined
  // by the user/delegate instead of auto-declined by tab closing.
  delegate_.SetPermissionDecision(PermissionDecisionGrant);
  delegate_.SetShouldHandlePermissionDecision(false);

  // Initialize the decision to a value that should map to none of the
  // WKPermissionDecisions.
  __block NSInteger decision = -1;
  WKWebView* web_view = [web_controller() ensureWebViewCreated];
  id<WKUIDelegate> ui_delegate = web_view.UIDelegate;
  {
    // Fake a media capture permission request. Use an inner scope to allow
    // the request to be destroyed, simulating the closing of a tab.
    CRWMediaCapturePermissionRequest* request =
        [[CRWMediaCapturePermissionRequest alloc]
            initWithDecisionHandler:^(
                WKPermissionDecision wk_permission_decision) {
              decision = static_cast<NSInteger>(wk_permission_decision);
            }
                       onTaskRunner:base::SequencedTaskRunner::
                                        GetCurrentDefault()];
    request.presenter = (id<CRWMediaCapturePermissionPresenter>)ui_delegate;
    [request displayPromptForMediaCaptureType:WKMediaCaptureTypeCamera
                                       origin:GURL(kSecureUrl)];
  }

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return LastRequestedPermissionsMatchesPermissions(@[ @(PermissionCamera) ]);
  }));
  EXPECT_EQ(decision, static_cast<NSInteger>(WKPermissionDecisionDeny));
}

// Tests that media capture request is auto-declined when the tab is
// prerendering.
TEST_F(PermissionsInttest, TestsThatCancelllingPrerenderDeniesPermission) {
  ScopedTestingWebClient scoped_testing_web_client(
      std::make_unique<TestPrerenderWebClient>(this, web_state()));

  // Set the permission decision to PermissionDecisionGrant first to eliminate
  // false positive test result, where the permission is erroneously declined
  // by the user/delegate instead of auto-declined of prerender cancelling.
  delegate_.SetPermissionDecision(PermissionDecisionGrant);

  // Observer is not needed in this test case.
  web_state()->RemoveObserver(&observer_);

  // Initialize the decision to a value that should map to none of the
  // WKPermissionDecisions.
  __block NSInteger decision = -1;
  WKWebView* web_view = [web_controller() ensureWebViewCreated];
  id<WKUIDelegate> ui_delegate = web_view.UIDelegate;
  // Fake a media capture permission request.
  CRWFakeWKFrameInfo* frame_info = [[CRWFakeWKFrameInfo alloc] init];
  frame_info.mainFrame = YES;
  [ui_delegate webView:web_view
      requestMediaCapturePermissionForOrigin:frame_info.securityOrigin
                            initiatedByFrame:frame_info
                                        type:WKMediaCaptureTypeCamera
                             decisionHandler:^(
                                 WKPermissionDecision wk_permission_decision) {
                               decision = static_cast<NSInteger>(
                                   wk_permission_decision);
                             }];
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return decision == static_cast<NSInteger>(WKPermissionDecisionDeny);
  }));
  EXPECT_FALSE(web_state());
}

// Tests that permission is denied for non-secure origins.
TEST_F(PermissionsInttest, TestPermissionDeniedForNonSecureOrigin) {
  // Initialize the decision to a value that should map to none of the
  // WKPermissionDecisions.
  __block NSInteger decision = -1;
  WKWebView* web_view = [web_controller() ensureWebViewCreated];
  id<WKUIDelegate> ui_delegate = web_view.UIDelegate;

  // Fake a media capture permission request.
  CRWMediaCapturePermissionRequest* request = [[CRWMediaCapturePermissionRequest
      alloc]
      initWithDecisionHandler:^(WKPermissionDecision wk_permission_decision) {
        decision = static_cast<NSInteger>(wk_permission_decision);
      }
                 onTaskRunner:base::SequencedTaskRunner::GetCurrentDefault()];
  request.presenter = (id<CRWMediaCapturePermissionPresenter>)ui_delegate;
  [request displayPromptForMediaCaptureType:WKMediaCaptureTypeCamera
                                     origin:GURL(kInsecureUrl)];

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, true, ^bool {
    return decision == static_cast<NSInteger>(WKPermissionDecisionDeny);
  }));
}

#endif  // TARGET_OS_SIMULATOR

}  // namespace web