blob: b2aab579e7587b7e31466985a7ed431f17d66067 [file] [log] [blame]
// Copyright 2017 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 <memory>
#include <string>
#include "base/ios/ios_util.h"
#include "base/scoped_observer.h"
#include "base/strings/stringprintf.h"
#import "base/test/ios/wait_util.h"
#include "base/test/scoped_feature_list.h"
#include "ios/testing/embedded_test_server_handlers.h"
#import "ios/web/public/crw_navigation_item_storage.h"
#import "ios/web/public/crw_session_storage.h"
#include "ios/web/public/features.h"
#import "ios/web/public/navigation_item.h"
#import "ios/web/public/navigation_manager.h"
#import "ios/web/public/test/fakes/test_native_content.h"
#import "ios/web/public/test/fakes/test_native_content_provider.h"
#import "ios/web/public/test/navigation_test_util.h"
#import "ios/web/public/test/web_view_content_test_util.h"
#import "ios/web/public/test/web_view_interaction_test_util.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state/navigation_context.h"
#include "ios/web/public/web_state/web_state_observer.h"
#import "ios/web/public/web_state/web_state_policy_decider.h"
#include "ios/web/test/test_url_constants.h"
#import "ios/web/test/web_int_test.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/web_state_impl.h"
#include "net/http/http_response_headers.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace web {
namespace {
const char kExpectedMimeType[] = "text/html";
// WebStateObserverTest is parameterized on this enum to test both
// LegacyNavigationManagerImpl and WKBasedNavigationManagerImpl.
enum NavigationManagerChoice {
TEST_LEGACY_NAVIGATION_MANAGER,
TEST_WK_BASED_NAVIGATION_MANAGER,
};
// Verifies correctness of |NavigationContext| (|arg1|) for new page navigation
// passed to |DidStartNavigation|. Stores |NavigationContext| in |context|
// pointer.
ACTION_P5(VerifyPageStartedContext,
web_state,
url,
transition,
context,
nav_id) {
*context = arg1;
ASSERT_TRUE(*context);
EXPECT_EQ(web_state, arg0);
EXPECT_EQ(web_state, (*context)->GetWebState());
*nav_id = (*context)->GetNavigationId();
EXPECT_NE(0, *nav_id);
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
ui::PageTransition actual_transition = (*context)->GetPageTransition();
EXPECT_TRUE(PageTransitionCoreTypeIs(transition, actual_transition))
<< "Got unexpected transition: " << actual_transition;
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_FALSE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_FALSE((*context)->IsRendererInitiated());
ASSERT_FALSE((*context)->GetResponseHeaders());
ASSERT_TRUE(web_state->IsLoading());
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetPendingItem();
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) for new page navigation
// passed to |DidFinishNavigation|. Asserts that |NavigationContext| the same as
// |context|.
ACTION_P5(VerifyNewPageFinishedContext,
web_state,
url,
mime_type,
context,
nav_id) {
ASSERT_EQ(*context, arg1);
EXPECT_EQ(web_state, arg0);
ASSERT_TRUE((*context));
EXPECT_EQ(web_state, (*context)->GetWebState());
EXPECT_EQ(*nav_id, (*context)->GetNavigationId());
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
EXPECT_TRUE(
PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_TYPED,
(*context)->GetPageTransition()));
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_TRUE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_FALSE((*context)->IsRendererInitiated());
ASSERT_TRUE((*context)->GetResponseHeaders());
std::string actual_mime_type;
(*context)->GetResponseHeaders()->GetMimeType(&actual_mime_type);
ASSERT_TRUE(web_state->IsLoading());
EXPECT_EQ(mime_type, actual_mime_type);
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetLastCommittedItem();
EXPECT_TRUE(!item->GetTimestamp().is_null());
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) for failed navigation
// passed to |DidFinishNavigation|. Asserts that |NavigationContext| the same as
// |context|.
ACTION_P4(VerifyErrorFinishedContext, web_state, url, context, nav_id) {
ASSERT_EQ(*context, arg1);
EXPECT_EQ(web_state, arg0);
ASSERT_TRUE((*context));
EXPECT_EQ(web_state, (*context)->GetWebState());
EXPECT_EQ(*nav_id, (*context)->GetNavigationId());
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
EXPECT_TRUE(
PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_TYPED,
(*context)->GetPageTransition()));
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_FALSE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
// The error code will be different on bots and for local runs. Allow both.
NSInteger error_code = (*context)->GetError().code;
EXPECT_TRUE(error_code == NSURLErrorNetworkConnectionLost);
EXPECT_FALSE((*context)->IsRendererInitiated());
EXPECT_FALSE((*context)->GetResponseHeaders());
if (base::FeatureList::IsEnabled(web::features::kWebErrorPages)) {
ASSERT_TRUE(web_state->IsLoading());
} else {
ASSERT_FALSE(web_state->IsLoading());
}
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetLastCommittedItem();
EXPECT_FALSE(item->GetTimestamp().is_null());
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) passed to
// |DidFinishNavigation| for navigation canceled due to a rejected response.
// Asserts that |NavigationContext| the same as |context|.
ACTION_P4(VerifyResponseRejectedFinishedContext,
web_state,
url,
context,
nav_id) {
ASSERT_EQ(*context, arg1);
EXPECT_EQ(web_state, arg0);
ASSERT_TRUE((*context));
EXPECT_EQ(web_state, (*context)->GetWebState());
EXPECT_EQ(*nav_id, (*context)->GetNavigationId());
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
EXPECT_TRUE(
PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_TYPED,
(*context)->GetPageTransition()));
EXPECT_FALSE((*context)->IsSameDocument());
// When the response is rejected discard non committed items is called and
// no item should be committed.
EXPECT_FALSE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_FALSE((*context)->IsRendererInitiated());
EXPECT_FALSE((*context)->GetResponseHeaders());
ASSERT_FALSE(web_state->IsLoading());
}
// Verifies correctness of |NavigationContext| (|arg1|) for navigations via POST
// HTTP methods passed to |DidStartNavigation|. Stores |NavigationContext| in
// |context| pointer.
ACTION_P6(VerifyPostStartedContext,
web_state,
url,
has_user_gesture,
context,
nav_id,
renderer_initiated) {
*context = arg1;
ASSERT_TRUE(*context);
EXPECT_EQ(web_state, arg0);
EXPECT_EQ(web_state, (*context)->GetWebState());
*nav_id = (*context)->GetNavigationId();
EXPECT_NE(0, *nav_id);
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_EQ(has_user_gesture, (*context)->HasUserGesture());
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_FALSE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_TRUE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_EQ(renderer_initiated, (*context)->IsRendererInitiated());
ASSERT_FALSE((*context)->GetResponseHeaders());
ASSERT_TRUE(web_state->IsLoading());
// TODO(crbug.com/676129): Reload does not create a pending item. Remove this
// workaround once the bug is fixed. The slim navigation manager fixes this
// bug.
if (GetWebClient()->IsSlimNavigationManagerEnabled() ||
!ui::PageTransitionTypeIncludingQualifiersIs(
ui::PageTransition::PAGE_TRANSITION_RELOAD,
(*context)->GetPageTransition())) {
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetPendingItem();
EXPECT_EQ(url, item->GetURL());
}
}
// Verifies correctness of |NavigationContext| (|arg1|) for navigations via POST
// HTTP methods passed to |DidFinishNavigation|. Stores |NavigationContext| in
// |context| pointer.
ACTION_P6(VerifyPostFinishedContext,
web_state,
url,
has_user_gesture,
context,
nav_id,
renderer_initiated) {
ASSERT_EQ(*context, arg1);
EXPECT_EQ(web_state, arg0);
ASSERT_TRUE((*context));
EXPECT_EQ(web_state, (*context)->GetWebState());
EXPECT_EQ(*nav_id, (*context)->GetNavigationId());
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_EQ(has_user_gesture, (*context)->HasUserGesture());
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_TRUE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_TRUE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_EQ(renderer_initiated, (*context)->IsRendererInitiated());
ASSERT_TRUE(web_state->IsLoading());
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetLastCommittedItem();
EXPECT_TRUE(!item->GetTimestamp().is_null());
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) for same page navigation
// passed to |DidFinishNavigation|. Stores |NavigationContext| in |context|
// pointer.
ACTION_P7(VerifySameDocumentStartedContext,
web_state,
url,
has_user_gesture,
context,
nav_id,
page_transition,
renderer_initiated) {
*context = arg1;
ASSERT_TRUE(*context);
EXPECT_EQ(web_state, arg0);
EXPECT_EQ(web_state, (*context)->GetWebState());
*nav_id = (*context)->GetNavigationId();
EXPECT_NE(0, *nav_id);
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_EQ(has_user_gesture, (*context)->HasUserGesture());
EXPECT_TRUE(PageTransitionTypeIncludingQualifiersIs(
page_transition, (*context)->GetPageTransition()));
EXPECT_TRUE((*context)->IsSameDocument());
EXPECT_FALSE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_FALSE((*context)->GetResponseHeaders());
}
// Verifies correctness of |NavigationContext| (|arg1|) for same page navigation
// passed to |DidFinishNavigation|. Asserts that |NavigationContext| the same as
// |context|.
ACTION_P7(VerifySameDocumentFinishedContext,
web_state,
url,
has_user_gesture,
context,
nav_id,
page_transition,
renderer_initiated) {
ASSERT_EQ(*context, arg1);
ASSERT_TRUE(*context);
EXPECT_EQ(web_state, arg0);
EXPECT_EQ(web_state, (*context)->GetWebState());
EXPECT_EQ(*nav_id, (*context)->GetNavigationId());
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_EQ(has_user_gesture, (*context)->HasUserGesture());
EXPECT_TRUE(PageTransitionTypeIncludingQualifiersIs(
page_transition, (*context)->GetPageTransition()));
EXPECT_TRUE((*context)->IsSameDocument());
EXPECT_FALSE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_FALSE((*context)->GetResponseHeaders());
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetLastCommittedItem();
EXPECT_TRUE(!item->GetTimestamp().is_null());
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) for new page navigation
// to native URLs passed to |DidStartNavigation|. Stores |NavigationContext| in
// |context| pointer.
ACTION_P4(VerifyNewNativePageStartedContext, web_state, url, context, nav_id) {
*context = arg1;
ASSERT_TRUE(*context);
EXPECT_EQ(web_state, arg0);
EXPECT_EQ(web_state, (*context)->GetWebState());
*nav_id = (*context)->GetNavigationId();
EXPECT_NE(0, *nav_id);
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
EXPECT_TRUE(
PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_TYPED,
(*context)->GetPageTransition()));
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_FALSE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_FALSE((*context)->IsRendererInitiated());
EXPECT_FALSE((*context)->GetResponseHeaders());
ASSERT_TRUE(web_state->IsLoading());
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetPendingItem();
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) for new page navigation
// to native URLs passed to |DidFinishNavigation|. Asserts that
// |NavigationContext| the same as |context|.
ACTION_P4(VerifyNewNativePageFinishedContext, web_state, url, context, nav_id) {
ASSERT_EQ(*context, arg1);
ASSERT_TRUE(*context);
EXPECT_EQ(web_state, arg0);
EXPECT_EQ(web_state, (*context)->GetWebState());
EXPECT_EQ(*nav_id, (*context)->GetNavigationId());
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
EXPECT_TRUE(
PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_TYPED,
(*context)->GetPageTransition()));
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_TRUE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_FALSE((*context)->IsRendererInitiated());
EXPECT_FALSE((*context)->GetResponseHeaders());
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetLastCommittedItem();
EXPECT_TRUE(!item->GetTimestamp().is_null());
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) for reload navigation
// passed to |DidStartNavigation|. Stores |NavigationContext| in |context|
// pointer.
ACTION_P4(VerifyReloadStartedContext, web_state, url, context, nav_id) {
*context = arg1;
ASSERT_TRUE(*context);
EXPECT_EQ(web_state, arg0);
EXPECT_EQ(web_state, (*context)->GetWebState());
*nav_id = (*context)->GetNavigationId();
EXPECT_NE(0, *nav_id);
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
EXPECT_TRUE(
PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_RELOAD,
(*context)->GetPageTransition()));
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_FALSE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->GetError());
// TODO(crbug.com/676129): Reload should not be renderer-initiated, but only
// marked so because LegacyNavigationManager doesn't create a pending item.
// WKBasedNavigationManager fixes this problem.
EXPECT_EQ(!GetWebClient()->IsSlimNavigationManagerEnabled(),
(*context)->IsRendererInitiated());
EXPECT_FALSE((*context)->GetResponseHeaders());
// TODO(crbug.com/676129): Reload does not create a pending item. Check
// pending item once the bug is fixed. The slim navigation manager fixes this
// bug.
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetPendingItem();
EXPECT_EQ(url, item->GetURL());
} else {
EXPECT_FALSE(web_state->GetNavigationManager()->GetPendingItem());
}
}
// Verifies correctness of |NavigationContext| (|arg1|) for reload navigation
// passed to |DidFinishNavigation|. Asserts that |NavigationContext| the same as
// |context|.
ACTION_P5(VerifyReloadFinishedContext,
web_state,
url,
context,
nav_id,
is_web_page) {
ASSERT_EQ(*context, arg1);
ASSERT_TRUE(*context);
EXPECT_EQ(web_state, arg0);
EXPECT_EQ(web_state, (*context)->GetWebState());
EXPECT_EQ(*nav_id, (*context)->GetNavigationId());
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
EXPECT_TRUE(
PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_RELOAD,
(*context)->GetPageTransition()));
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_TRUE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->GetError());
// TODO(crbug.com/676129): Reload should not be renderer-initiated, but only
// marked so because LegacyNavigationManager doesn't create a pending item.
// WKBasedNavigationManager fixes this problem.
EXPECT_EQ(!GetWebClient()->IsSlimNavigationManagerEnabled(),
(*context)->IsRendererInitiated());
if (is_web_page) {
ASSERT_TRUE((*context)->GetResponseHeaders());
std::string mime_type;
(*context)->GetResponseHeaders()->GetMimeType(&mime_type);
EXPECT_EQ(kExpectedMimeType, mime_type);
} else {
EXPECT_FALSE((*context)->GetResponseHeaders());
}
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetLastCommittedItem();
EXPECT_TRUE(!item->GetTimestamp().is_null());
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) for download navigation
// passed to |DidFinishNavigation|. Asserts that |NavigationContext| the same as
// |context|.
ACTION_P4(VerifyDownloadFinishedContext, web_state, url, context, nav_id) {
ASSERT_EQ(*context, arg1);
EXPECT_EQ(web_state, arg0);
ASSERT_TRUE((*context));
EXPECT_EQ(web_state, (*context)->GetWebState());
EXPECT_EQ(*nav_id, (*context)->GetNavigationId());
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
EXPECT_TRUE(
PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_TYPED,
(*context)->GetPageTransition()));
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_FALSE((*context)->HasCommitted());
EXPECT_TRUE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_FALSE((*context)->IsRendererInitiated());
}
// Verifies correctness of |NavigationContext| (|arg1|) for restoration
// navigation passed to |DidStartNavigation|. Stores |NavigationContext| in
// |context| pointer.
ACTION_P4(VerifyRestorationStartedContext, web_state, url, context, nav_id) {
*context = arg1;
ASSERT_TRUE(*context);
EXPECT_EQ(web_state, arg0);
EXPECT_EQ(web_state, (*context)->GetWebState());
*nav_id = (*context)->GetNavigationId();
EXPECT_NE(0, *nav_id);
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
// Transition type is not restored (crbug.com/888011).
ui::PageTransition actual_transition = (*context)->GetPageTransition();
EXPECT_TRUE(PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_LINK,
actual_transition))
<< "Got unexpected transition: " << actual_transition;
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_FALSE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
// This should be false. Navigation is determined as renderer initiated
// because there is no pending item during the restoration (crbug.com/888021).
EXPECT_TRUE((*context)->IsRendererInitiated());
ASSERT_FALSE((*context)->GetResponseHeaders());
ASSERT_TRUE(web_state->IsLoading());
// Pending item is null during restoration (crbug.com/888021).
NavigationManager* navigation_manager = web_state->GetNavigationManager();
ASSERT_FALSE(navigation_manager->GetPendingItem());
}
// Verifies correctness of |NavigationContext| (|arg1|) for restoration
// navigation passed to |DidFinishNavigation|. Asserts that |NavigationContext|
// the same as |context|.
ACTION_P5(VerifyRestorationFinishedContext,
web_state,
url,
mime_type,
context,
nav_id) {
ASSERT_EQ(*context, arg1);
EXPECT_EQ(web_state, arg0);
ASSERT_TRUE((*context));
EXPECT_EQ(web_state, (*context)->GetWebState());
EXPECT_EQ(*nav_id, (*context)->GetNavigationId());
EXPECT_EQ(url, (*context)->GetUrl());
EXPECT_TRUE((*context)->HasUserGesture());
// Transition type is not restored (crbug.com/888011).
ui::PageTransition actual_transition = (*context)->GetPageTransition();
EXPECT_TRUE(PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_LINK,
actual_transition))
<< "Got unexpected transition: " << actual_transition;
EXPECT_FALSE((*context)->IsSameDocument());
EXPECT_TRUE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
// This should be false. Navigation is determined as renderer initiated
// because there is no pending item during the restoration (crbug.com/888021).
EXPECT_TRUE((*context)->IsRendererInitiated());
ASSERT_TRUE((*context)->GetResponseHeaders());
std::string actual_mime_type;
(*context)->GetResponseHeaders()->GetMimeType(&actual_mime_type);
ASSERT_TRUE(web_state->IsLoading());
EXPECT_EQ(mime_type, actual_mime_type);
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetLastCommittedItem();
EXPECT_TRUE(!item->GetTimestamp().is_null());
EXPECT_EQ(url, item->GetURL());
}
// A Google Mock matcher which matches |target_frame_is_main| member of
// WebStatePolicyDecider::RequestInfo. This is needed because
// WebStatePolicyDecider::RequestInfo doesn't support operator==.
MATCHER_P(RequestInfoMatch, expected_request_info, /* argument_name = */ "") {
return ui::PageTransitionTypeIncludingQualifiersIs(
arg.transition_type, expected_request_info.transition_type) &&
arg.source_url == expected_request_info.source_url &&
arg.target_frame_is_main ==
expected_request_info.target_frame_is_main &&
arg.has_user_gesture == expected_request_info.has_user_gesture;
}
// Mocks WebStateObserver navigation callbacks.
class WebStateObserverMock : public WebStateObserver {
public:
WebStateObserverMock() = default;
MOCK_METHOD2(DidStartNavigation, void(WebState*, NavigationContext*));
MOCK_METHOD2(DidFinishNavigation, void(WebState*, NavigationContext*));
MOCK_METHOD1(DidStartLoading, void(WebState*));
MOCK_METHOD1(DidStopLoading, void(WebState*));
MOCK_METHOD2(PageLoaded, void(WebState*, PageLoadCompletionStatus));
MOCK_METHOD1(DidChangeBackForwardState, void(WebState*));
MOCK_METHOD1(TitleWasSet, void(WebState*));
void WebStateDestroyed(WebState* web_state) override { NOTREACHED(); }
private:
DISALLOW_COPY_AND_ASSIGN(WebStateObserverMock);
};
// Mocks WebStatePolicyDecider decision callbacks.
class PolicyDeciderMock : public WebStatePolicyDecider {
public:
PolicyDeciderMock(WebState* web_state) : WebStatePolicyDecider(web_state) {}
MOCK_METHOD2(ShouldAllowRequest,
bool(NSURLRequest*,
const WebStatePolicyDecider::RequestInfo& request_info));
MOCK_METHOD2(ShouldAllowResponse, bool(NSURLResponse*, bool for_main_frame));
};
} // namespace
using testing::Return;
using testing::StrictMock;
using testing::_;
using base::test::ios::kWaitForPageLoadTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using test::WaitForWebViewContainingText;
// Test fixture to test navigation and load callbacks from WebStateObserver and
// WebStatePolicyDecider.
class WebStateObserverTest
: public WebIntTest,
public ::testing::WithParamInterface<NavigationManagerChoice> {
public:
WebStateObserverTest() : scoped_observer_(&observer_) {}
void SetUp() override {
if (GetParam() == TEST_LEGACY_NAVIGATION_MANAGER) {
scoped_feature_list_.InitAndDisableFeature(
features::kSlimNavigationManager);
} else {
scoped_feature_list_.InitAndEnableFeature(
features::kSlimNavigationManager);
}
WebIntTest::SetUp();
decider_ = std::make_unique<StrictMock<PolicyDeciderMock>>(web_state());
scoped_observer_.Add(web_state());
// Stub out NativeContent objects.
provider_ = [[TestNativeContentProvider alloc] init];
content_ = [[TestNativeContent alloc] initWithURL:GURL::EmptyGURL()
virtualURL:GURL::EmptyGURL()];
WebStateImpl* web_state_impl = reinterpret_cast<WebStateImpl*>(web_state());
web_state_impl->GetWebController().nativeProvider = provider_;
test_server_ = std::make_unique<net::test_server::EmbeddedTestServer>();
test_server_->RegisterRequestHandler(
base::BindRepeating(&net::test_server::HandlePrefixedRequest, "/form",
base::BindRepeating(&testing::HandleForm)));
test_server_->RegisterRequestHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest, "/download",
base::BindRepeating(&testing::HandleDownload)));
RegisterDefaultHandlers(test_server_.get());
test_server_->ServeFilesFromSourceDirectory(
base::FilePath("ios/testing/data/http_server_files/"));
ASSERT_TRUE(test_server_->Start());
}
void TearDown() override {
scoped_observer_.RemoveAll();
WebIntTest::TearDown();
}
protected:
TestNativeContentProvider* provider_;
TestNativeContent* content_;
std::unique_ptr<StrictMock<PolicyDeciderMock>> decider_;
StrictMock<WebStateObserverMock> observer_;
std::unique_ptr<net::test_server::EmbeddedTestServer> test_server_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
ScopedObserver<WebState, WebStateObserver> scoped_observer_;
testing::InSequence callbacks_sequence_checker_;
DISALLOW_COPY_AND_ASSIGN(WebStateObserverTest);
};
// Tests successful navigation to a new page.
TEST_P(WebStateObserverTest, NewPageNavigation) {
const GURL url = test_server_->GetURL("/echo");
// Perform new page navigation.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewPageFinishedContext(
web_state(), url, kExpectedMimeType, &context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
}
// Tests that if web usage is already enabled, enabling it again would not cause
// any page loads (related to restoring cached session). This is a regression
// test for crbug.com/781916.
TEST_P(WebStateObserverTest, EnableWebUsageTwice) {
const GURL url = test_server_->GetURL("/echo");
// Only expect one set of load events from the first LoadUrl(), not subsequent
// SetWebUsageEnabled(true) calls. Web usage is already enabled, so the
// subsequent calls should be noops.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewPageFinishedContext(
web_state(), url, kExpectedMimeType, &context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
web_state()->SetWebUsageEnabled(true);
web_state()->SetWebUsageEnabled(true);
}
// Tests failed navigation to a new page.
TEST_P(WebStateObserverTest, FailedNavigation) {
// TODO(crbug.com/851119): temporarily disable this failing test.
if (GetParam() == TEST_WK_BASED_NAVIGATION_MANAGER) {
return;
}
const GURL url = test_server_->GetURL("/close-socket");
// Perform a navigation to url with unsupported scheme, which will fail.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
if (base::FeatureList::IsEnabled(web::features::kWebErrorPages)) {
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(
VerifyErrorFinishedContext(web_state(), url, &context, &nav_id));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
} else {
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(
VerifyErrorFinishedContext(web_state(), url, &context, &nav_id));
}
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::FAILURE));
test::LoadUrl(web_state(), url);
ASSERT_TRUE(test::WaitForPageToFinishLoading(web_state()));
}
// Tests web page reload navigation.
TEST_P(WebStateObserverTest, WebPageReloadNavigation) {
const GURL url = test_server_->GetURL("/echo");
// Perform new page navigation.
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
// Reload web page.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo reload_request_info(
ui::PageTransition::PAGE_TRANSITION_RELOAD, url,
/*target_main_frame=*/true,
/*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(reload_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(
VerifyReloadStartedContext(web_state(), url, &context, &nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyReloadFinishedContext(web_state(), url, &context, &nav_id,
true /* is_web_page */));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
// TODO(crbug.com/700958): ios/web ignores |check_for_repost| flag and current
// delegate does not run callback for ShowRepostFormWarningDialog. Clearing
// the delegate will allow form resubmission. Remove this workaround (clearing
// the delegate, once |check_for_repost| is supported).
// web_state()->SetDelegate(nullptr);
ASSERT_TRUE(ExecuteBlockAndWaitForLoad(url, ^{
navigation_manager()->Reload(ReloadType::NORMAL,
false /*check_for_repost*/);
}));
}
// Tests web page reload with user agent override.
TEST_P(WebStateObserverTest, ReloadWithUserAgentType) {
// TODO(crbug.com/851119): temporarily disable this failing test.
if (GetParam() == TEST_WK_BASED_NAVIGATION_MANAGER) {
return;
}
const GURL url = test_server_->GetURL("/echo");
// Perform new page navigation.
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
// Reload web page with desktop user agent.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_RELOAD,
&context, &nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
// TODO(crbug.com/798836): verify the correct User-Agent header is sent.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
web_state()->SetDelegate(nullptr);
ASSERT_TRUE(ExecuteBlockAndWaitForLoad(url, ^{
navigation_manager()->ReloadWithUserAgentType(UserAgentType::DESKTOP);
}));
}
// Tests user-initiated hash change.
TEST_P(WebStateObserverTest, UserInitiatedHashChangeNavigation) {
// TODO(crbug.com/851119): temporarily disable this failing test.
if (GetParam() == TEST_WK_BASED_NAVIGATION_MANAGER) {
return;
}
const GURL url = test_server_->GetURL("/echo");
// Perform new page navigation.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewPageFinishedContext(
web_state(), url, kExpectedMimeType, &context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
// Perform same-document navigation.
const GURL hash_url = test_server_->GetURL("/echo#1");
WebStatePolicyDecider::RequestInfo hash_url_expected_request_info_(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, hash_url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(
*decider_,
ShouldAllowRequest(_, RequestInfoMatch(hash_url_expected_request_info_)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifySameDocumentStartedContext(
web_state(), hash_url, /*has_user_gesture=*/true, &context, &nav_id,
ui::PageTransition::PAGE_TRANSITION_TYPED,
/*renderer_initiated=*/false));
// No ShouldAllowResponse callback for same-document navigations.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifySameDocumentFinishedContext(
web_state(), hash_url, /*has_user_gesture=*/true, &context, &nav_id,
ui::PageTransition::PAGE_TRANSITION_TYPED,
/*renderer_initiated=*/false));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(hash_url));
// Perform same-document navigation by going back.
// No ShouldAllowRequest callback for same-document back-forward navigations.
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifySameDocumentStartedContext(
web_state(), url, /*has_user_gesture=*/true, &context, &nav_id,
ui::PageTransition::PAGE_TRANSITION_FORWARD_BACK,
/*renderer_initiated=*/false));
// No ShouldAllowResponse callbacks for same-document back-forward
// navigations.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifySameDocumentFinishedContext(
web_state(), url, /*has_user_gesture=*/true, &context, &nav_id,
ui::PageTransition::PAGE_TRANSITION_FORWARD_BACK,
/*renderer_initiated=*/false));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(ExecuteBlockAndWaitForLoad(url, ^{
navigation_manager()->GoBack();
}));
}
// Tests renderer-initiated hash change.
TEST_P(WebStateObserverTest, RendererInitiatedHashChangeNavigation) {
const GURL url = test_server_->GetURL("/echo");
// Perform new page navigation.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewPageFinishedContext(
web_state(), url, kExpectedMimeType, &context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
// Perform same-page navigation using JavaScript.
const GURL hash_url = test_server_->GetURL("/echo#1");
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state()));
}
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifySameDocumentStartedContext(
web_state(), hash_url, /*has_user_gesture=*/false, &context, &nav_id,
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT,
/*renderer_initiated=*/true));
// No ShouldAllowResponse callback for same-document navigations.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifySameDocumentFinishedContext(
web_state(), hash_url, /*has_user_gesture=*/false, &context, &nav_id,
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT,
/*renderer_initiated=*/true));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ExecuteJavaScript(@"window.location.hash = '#1'");
}
// Tests state change.
TEST_P(WebStateObserverTest, StateNavigation) {
const GURL url = test_server_->GetURL("/echo");
// Perform new page navigation.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewPageFinishedContext(
web_state(), url, kExpectedMimeType, &context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
// Perform push state using JavaScript.
const GURL push_url = test_server_->GetURL("/test.html");
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state()));
}
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifySameDocumentStartedContext(
web_state(), push_url, /*has_user_gesture=*/false, &context, &nav_id,
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT,
/*renderer_initiated=*/true));
// No ShouldAllowRequest/ShouldAllowResponse callbacks for same-document push
// state navigations.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifySameDocumentFinishedContext(
web_state(), push_url, /*has_user_gesture=*/false, &context, &nav_id,
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT,
/*renderer_initiated=*/true));
ExecuteJavaScript(@"window.history.pushState('', 'Test', 'test.html')");
// Perform replace state using JavaScript.
const GURL replace_url = test_server_->GetURL("/1.html");
// No ShouldAllowRequest callbacks for same-document push state navigations.
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifySameDocumentStartedContext(
web_state(), replace_url, /*has_user_gesture=*/false, &context,
&nav_id, ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT,
/*renderer_initiated=*/true));
// No ShouldAllowResponse callbacks for same-document push state navigations.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifySameDocumentFinishedContext(
web_state(), replace_url, /*has_user_gesture=*/false, &context,
&nav_id, ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT,
/*renderer_initiated=*/true));
ExecuteJavaScript(@"window.history.replaceState('', 'Test', '1.html')");
}
// Tests native content navigation.
TEST_P(WebStateObserverTest, NativeContentNavigation) {
GURL url(url::SchemeHostPort(kTestNativeContentScheme, "ui", 0).Serialize());
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyNewNativePageStartedContext(web_state(), url, &context,
&nav_id));
// No ShouldAllowRequest/ShouldAllowResponse callbacks for native content
// navigations.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewNativePageFinishedContext(web_state(), url, &context,
&nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
[provider_ setController:content_ forURL:url];
ASSERT_TRUE(LoadUrl(url));
}
// Tests native content reload navigation.
TEST_P(WebStateObserverTest, NativeContentReload) {
GURL url(url::SchemeHostPort(kTestNativeContentScheme, "ui", 0).Serialize());
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
// No ShouldAllowRequest/ShouldAllowResponse callbacks for native content
// navigations.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
[provider_ setController:content_ forURL:url];
ASSERT_TRUE(LoadUrl(url));
// Reload native content.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
// No ShouldAllowRequest callbacks for native content navigations.
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(
VerifyReloadStartedContext(web_state(), url, &context, &nav_id));
// No ShouldAllowResponse callbacks for native content navigations.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyReloadFinishedContext(web_state(), url, &context, &nav_id,
false /* is_web_page */));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
web_state()->GetNavigationManager()->Reload(ReloadType::NORMAL,
/*check_for_repost=*/false);
}
// Tests WasTitleSet callback triggered when navigating back to native content
// from web content.
TEST_P(WebStateObserverTest, GoBackToNativeContent) {
// Load a native content URL.
GURL url(url::SchemeHostPort(kTestNativeContentScheme, "ui", 0).Serialize());
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
// No ShouldAllowRequest/ShouldAllowResponse callbacks for native content
// navigations.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
[provider_ setController:content_ forURL:url];
ASSERT_TRUE(LoadUrl(url));
// Load a web navigation.
const GURL web_url = test_server_->GetURL("/echo");
// Perform new page navigation.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, web_url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), web_url, ui::PageTransition::PAGE_TRANSITION_TYPED,
&context, &nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state()));
}
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewPageFinishedContext(
web_state(), web_url, kExpectedMimeType, &context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(web_url));
// Going back to native content should trigger TitleWasSet.
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state())).Times(2);
// TODO(crbug.com/867095): fix this extra callback triggered by placeholder
// URL load.
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
}
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
// No ShouldAllowRequest/ShouldAllowResponse callbacks for native content
// navigations.
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
// This call is required to make sure WebStateObservers update their cached
// version of current title.
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(ExecuteBlockAndWaitForLoad(url, ^{
navigation_manager()->GoBack();
}));
}
// Tests successful navigation to a new page with post HTTP method.
TEST_P(WebStateObserverTest, UserInitiatedPostNavigation) {
// Prior to iOS 11, POST navigation is implemented as an XMLHttpRequest in
// JavaScript. This doesn't create a WKBackForwardListItem in WKWebView on
// which to attach the pending NavigationItem, if WKBasedNavigationManager is
// used. When POST is the first navigation in an empty web view, this causes a
// DCHECK in WKBasedNavigationManagerImpl::CommitPendingItem(). When it is not
// the first navigation, it attaches the pending NavigationItem to the wrong
// WKBackForwardListItem. However, this is not worth fixing now. Load an
// initial request to the web view to stop this test from crashing, even
// though this doesn't fix the underlying bug.
// TODO(crbug.com/740987): remove this hack once support for iOS10 is dropped.
if (!base::ios::IsRunningOnIOS11OrLater() &&
GetParam() == TEST_WK_BASED_NAVIGATION_MANAGER) {
const GURL url = test_server_->GetURL("/echoall?bar");
// Perform new page navigation.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED,
&context, &nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewPageFinishedContext(
web_state(), url, kExpectedMimeType, &context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state())).Times(2);
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
ASSERT_TRUE(WaitForWebViewContainingText(web_state(), "bar"));
}
const GURL url = test_server_->GetURL("/echo");
// Perform new page navigation.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPostStartedContext(
web_state(), url, /*has_user_gesture=*/true, &context, &nav_id,
/*renderer_initiated=*/false));
if (@available(iOS 11, *)) {
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
}
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyPostFinishedContext(
web_state(), url, /*has_user_gesture=*/true, &context, &nav_id,
/*renderer_initiated=*/false));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
// Load request using POST HTTP method.
NavigationManager::WebLoadParams params(url);
params.post_data = [@"foo" dataUsingEncoding:NSUTF8StringEncoding];
params.extra_headers = @{@"Content-Type" : @"text/html"};
ASSERT_TRUE(LoadWithParams(params));
ASSERT_TRUE(WaitForWebViewContainingText(web_state(), "foo"));
}
// Tests successful navigation to a new page with post HTTP method.
TEST_P(WebStateObserverTest, RendererInitiatedPostNavigation) {
const GURL url = test_server_->GetURL("/form?echo");
const GURL action = test_server_->GetURL("/echo");
// Perform new page navigation.
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
ASSERT_TRUE(
WaitForWebViewContainingText(web_state(), testing::kTestFormPage));
// Submit the form using JavaScript.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
WebStatePolicyDecider::RequestInfo form_request_info(
ui::PageTransition::PAGE_TRANSITION_FORM_SUBMIT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(form_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPostStartedContext(
web_state(), action, /*has_user_gesture=*/false, &context, &nav_id,
/*renderer_initiated=*/true));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state()));
}
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyPostFinishedContext(
web_state(), action, /*has_user_gesture=*/false, &context, &nav_id,
/*renderer_initiated=*/true));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ExecuteJavaScript(@"document.getElementById('form').submit();");
ASSERT_TRUE(
WaitForWebViewContainingText(web_state(), testing::kTestFormFieldValue));
}
// Tests successful reload of a page returned for post request.
TEST_P(WebStateObserverTest, ReloadPostNavigation) {
const GURL url = test_server_->GetURL("/form?echo");
const GURL action = test_server_->GetURL("/echo");
// Perform new page navigation.
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
ASSERT_TRUE(
WaitForWebViewContainingText(web_state(), testing::kTestFormPage));
// Submit the form using JavaScript.
WebStatePolicyDecider::RequestInfo form_request_info(
ui::PageTransition::PAGE_TRANSITION_FORM_SUBMIT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(form_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state()));
}
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ExecuteJavaScript(@"window.document.getElementById('form').submit();");
ASSERT_TRUE(
WaitForWebViewContainingText(web_state(), testing::kTestFormFieldValue));
// Reload the page.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
// ShouldAllowRequest() not called because SlimNavigationManager catches
// repost before calling policy decider.
} else {
WebStatePolicyDecider::RequestInfo form_reload_request_info(
ui::PageTransition::PAGE_TRANSITION_FORM_SUBMIT, action,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_, ShouldAllowRequest(
_, RequestInfoMatch(form_reload_request_info)))
.WillOnce(Return(true));
}
bool reload_is_renderer_initiated = false;
if (!GetWebClient()->IsSlimNavigationManagerEnabled()) {
// TODO(crbug.com/676129) LegacyNavigationManager doesn't create a pending
// item on reload. This causes the navigation context to be incorrectly
// marked renderer-initiated. Remove this workaround.
reload_is_renderer_initiated = true;
}
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPostStartedContext(
web_state(), action, /*has_user_gesture=*/true, &context, &nav_id,
reload_is_renderer_initiated));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyPostFinishedContext(
web_state(), action, /*has_user_gesture=*/true, &context, &nav_id,
reload_is_renderer_initiated));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
// TODO(crbug.com/700958): ios/web ignores |check_for_repost| flag and current
// delegate does not run callback for ShowRepostFormWarningDialog. Clearing
// the delegate will allow form resubmission. Remove this workaround (clearing
// the delegate, once |check_for_repost| is supported).
web_state()->SetDelegate(nullptr);
ASSERT_TRUE(ExecuteBlockAndWaitForLoad(action, ^{
navigation_manager()->Reload(ReloadType::NORMAL,
false /*check_for_repost*/);
}));
}
// Tests going forward to a page rendered from post response.
TEST_P(WebStateObserverTest, ForwardPostNavigation) {
// TODO(crbug.com/851119): temporarily disable this failing test.
if (GetParam() == TEST_WK_BASED_NAVIGATION_MANAGER) {
return;
}
const GURL url = test_server_->GetURL("/form?echo");
const GURL action = test_server_->GetURL("/echo");
// Perform new page navigation.
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
ASSERT_TRUE(
WaitForWebViewContainingText(web_state(), testing::kTestFormPage));
// Submit the form using JavaScript.
WebStatePolicyDecider::RequestInfo form_request_info(
ui::PageTransition::PAGE_TRANSITION_FORM_SUBMIT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(form_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state()));
}
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ExecuteJavaScript(@"window.document.getElementById('form').submit();");
ASSERT_TRUE(
WaitForWebViewContainingText(web_state(), testing::kTestFormFieldValue));
// Go Back.
WebStatePolicyDecider::RequestInfo back_request_info(
ui::PageTransition::PAGE_TRANSITION_FORWARD_BACK, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state())).Times(2);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(back_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartLoading(web_state()));
} else {
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(back_request_info)))
.WillOnce(Return(true));
}
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
if (@available(iOS 12, *)) {
// On iOS 11 and earlier, ShouldAllowResponse is not called when going back
// after form submission.
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
}
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(ExecuteBlockAndWaitForLoad(url, ^{
navigation_manager()->GoBack();
}));
// Go forward.
WebStatePolicyDecider::RequestInfo forward_request_info(
ui::PageTransition::PAGE_TRANSITION_FORWARD_BACK, action,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
NavigationContext* context = nullptr;
int32_t nav_id = 0;
if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state())).Times(2);
// ShouldAllowRequest() not called on repost.
EXPECT_CALL(observer_, DidStartLoading(web_state()));
} else {
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(forward_request_info)))
.WillOnce(Return(true));
}
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPostStartedContext(
web_state(), action, /*has_user_gesture=*/true, &context, &nav_id,
/*renderer_initiated=*/false));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyPostFinishedContext(
web_state(), action, /*has_user_gesture=*/true, &context, &nav_id,
/*renderer_initiated=*/false));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
// TODO(crbug.com/700958): ios/web ignores |check_for_repost| flag and current
// delegate does not run callback for ShowRepostFormWarningDialog. Clearing
// the delegate will allow form resubmission. Remove this workaround (clearing
// the delegate, once |check_for_repost| is supported).
web_state()->SetDelegate(nullptr);
ASSERT_TRUE(ExecuteBlockAndWaitForLoad(action, ^{
navigation_manager()->GoForward();
}));
}
// Tests server redirect navigation.
TEST_P(WebStateObserverTest, RedirectNavigation) {
const GURL url = test_server_->GetURL("/server-redirect?echo");
const GURL redirect_url = test_server_->GetURL("/echo");
// Load url which replies with redirect.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
// Second ShouldAllowRequest call is for redirect_url.
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewPageFinishedContext(
web_state(), redirect_url, kExpectedMimeType, &context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(url));
}
// Tests download navigation.
TEST_P(WebStateObserverTest, DownloadNavigation) {
GURL url = test_server_->GetURL("/download");
// Perform download navigation.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(
VerifyDownloadFinishedContext(web_state(), url, &context, &nav_id));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
test::LoadUrl(web_state(), url);
ASSERT_TRUE(test::WaitForPageToFinishLoading(web_state()));
EXPECT_FALSE(web_state()->GetNavigationManager()->GetPendingItem());
}
// Tests failed load after the navigation is sucessfully finished.
// TODO(crbug.com/845879): test is flaky (probably since crrev.com/1056203).
#if TARGET_IPHONE_SIMULATOR
#define MAYBE_FailedLoad FailedLoad
#else
#define MAYBE_FailedLoad FLAKY_FailedLoad
#endif
TEST_P(WebStateObserverTest, FLAKY_FailedLoad) {
GURL url = test_server_->GetURL("/exabyte_response");
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewPageFinishedContext(web_state(), url, /*mime_type=*/"",
&context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::FAILURE));
test::LoadUrl(web_state(), url);
// Server will never stop responding. Wait until the navigation is committed.
EXPECT_FALSE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
return context && context->HasCommitted();
}));
// It this point the navigation should be finished. Shutdown the server and
// wait until web state stop loading.
ASSERT_TRUE(test_server_->ShutdownAndWaitUntilComplete());
ASSERT_TRUE(test::WaitForPageToFinishLoading(web_state()));
}
// Tests rejecting the navigation from ShouldAllowRequest. The load should stop,
// but no other callbacks are called.
TEST_P(WebStateObserverTest, DisallowRequest) {
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT,
test_server_->GetURL("/echo"),
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(false));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
test::LoadUrl(web_state(), test_server_->GetURL("/echo"));
ASSERT_TRUE(test::WaitForPageToFinishLoading(web_state()));
}
// Tests rejecting the navigation from ShouldAllowResponse. PageLoaded callback
// is not called.
TEST_P(WebStateObserverTest, DisallowResponse) {
const GURL url = test_server_->GetURL("/echo");
// Perform new page navigation.
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(false));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyResponseRejectedFinishedContext(web_state(), url,
&context, &nav_id));
test::LoadUrl(web_state(), test_server_->GetURL("/echo"));
ASSERT_TRUE(test::WaitForPageToFinishLoading(web_state()));
}
// Tests stopping a navigation. Did FinishLoading and PageLoaded are never
// called.
TEST_P(WebStateObserverTest, StopNavigation) {
EXPECT_CALL(observer_, DidStartLoading(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT,
/*source_url=*/GURL::EmptyGURL(),
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
test::LoadUrl(web_state(), test_server_->GetURL("/hung"));
web_state()->Stop();
ASSERT_TRUE(test::WaitForPageToFinishLoading(web_state()));
}
// Tests stopping a finished navigation. PageLoaded is never called.
TEST_P(WebStateObserverTest, StopFinishedNavigation) {
GURL url = test_server_->GetURL("/exabyte_response");
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), url, ui::PageTransition::PAGE_TRANSITION_TYPED, &context,
&nav_id));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyNewPageFinishedContext(web_state(), url, /*mime_type=*/"",
&context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
test::LoadUrl(web_state(), url);
// Server will never stop responding. Wait until the navigation is committed.
EXPECT_FALSE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
return context && context->HasCommitted();
}));
// Stop the loading.
web_state()->Stop();
ASSERT_TRUE(test::WaitForPageToFinishLoading(web_state()));
}
// Tests that iframe navigation triggers DidChangeBackForwardState.
TEST_P(WebStateObserverTest, IframeNavigation) {
// LegacyNavigationManager doesn't support iframe navigation history.
if (!GetWebClient()->IsSlimNavigationManagerEnabled())
return;
// TODO(crbug.com/851119): temporarily disable this failing test.
if (GetParam() == TEST_WK_BASED_NAVIGATION_MANAGER) {
return;
}
GURL url = test_server_->GetURL("/iframe_host.html");
// Callbacks due to loading of the main frame.
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
// Callbacks due to initial loading of iframe.
WebStatePolicyDecider::RequestInfo iframe_request_info(
ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT, url,
/*target_main_frame=*/false, /*has_user_gesture=*/true);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(iframe_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/false))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
test::LoadUrl(web_state(), url);
ASSERT_TRUE(test::WaitForPageToFinishLoading(web_state()));
// Trigger different-document load in iframe.
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(iframe_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/false))
.WillOnce(Return(true));
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state()));
test::TapWebViewElementWithIdInIframe(web_state(), "normal-link");
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
return web_state()->GetNavigationManager()->CanGoBack();
}));
id history_length = ExecuteJavaScript(@"history.length;");
ASSERT_NSEQ(@2, history_length);
EXPECT_FALSE(web_state()->GetNavigationManager()->CanGoForward());
// Go back to top.
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state()))
.Times(2); // called once each for canGoBack and canGoForward
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(iframe_request_info)))
.WillOnce(Return(true));
EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/false))
.WillOnce(Return(true));
web_state()->GetNavigationManager()->GoBack();
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
return web_state()->GetNavigationManager()->CanGoForward();
}));
EXPECT_FALSE(web_state()->GetNavigationManager()->CanGoBack());
// Trigger same-document load in iframe.
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(iframe_request_info)))
.WillOnce(Return(true));
// ShouldAllowResponse() is not called for same-document navigation.
EXPECT_CALL(observer_, DidChangeBackForwardState(web_state()))
.Times(2); // called once each for canGoBack and canGoForward
test::TapWebViewElementWithIdInIframe(web_state(), "same-page-link");
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
return web_state()->GetNavigationManager()->CanGoBack();
}));
EXPECT_FALSE(web_state()->GetNavigationManager()->CanGoForward());
}
// Verifies that WebState::CreateWithStorageSession does not call any
// WebStateObserver callbacks.
TEST_P(WebStateObserverTest, RestoreSession) {
// Create session storage.
CRWNavigationItemStorage* item = [[CRWNavigationItemStorage alloc] init];
GURL url(test_server_->GetURL("/echo"));
item.virtualURL = url;
NSArray<CRWNavigationItemStorage*>* item_storages = @[ item ];
// Create the session with storage and add observer.
WebState::CreateParams params(GetBrowserState());
CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init];
session_storage.itemStorages = item_storages;
auto web_state = WebState::CreateWithStorageSession(params, session_storage);
StrictMock<WebStateObserverMock> observer;
ScopedObserver<WebState, WebStateObserver> scoped_observer(&observer);
scoped_observer.Add(web_state.get());
// TODO(crbug.com/877671): WebStateObserver callbacks should be called for
// /echo URL.
if (!GetWebClient()->IsSlimNavigationManagerEnabled()) {
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer, DidStartLoading(web_state.get()));
EXPECT_CALL(observer, DidStartNavigation(web_state.get(), _))
.WillOnce(VerifyRestorationStartedContext(web_state.get(), url,
&context, &nav_id));
EXPECT_CALL(observer, DidFinishNavigation(web_state.get(), _))
.WillOnce(VerifyRestorationFinishedContext(
web_state.get(), url, kExpectedMimeType, &context, &nav_id));
EXPECT_CALL(observer, TitleWasSet(web_state.get()));
EXPECT_CALL(observer, DidStopLoading(web_state.get()));
EXPECT_CALL(observer,
PageLoaded(web_state.get(), PageLoadCompletionStatus::SUCCESS));
}
// Trigger the session restoration.
NavigationManager* navigation_manager = web_state->GetNavigationManager();
// TODO(crbug.com/873729): The session will not be restored until
// LoadIfNecessary call. Fix the bug and replace this call with
// SessionStorageBuilder::ExtractSessionState().
navigation_manager->LoadIfNecessary();
// Wait until the session is restored.
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
return navigation_manager->GetItemCount() == 1;
}));
// Wait until the page finishes loading.
if (!GetWebClient()->IsSlimNavigationManagerEnabled()) {
ASSERT_TRUE(test::WaitForPageToFinishLoading(web_state.get()));
}
}
INSTANTIATE_TEST_CASE_P(
ProgrammaticWebStateObserverTest,
WebStateObserverTest,
::testing::Values(
NavigationManagerChoice::TEST_LEGACY_NAVIGATION_MANAGER,
NavigationManagerChoice::TEST_WK_BASED_NAVIGATION_MANAGER));
} // namespace web