blob: ad0ac2101106a71bc0c18b1e306d170f64eb31d2 [file] [log] [blame]
// Copyright 2014 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.
#import "ios/web/web_state/web_state_impl.h"
#include <stddef.h>
#include <memory>
#import <OCMock/OCMock.h>
#include "base/base64.h"
#include "base/bind.h"
#include "base/logging.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#include "base/test/scoped_feature_list.h"
#import "ios/web/interstitials/web_interstitial_impl.h"
#import "ios/web/navigation/navigation_item_impl.h"
#import "ios/web/navigation/wk_navigation_util.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/java_script_dialog_presenter.h"
#include "ios/web/public/load_committed_details.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/fake_web_frame.h"
#include "ios/web/public/test/fakes/test_browser_state.h"
#import "ios/web/public/test/fakes/test_web_state_delegate.h"
#import "ios/web/public/test/fakes/test_web_state_observer.h"
#include "ios/web/public/test/web_test.h"
#import "ios/web/public/web_state/context_menu_params.h"
#include "ios/web/public/web_state/global_web_state_observer.h"
#import "ios/web/public/web_state/web_state_delegate.h"
#include "ios/web/public/web_state/web_state_observer.h"
#import "ios/web/public/web_state/web_state_policy_decider.h"
#import "ios/web/test/fakes/mock_interstitial_delegate.h"
#include "ios/web/web_state/global_web_state_event_tracker.h"
#import "ios/web/web_state/navigation_context_impl.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using testing::_;
using testing::Assign;
using testing::AtMost;
using testing::DoAll;
using testing::Return;
namespace web {
namespace {
// WebStateImplTest is parameterized on this enum to test both implementations
// of navigation manager.
enum class NavigationManagerChoice {
LEGACY,
WK_BASED,
};
// Test observer to check that the GlobalWebStateObserver methods are called as
// expected.
class TestGlobalWebStateObserver : public GlobalWebStateObserver {
public:
TestGlobalWebStateObserver()
: GlobalWebStateObserver(),
navigation_items_pruned_called_(false),
navigation_item_changed_called_(false),
navigation_item_committed_called_(false),
did_start_loading_called_(false),
did_stop_loading_called_(false),
did_start_navigation_called_(false),
page_loaded_called_with_success_(false),
web_state_destroyed_called_(false) {}
// Methods returning true if the corresponding GlobalWebStateObserver method
// has been called.
bool navigation_items_pruned_called() const {
return navigation_items_pruned_called_;
}
bool navigation_item_changed_called() const {
return navigation_item_changed_called_;
}
bool navigation_item_committed_called() const {
return navigation_item_committed_called_;
}
bool did_start_loading_called() const { return did_start_loading_called_; }
bool did_stop_loading_called() const { return did_stop_loading_called_; }
bool did_start_navigation_called() const {
return did_start_navigation_called_;
}
bool page_loaded_called_with_success() const {
return page_loaded_called_with_success_;
}
bool web_state_destroyed_called() const {
return web_state_destroyed_called_;
}
private:
// GlobalWebStateObserver implementation:
void NavigationItemsPruned(WebState* web_state,
size_t pruned_item_count) override {
navigation_items_pruned_called_ = true;
}
void NavigationItemChanged(WebState* web_state) override {
navigation_item_changed_called_ = true;
}
void NavigationItemCommitted(
WebState* web_state,
const LoadCommittedDetails& load_details) override {
navigation_item_committed_called_ = true;
}
void WebStateDidStartLoading(WebState* web_state) override {
did_start_loading_called_ = true;
}
void WebStateDidStopLoading(WebState* web_state) override {
did_stop_loading_called_ = true;
}
void WebStateDidStartNavigation(
WebState* web_state,
NavigationContext* navigation_context) override {
did_start_navigation_called_ = true;
}
void PageLoaded(WebState* web_state,
PageLoadCompletionStatus load_completion_status) override {
page_loaded_called_with_success_ =
load_completion_status == PageLoadCompletionStatus::SUCCESS;
}
void WebStateDestroyed(WebState* web_state) override {
web_state_destroyed_called_ = true;
}
bool navigation_items_pruned_called_;
bool navigation_item_changed_called_;
bool navigation_item_committed_called_;
bool did_start_loading_called_;
bool did_stop_loading_called_;
bool did_start_navigation_called_;
bool page_loaded_called_with_success_;
bool web_state_destroyed_called_;
};
// Test decider to check that the WebStatePolicyDecider methods are called as
// expected.
class MockWebStatePolicyDecider : public WebStatePolicyDecider {
public:
explicit MockWebStatePolicyDecider(WebState* web_state)
: WebStatePolicyDecider(web_state) {}
virtual ~MockWebStatePolicyDecider() {}
MOCK_METHOD2(ShouldAllowRequest,
bool(NSURLRequest* request,
const WebStatePolicyDecider::RequestInfo& request_info));
MOCK_METHOD2(ShouldAllowResponse,
bool(NSURLResponse* response, bool for_main_frame));
MOCK_METHOD0(WebStateDestroyed, void());
};
// Creates and returns an HttpResponseHeader using the string representation.
scoped_refptr<net::HttpResponseHeaders> HeadersFromString(const char* string) {
std::string raw_string(string);
std::string headers_string = net::HttpUtil::AssembleRawHeaders(
raw_string.c_str(), raw_string.length());
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders(headers_string));
return headers;
}
// Test callback for script commands.
// Sets |is_called| to true if it is called, and checks that the parameters
// match their expected values.
bool HandleScriptCommand(bool* is_called,
bool should_handle,
base::DictionaryValue* expected_value,
const GURL& expected_url,
bool expected_user_is_interacting,
bool expected_is_main_frame,
web::WebFrame* expected_sender_frame,
const base::DictionaryValue& value,
const GURL& url,
bool user_is_interacting,
bool is_main_frame,
web::WebFrame* sender_frame) {
*is_called = true;
EXPECT_TRUE(expected_value->Equals(&value));
EXPECT_EQ(expected_url, url);
EXPECT_EQ(expected_user_is_interacting, user_is_interacting);
EXPECT_EQ(expected_is_main_frame, is_main_frame);
EXPECT_EQ(expected_sender_frame, sender_frame);
return should_handle;
}
} // namespace
// Test fixture for web::WebStateImpl class.
class WebStateImplTest
: public web::WebTest,
public ::testing::WithParamInterface<NavigationManagerChoice> {
protected:
WebStateImplTest() : web::WebTest() {
if (GetParam() == NavigationManagerChoice::LEGACY) {
scoped_feature_list_.InitAndDisableFeature(
features::kSlimNavigationManager);
} else {
scoped_feature_list_.InitAndEnableFeature(
features::kSlimNavigationManager);
}
web::WebState::CreateParams params(GetBrowserState());
web_state_ = std::make_unique<web::WebStateImpl>(params);
}
// Adds PendingNavigationItem and commits it.
void AddCommittedNavigationItem() {
web_state_->GetNavigationManagerImpl().InitializeSession();
web_state_->GetNavigationManagerImpl().AddPendingItem(
GURL::EmptyGURL(), web::Referrer(), ui::PAGE_TRANSITION_LINK,
NavigationInitiationType::RENDERER_INITIATED,
NavigationManager::UserAgentOverrideOption::DESKTOP);
web_state_->GetNavigationManagerImpl().CommitPendingItem();
}
// Creates interstitial raw pointer and calls Show(). The pointer must be
// deleted by dismissing the interstitial.
WebInterstitialImpl* ShowInterstitial() {
auto delegate = std::make_unique<MockInterstitialDelegate>();
WebInterstitialImpl* result =
new WebInterstitialImpl(web_state_.get(), /*new_navigation=*/true,
GURL::EmptyGURL(), std::move(delegate));
result->Show();
return result;
}
std::unique_ptr<WebStateImpl> web_state_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_P(WebStateImplTest, WebUsageEnabled) {
// Default is false.
ASSERT_TRUE(web_state_->IsWebUsageEnabled());
web_state_->SetWebUsageEnabled(false);
EXPECT_FALSE(web_state_->IsWebUsageEnabled());
EXPECT_FALSE(web_state_->GetWebController().webUsageEnabled);
web_state_->SetWebUsageEnabled(true);
EXPECT_TRUE(web_state_->IsWebUsageEnabled());
EXPECT_TRUE(web_state_->GetWebController().webUsageEnabled);
}
TEST_P(WebStateImplTest, ResponseHeaders) {
GURL real_url("http://foo.com/bar");
GURL frame_url("http://frames-r-us.com/");
scoped_refptr<net::HttpResponseHeaders> real_headers(HeadersFromString(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"X-Should-Be-Here: yep\r\n"
"\r\n"));
scoped_refptr<net::HttpResponseHeaders> frame_headers(HeadersFromString(
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/pdf\r\n"
"X-Should-Not-Be-Here: oops\r\n"
"\r\n"));
// Simulate a load of a page with a frame.
web_state_->OnHttpResponseHeadersReceived(real_headers.get(), real_url);
web_state_->OnHttpResponseHeadersReceived(frame_headers.get(), frame_url);
// Include a hash to be sure it's handled correctly.
web_state_->UpdateHttpResponseHeaders(
GURL(real_url.spec() + std::string("#baz")));
// Verify that the right header set was kept.
ASSERT_TRUE(web_state_->GetHttpResponseHeaders());
EXPECT_TRUE(
web_state_->GetHttpResponseHeaders()->HasHeader("X-Should-Be-Here"));
EXPECT_FALSE(
web_state_->GetHttpResponseHeaders()->HasHeader("X-Should-Not-Be-Here"));
// And that it was parsed correctly.
EXPECT_EQ("text/html", web_state_->GetContentsMimeType());
}
TEST_P(WebStateImplTest, ResponseHeaderClearing) {
GURL url("http://foo.com/");
scoped_refptr<net::HttpResponseHeaders> headers(HeadersFromString(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"\r\n"));
web_state_->OnHttpResponseHeadersReceived(headers.get(), url);
// There should be no headers before loading.
EXPECT_EQ(NULL, web_state_->GetHttpResponseHeaders());
// There should be headers and parsed values after loading.
web_state_->UpdateHttpResponseHeaders(url);
ASSERT_TRUE(web_state_->GetHttpResponseHeaders());
EXPECT_TRUE(web_state_->GetHttpResponseHeaders()->HasHeader("Content-Type"));
EXPECT_NE("", web_state_->GetContentsMimeType());
// ... but not after loading another page, nor should there be specific
// parsed values.
web_state_->UpdateHttpResponseHeaders(GURL("http://elsewhere.com/"));
EXPECT_EQ(NULL, web_state_->GetHttpResponseHeaders());
EXPECT_EQ("", web_state_->GetContentsMimeType());
}
// Tests forwarding to WebStateObserver callbacks.
TEST_P(WebStateImplTest, ObserverTest) {
std::unique_ptr<TestWebStateObserver> observer(
new TestWebStateObserver(web_state_.get()));
EXPECT_EQ(web_state_.get(), observer->web_state());
// Test that WasShown() is called.
ASSERT_FALSE(web_state_->IsVisible());
ASSERT_FALSE(observer->was_shown_info());
web_state_->WasShown();
ASSERT_TRUE(observer->was_shown_info());
EXPECT_EQ(web_state_.get(), observer->was_shown_info()->web_state);
EXPECT_TRUE(web_state_->IsVisible());
// Test that WasShown() callback is not called for the second time.
observer = std::make_unique<TestWebStateObserver>(web_state_.get());
web_state_->WasShown();
EXPECT_FALSE(observer->was_shown_info());
// Test that WasHidden() is called.
ASSERT_TRUE(web_state_->IsVisible());
ASSERT_FALSE(observer->was_hidden_info());
web_state_->WasHidden();
ASSERT_TRUE(observer->was_hidden_info());
EXPECT_EQ(web_state_.get(), observer->was_hidden_info()->web_state);
EXPECT_FALSE(web_state_->IsVisible());
// Test that WasHidden() callback is not called for the second time.
observer = std::make_unique<TestWebStateObserver>(web_state_.get());
web_state_->WasHidden();
EXPECT_FALSE(observer->was_hidden_info());
// Test that LoadProgressChanged() is called.
ASSERT_FALSE(observer->change_loading_progress_info());
const double kTestLoadProgress = 0.75;
web_state_->SendChangeLoadProgress(kTestLoadProgress);
ASSERT_TRUE(observer->change_loading_progress_info());
EXPECT_EQ(web_state_.get(),
observer->change_loading_progress_info()->web_state);
EXPECT_EQ(kTestLoadProgress,
observer->change_loading_progress_info()->progress);
// Test that TitleWasSet() is called.
ASSERT_FALSE(observer->title_was_set_info());
web_state_->OnTitleChanged();
ASSERT_TRUE(observer->title_was_set_info());
EXPECT_EQ(web_state_.get(), observer->title_was_set_info()->web_state);
// Test that DidChangeVisibleSecurityState() is called.
ASSERT_FALSE(observer->did_change_visible_security_state_info());
web_state_->DidChangeVisibleSecurityState();
ASSERT_TRUE(observer->did_change_visible_security_state_info());
EXPECT_EQ(web_state_.get(),
observer->did_change_visible_security_state_info()->web_state);
// Test that FaviconUrlUpdated() is called.
ASSERT_FALSE(observer->update_favicon_url_candidates_info());
web::FaviconURL favicon_url(GURL("https://chromium.test/"),
web::FaviconURL::IconType::kTouchIcon,
{gfx::Size(5, 6)});
web_state_->OnFaviconUrlUpdated({favicon_url});
ASSERT_TRUE(observer->update_favicon_url_candidates_info());
EXPECT_EQ(web_state_.get(),
observer->update_favicon_url_candidates_info()->web_state);
ASSERT_EQ(1U,
observer->update_favicon_url_candidates_info()->candidates.size());
const web::FaviconURL& actual_favicon_url =
observer->update_favicon_url_candidates_info()->candidates[0];
EXPECT_EQ(favicon_url.icon_url, actual_favicon_url.icon_url);
EXPECT_EQ(favicon_url.icon_type, actual_favicon_url.icon_type);
ASSERT_EQ(favicon_url.icon_sizes.size(),
actual_favicon_url.icon_sizes.size());
EXPECT_EQ(favicon_url.icon_sizes[0].width(),
actual_favicon_url.icon_sizes[0].width());
EXPECT_EQ(favicon_url.icon_sizes[0].height(),
actual_favicon_url.icon_sizes[0].height());
// Test that WebFrameDidBecomeAvailable() is called.
ASSERT_FALSE(observer->web_frame_available_info());
web::FakeWebFrame main_frame("main", true, GURL());
web_state_->OnWebFrameAvailable(&main_frame);
ASSERT_TRUE(observer->web_frame_available_info());
EXPECT_EQ(web_state_.get(), observer->web_frame_available_info()->web_state);
EXPECT_EQ(&main_frame, observer->web_frame_available_info()->web_frame);
// Test that WebFrameWillBecomeUnavailable() is called.
ASSERT_FALSE(observer->web_frame_unavailable_info());
web_state_->OnWebFrameUnavailable(&main_frame);
ASSERT_TRUE(observer->web_frame_unavailable_info());
EXPECT_EQ(web_state_.get(),
observer->web_frame_unavailable_info()->web_state);
EXPECT_EQ(&main_frame, observer->web_frame_unavailable_info()->web_frame);
// Test that RenderProcessGone() is called.
SetIgnoreRenderProcessCrashesDuringTesting(true);
ASSERT_FALSE(observer->render_process_gone_info());
web_state_->OnRenderProcessGone();
ASSERT_TRUE(observer->render_process_gone_info());
EXPECT_EQ(web_state_.get(), observer->render_process_gone_info()->web_state);
// Test that DidFinishNavigation() is called.
ASSERT_FALSE(observer->did_finish_navigation_info());
const GURL url("http://test");
std::unique_ptr<NavigationContextImpl> context =
NavigationContextImpl::CreateNavigationContext(
web_state_.get(), url, /*has_user_gesture=*/true,
ui::PageTransition::PAGE_TRANSITION_AUTO_BOOKMARK,
/*is_renderer_initiated=*/true);
web_state_->OnNavigationFinished(context.get());
ASSERT_TRUE(observer->did_finish_navigation_info());
EXPECT_EQ(web_state_.get(),
observer->did_finish_navigation_info()->web_state);
NavigationContext* actual_context =
observer->did_finish_navigation_info()->context.get();
EXPECT_EQ(context->GetUrl(), actual_context->GetUrl());
EXPECT_TRUE(PageTransitionTypeIncludingQualifiersIs(
context->GetPageTransition(), actual_context->GetPageTransition()));
EXPECT_FALSE(actual_context->IsSameDocument());
EXPECT_FALSE(actual_context->IsPost());
EXPECT_FALSE(actual_context->GetError());
EXPECT_FALSE(actual_context->GetResponseHeaders());
// Test that DidStartNavigation() is called.
ASSERT_FALSE(observer->did_start_navigation_info());
web_state_->OnNavigationStarted(context.get());
ASSERT_TRUE(observer->did_start_navigation_info());
EXPECT_EQ(web_state_.get(), observer->did_start_navigation_info()->web_state);
actual_context = observer->did_start_navigation_info()->context.get();
EXPECT_EQ(context->GetUrl(), actual_context->GetUrl());
EXPECT_TRUE(PageTransitionTypeIncludingQualifiersIs(
context->GetPageTransition(), actual_context->GetPageTransition()));
EXPECT_FALSE(actual_context->IsSameDocument());
EXPECT_FALSE(actual_context->IsPost());
EXPECT_FALSE(actual_context->GetError());
EXPECT_FALSE(actual_context->GetResponseHeaders());
// Test that NavigationItemsPruned() is called.
ASSERT_FALSE(observer->navigation_items_pruned_info());
web_state_->OnNavigationItemsPruned(1);
ASSERT_TRUE(observer->navigation_items_pruned_info());
EXPECT_EQ(web_state_.get(),
observer->navigation_items_pruned_info()->web_state);
// Test that NavigationItemChanged() is called.
ASSERT_FALSE(observer->navigation_item_changed_info());
web_state_->OnNavigationItemChanged();
ASSERT_TRUE(observer->navigation_item_changed_info());
EXPECT_EQ(web_state_.get(),
observer->navigation_item_changed_info()->web_state);
// Test that NavigationItemCommitted() is called.
ASSERT_FALSE(observer->commit_navigation_info());
LoadCommittedDetails details;
auto item = std::make_unique<NavigationItemImpl>();
details.item = item.get();
web_state_->OnNavigationItemCommitted(details);
ASSERT_TRUE(observer->commit_navigation_info());
EXPECT_EQ(web_state_.get(), observer->commit_navigation_info()->web_state);
LoadCommittedDetails actual_details =
observer->commit_navigation_info()->load_details;
EXPECT_EQ(details.item, actual_details.item);
EXPECT_EQ(details.previous_item_index, actual_details.previous_item_index);
EXPECT_EQ(details.is_in_page, actual_details.is_in_page);
// Test that OnPageLoaded() is called with success when there is no error.
ASSERT_FALSE(observer->load_page_info());
web_state_->OnPageLoaded(url, false);
ASSERT_TRUE(observer->load_page_info());
EXPECT_EQ(web_state_.get(), observer->load_page_info()->web_state);
EXPECT_FALSE(observer->load_page_info()->success);
web_state_->OnPageLoaded(url, true);
ASSERT_TRUE(observer->load_page_info());
EXPECT_EQ(web_state_.get(), observer->load_page_info()->web_state);
EXPECT_TRUE(observer->load_page_info()->success);
// Test that OnTitleChanged() is called.
observer = std::make_unique<TestWebStateObserver>(web_state_.get());
ASSERT_FALSE(observer->title_was_set_info());
web_state_->OnTitleChanged();
ASSERT_TRUE(observer->title_was_set_info());
EXPECT_EQ(web_state_.get(), observer->title_was_set_info()->web_state);
// Test that WebStateDestroyed() is called.
EXPECT_FALSE(observer->web_state_destroyed_info());
web_state_.reset();
EXPECT_TRUE(observer->web_state_destroyed_info());
EXPECT_EQ(nullptr, observer->web_state());
}
// Tests that placeholder navigations are not visible to WebStateObservers.
TEST_P(WebStateImplTest, PlaceholderNavigationNotExposedToObservers) {
TestWebStateObserver observer(web_state_.get());
GURL placeholder_url =
wk_navigation_util::CreatePlaceholderUrlForUrl(GURL("chrome://newtab"));
std::unique_ptr<NavigationContextImpl> context =
NavigationContextImpl::CreateNavigationContext(
web_state_.get(), placeholder_url,
/*has_user_gesture=*/true,
ui::PageTransition::PAGE_TRANSITION_AUTO_BOOKMARK,
/*is_renderer_initiated=*/true);
context->SetPlaceholderNavigation(true);
// Test that OnPageLoaded() is not called.
web_state_->OnPageLoaded(placeholder_url, /*load_success=*/true);
EXPECT_FALSE(observer.load_page_info());
web_state_->OnPageLoaded(placeholder_url, /*load_success=*/false);
EXPECT_FALSE(observer.load_page_info());
// Test that OnNavigationStarted() is not called.
web_state_->OnNavigationStarted(context.get());
EXPECT_FALSE(observer.did_start_navigation_info());
// Test that OnNavigationFinished() is not called.
web_state_->OnNavigationFinished(context.get());
EXPECT_FALSE(observer.did_finish_navigation_info());
}
// Tests that WebStateDelegate methods appropriately called.
TEST_P(WebStateImplTest, DelegateTest) {
TestWebStateDelegate delegate;
web_state_->SetDelegate(&delegate);
// Test that CreateNewWebState() is called.
GURL child_url("https://child.test/");
GURL opener_url("https://opener.test/");
EXPECT_FALSE(delegate.last_create_new_web_state_request());
web_state_->CreateNewWebState(child_url, opener_url, true);
TestCreateNewWebStateRequest* create_new_web_state_request =
delegate.last_create_new_web_state_request();
ASSERT_TRUE(create_new_web_state_request);
EXPECT_EQ(web_state_.get(), create_new_web_state_request->web_state);
EXPECT_EQ(child_url, create_new_web_state_request->url);
EXPECT_EQ(opener_url, create_new_web_state_request->opener_url);
EXPECT_TRUE(create_new_web_state_request->initiated_by_user);
// Test that CloseWebState() is called.
EXPECT_FALSE(delegate.last_close_web_state_request());
web_state_->CloseWebState();
ASSERT_TRUE(delegate.last_close_web_state_request());
EXPECT_EQ(web_state_.get(),
delegate.last_close_web_state_request()->web_state);
// Test that OpenURLFromWebState() is called.
WebState::OpenURLParams params(GURL("https://chromium.test/"), Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_LINK, true);
EXPECT_FALSE(delegate.last_open_url_request());
web_state_->OpenURL(params);
TestOpenURLRequest* open_url_request = delegate.last_open_url_request();
ASSERT_TRUE(open_url_request);
EXPECT_EQ(web_state_.get(), open_url_request->web_state);
WebState::OpenURLParams actual_params = open_url_request->params;
EXPECT_EQ(params.url, actual_params.url);
EXPECT_EQ(params.referrer.url, actual_params.referrer.url);
EXPECT_EQ(params.referrer.policy, actual_params.referrer.policy);
EXPECT_EQ(params.disposition, actual_params.disposition);
EXPECT_TRUE(
PageTransitionCoreTypeIs(params.transition, actual_params.transition));
EXPECT_EQ(params.is_renderer_initiated, actual_params.is_renderer_initiated);
// Test that HandleContextMenu() is called.
EXPECT_FALSE(delegate.handle_context_menu_called());
web::ContextMenuParams context_menu_params;
web_state_->HandleContextMenu(context_menu_params);
EXPECT_TRUE(delegate.handle_context_menu_called());
// Test that ShowRepostFormWarningDialog() is called.
EXPECT_FALSE(delegate.last_repost_form_request());
base::Callback<void(bool)> repost_callback;
web_state_->ShowRepostFormWarningDialog(repost_callback);
ASSERT_TRUE(delegate.last_repost_form_request());
EXPECT_EQ(delegate.last_repost_form_request()->web_state, web_state_.get());
// Test that GetJavaScriptDialogPresenter() is called.
TestJavaScriptDialogPresenter* presenter =
delegate.GetTestJavaScriptDialogPresenter();
EXPECT_FALSE(delegate.get_java_script_dialog_presenter_called());
EXPECT_TRUE(presenter->requested_dialogs().empty());
EXPECT_FALSE(presenter->cancel_dialogs_called());
__block bool callback_called = false;
web_state_->RunJavaScriptDialog(GURL(), JAVASCRIPT_DIALOG_TYPE_ALERT, @"",
nil, base::BindOnce(^(bool, NSString*) {
callback_called = true;
}));
EXPECT_TRUE(delegate.get_java_script_dialog_presenter_called());
EXPECT_EQ(1U, presenter->requested_dialogs().size());
EXPECT_TRUE(callback_called);
EXPECT_FALSE(presenter->cancel_dialogs_called());
web_state_->CancelDialogs();
EXPECT_TRUE(presenter->cancel_dialogs_called());
// Test that OnAuthRequired() is called.
EXPECT_FALSE(delegate.last_authentication_request());
NSURLProtectionSpace* protection_space = [[NSURLProtectionSpace alloc] init];
NSURLCredential* credential = [[NSURLCredential alloc] init];
WebStateDelegate::AuthCallback callback;
web_state_->OnAuthRequired(protection_space, credential, callback);
ASSERT_TRUE(delegate.last_authentication_request());
EXPECT_EQ(delegate.last_authentication_request()->web_state,
web_state_.get());
EXPECT_EQ(delegate.last_authentication_request()->protection_space,
protection_space);
EXPECT_EQ(delegate.last_authentication_request()->credential, credential);
// Test that ShouldPreviewLink() is delegated correctly.
GURL link_url("http://link.test/");
delegate.SetShouldPreviewLink(false);
delegate.ClearLastLinkURL();
EXPECT_FALSE(web_state_->ShouldPreviewLink(link_url));
EXPECT_EQ(link_url, delegate.last_link_url());
delegate.SetShouldPreviewLink(true);
delegate.ClearLastLinkURL();
EXPECT_TRUE(web_state_->ShouldPreviewLink(link_url));
EXPECT_EQ(link_url, delegate.last_link_url());
// Test that GetPreviewingViewController() is delegated correctly.
UIViewController* previewing_view_controller =
OCMClassMock([UIViewController class]);
delegate.SetPreviewingViewController(previewing_view_controller);
delegate.ClearLastLinkURL();
EXPECT_EQ(previewing_view_controller,
web_state_->GetPreviewingViewController(link_url));
EXPECT_EQ(link_url, delegate.last_link_url());
// Test that CommitPreviewingViewController() is called.
delegate.ClearLastPreviewingViewController();
web_state_->CommitPreviewingViewController(previewing_view_controller);
EXPECT_EQ(previewing_view_controller,
delegate.last_previewing_view_controller());
}
// Verifies that GlobalWebStateObservers are called when expected.
TEST_P(WebStateImplTest, GlobalObserverTest) {
std::unique_ptr<TestGlobalWebStateObserver> observer(
new TestGlobalWebStateObserver());
// Test that NavigationItemsPruned() is called.
EXPECT_FALSE(observer->navigation_items_pruned_called());
web_state_->OnNavigationItemsPruned(1);
EXPECT_TRUE(observer->navigation_items_pruned_called());
// Test that NavigationItemChanged() is called.
EXPECT_FALSE(observer->navigation_item_changed_called());
web_state_->OnNavigationItemChanged();
EXPECT_TRUE(observer->navigation_item_changed_called());
// Test that NavigationItemCommitted() is called.
EXPECT_FALSE(observer->navigation_item_committed_called());
LoadCommittedDetails details;
auto item = std::make_unique<NavigationItemImpl>();
details.item = item.get();
web_state_->OnNavigationItemCommitted(details);
EXPECT_TRUE(observer->navigation_item_committed_called());
// Test that DidStartNavigation() is called.
EXPECT_FALSE(observer->did_start_navigation_called());
std::unique_ptr<NavigationContextImpl> context =
NavigationContextImpl::CreateNavigationContext(
web_state_.get(), GURL::EmptyGURL(), /*has_user_gesture=*/true,
ui::PageTransition::PAGE_TRANSITION_AUTO_BOOKMARK,
/*is_renderer_initiated=*/true);
web_state_->OnNavigationStarted(context.get());
EXPECT_TRUE(observer->did_start_navigation_called());
// Test that WebStateDidStartLoading() is called.
EXPECT_FALSE(observer->did_start_loading_called());
web_state_->SetIsLoading(true);
EXPECT_TRUE(observer->did_start_loading_called());
// Test that WebStateDidStopLoading() is called.
EXPECT_FALSE(observer->did_stop_loading_called());
web_state_->SetIsLoading(false);
EXPECT_TRUE(observer->did_stop_loading_called());
// Test that OnPageLoaded() is called with success when there is no error.
EXPECT_FALSE(observer->page_loaded_called_with_success());
web_state_->OnPageLoaded(GURL("http://test"), false);
EXPECT_FALSE(observer->page_loaded_called_with_success());
web_state_->OnPageLoaded(GURL("http://test"), true);
EXPECT_TRUE(observer->page_loaded_called_with_success());
// Test that WebStateDestroyed() is called.
EXPECT_FALSE(observer->web_state_destroyed_called());
web_state_.reset();
EXPECT_TRUE(observer->web_state_destroyed_called());
}
// A Google Mock matcher which matches 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.target_frame_is_main ==
expected_request_info.target_frame_is_main &&
arg.has_user_gesture == expected_request_info.has_user_gesture;
}
// Verifies that policy deciders are correctly called by the web state.
TEST_P(WebStateImplTest, PolicyDeciderTest) {
MockWebStatePolicyDecider decider(web_state_.get());
MockWebStatePolicyDecider decider2(web_state_.get());
EXPECT_EQ(web_state_.get(), decider.web_state());
NSURL* url = [NSURL URLWithString:@"http://example.com"];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
NSURLResponse* response = [[NSURLResponse alloc] initWithURL:url
MIMEType:@"text/html"
expectedContentLength:0
textEncodingName:nil];
// Test that ShouldAllowRequest() is called for the same parameters.
WebStatePolicyDecider::RequestInfo request_info_main_frame(
ui::PageTransition::PAGE_TRANSITION_LINK,
/*target_main_frame=*/true,
/*has_user_gesture=*/false);
EXPECT_CALL(decider, ShouldAllowRequest(
request, RequestInfoMatch(request_info_main_frame)))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(decider2, ShouldAllowRequest(
request, RequestInfoMatch(request_info_main_frame)))
.Times(1)
.WillOnce(Return(true));
EXPECT_TRUE(web_state_->ShouldAllowRequest(request, request_info_main_frame));
WebStatePolicyDecider::RequestInfo request_info_iframe(
ui::PageTransition::PAGE_TRANSITION_LINK,
/*target_main_frame=*/false,
/*has_user_gesture=*/false);
EXPECT_CALL(decider, ShouldAllowRequest(
request, RequestInfoMatch(request_info_iframe)))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(decider2, ShouldAllowRequest(
request, RequestInfoMatch(request_info_iframe)))
.Times(1)
.WillOnce(Return(true));
EXPECT_TRUE(web_state_->ShouldAllowRequest(request, request_info_iframe));
// Test that ShouldAllowRequest() is stopping on negative answer. Only one
// one the decider should be called.
{
bool decider_called = false;
bool decider2_called = false;
EXPECT_CALL(
decider,
ShouldAllowRequest(request, RequestInfoMatch(request_info_main_frame)))
.Times(AtMost(1))
.WillOnce(DoAll(Assign(&decider_called, true), Return(false)));
EXPECT_CALL(
decider2,
ShouldAllowRequest(request, RequestInfoMatch(request_info_main_frame)))
.Times(AtMost(1))
.WillOnce(DoAll(Assign(&decider2_called, true), Return(false)));
EXPECT_FALSE(
web_state_->ShouldAllowRequest(request, request_info_main_frame));
EXPECT_FALSE(decider_called && decider2_called);
}
// Test that ShouldAllowResponse() is called.
EXPECT_CALL(decider, ShouldAllowResponse(response, true))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(decider2, ShouldAllowResponse(response, true))
.Times(1)
.WillOnce(Return(true));
EXPECT_TRUE(web_state_->ShouldAllowResponse(response, true));
// Test that ShouldAllowResponse() is stopping on negative answer. Only one
// one the decider should be called.
{
bool decider_called = false;
bool decider2_called = false;
EXPECT_CALL(decider, ShouldAllowResponse(response, false))
.Times(AtMost(1))
.WillOnce(DoAll(Assign(&decider_called, true), Return(false)));
EXPECT_CALL(decider2, ShouldAllowResponse(response, false))
.Times(AtMost(1))
.WillOnce(DoAll(Assign(&decider2_called, true), Return(false)));
EXPECT_FALSE(web_state_->ShouldAllowResponse(response, false));
EXPECT_FALSE(decider_called && decider2_called);
}
// Test that WebStateDestroyed() is called.
EXPECT_CALL(decider, WebStateDestroyed()).Times(1);
EXPECT_CALL(decider2, WebStateDestroyed()).Times(1);
web_state_.reset();
EXPECT_EQ(nullptr, decider.web_state());
}
// Tests that script command callbacks are called correctly.
TEST_P(WebStateImplTest, ScriptCommand) {
// Set up three script command callbacks.
const std::string kPrefix1("prefix1");
const std::string kCommand1("prefix1.command1");
base::DictionaryValue value_1;
value_1.SetString("a", "b");
const GURL kUrl1("http://foo");
bool is_called_1 = false;
web::FakeWebFrame main_frame("main", true, GURL());
web_state_->AddScriptCommandCallback(
base::BindRepeating(&HandleScriptCommand, &is_called_1,
/*should_handle*/ true, &value_1, kUrl1,
/*expected_user_is_interacting*/ false,
/*expected_is_main_frame*/ true, &main_frame),
kPrefix1);
const std::string kPrefix2("prefix2");
const std::string kCommand2("prefix2.command2");
base::DictionaryValue value_2;
value_2.SetString("c", "d");
const GURL kUrl2("http://bar");
bool is_called_2 = false;
web_state_->AddScriptCommandCallback(
base::BindRepeating(&HandleScriptCommand, &is_called_2,
/*should_handle*/ false, &value_2, kUrl2,
/*expected_user_is_interacting*/ false,
/*expected_is_main_frame*/ true, &main_frame),
kPrefix2);
const std::string kPrefix3("prefix3");
const std::string kCommand3("prefix3.command3");
base::DictionaryValue value_3;
value_3.SetString("e", "f");
const GURL kUrl3("http://iframe");
bool is_called_3 = false;
web::FakeWebFrame subframe("subframe", false, GURL());
web_state_->AddScriptCommandCallback(
base::BindRepeating(&HandleScriptCommand, &is_called_3,
/*should_handle*/ true, &value_3, kUrl3,
/*expected_user_is_interacting*/ false,
/*expected_is_main_frame*/ false, &subframe),
kPrefix3);
// Check that a irrelevant or invalid command does not trigger the callbacks.
EXPECT_FALSE(web_state_->OnScriptCommandReceived(
"wohoo.blah", value_1, kUrl1,
/*user_is_interacting*/ false, /*is_main_frame*/ true,
/*sender_frame*/ &main_frame));
EXPECT_FALSE(is_called_1);
EXPECT_FALSE(is_called_2);
EXPECT_FALSE(is_called_3);
EXPECT_FALSE(web_state_->OnScriptCommandReceived(
"prefix1ButMissingDot", value_1, kUrl1, /*user_is_interacting*/ false,
/*is_main_frame*/ true, /*sender_frame*/ &main_frame));
EXPECT_FALSE(is_called_1);
EXPECT_FALSE(is_called_2);
EXPECT_FALSE(is_called_3);
// Check that only the callback matching the prefix is called, with the
// expected parameters and return value;
EXPECT_TRUE(
web_state_->OnScriptCommandReceived(kCommand1, value_1, kUrl1,
/*user_is_interacting*/ false,
/*is_main_frame*/ true,
/*sender_frame*/ &main_frame));
EXPECT_TRUE(is_called_1);
EXPECT_FALSE(is_called_2);
EXPECT_FALSE(is_called_3);
is_called_1 = false;
// Check that sending message from iframe sets |is_main_frame| to false.
EXPECT_TRUE(web_state_->OnScriptCommandReceived(kCommand3, value_3, kUrl3,
/*user_is_interacting*/ false,
/*is_main_frame*/ false,
/*sender_frame*/ &subframe));
EXPECT_FALSE(is_called_1);
EXPECT_FALSE(is_called_2);
EXPECT_TRUE(is_called_3);
is_called_3 = false;
// Remove the callback and check it is no longer called.
web_state_->RemoveScriptCommandCallback(kPrefix1);
EXPECT_FALSE(web_state_->OnScriptCommandReceived(
kCommand1, value_1, kUrl1,
/*user_is_interacting*/ false, /*is_main_frame*/ true,
/*sender_frame*/ &main_frame));
EXPECT_FALSE(is_called_1);
EXPECT_FALSE(is_called_2);
EXPECT_FALSE(is_called_3);
// Check that a false return value is forwarded correctly.
EXPECT_FALSE(web_state_->OnScriptCommandReceived(
kCommand2, value_2, kUrl2,
/*user_is_interacting*/ false, /*is_main_frame*/ true,
/*sender_frame*/ &main_frame));
EXPECT_FALSE(is_called_1);
EXPECT_TRUE(is_called_2);
EXPECT_FALSE(is_called_3);
web_state_->RemoveScriptCommandCallback(kPrefix2);
web_state_->RemoveScriptCommandCallback(kPrefix3);
}
// Tests that WebState::CreateParams::created_with_opener is translated to
// WebState::HasOpener() return values.
TEST_P(WebStateImplTest, CreatedWithOpener) {
// Verify that the HasOpener() returns false if not specified in the create
// params.
EXPECT_FALSE(web_state_->HasOpener());
// Set |created_with_opener| to true and verify that HasOpener() returns true.
WebState::CreateParams params_with_opener =
WebState::CreateParams(GetBrowserState());
params_with_opener.created_with_opener = true;
std::unique_ptr<WebState> web_state_with_opener =
WebState::Create(params_with_opener);
EXPECT_TRUE(web_state_with_opener->HasOpener());
}
// Tests that WebStateObserver::FaviconUrlUpdated is called for same-document
// navigations.
TEST_P(WebStateImplTest, FaviconUpdateForSameDocumentNavigations) {
auto observer = std::make_unique<TestWebStateObserver>(web_state_.get());
// No callback if icons has not been fetched yet.
std::unique_ptr<NavigationContextImpl> context =
NavigationContextImpl::CreateNavigationContext(
web_state_.get(), GURL::EmptyGURL(),
/*has_user_gesture=*/false, ui::PageTransition::PAGE_TRANSITION_LINK,
/*is_renderer_initiated=*/false);
context->SetIsSameDocument(true);
web_state_->OnNavigationFinished(context.get());
EXPECT_FALSE(observer->update_favicon_url_candidates_info());
// Callback is called when icons were fetched.
observer = std::make_unique<TestWebStateObserver>(web_state_.get());
web::FaviconURL favicon_url(GURL("https://chromium.test/"),
web::FaviconURL::IconType::kTouchIcon,
{gfx::Size(5, 6)});
web_state_->OnFaviconUrlUpdated({favicon_url});
EXPECT_TRUE(observer->update_favicon_url_candidates_info());
// Callback is now called after same-document navigation.
observer = std::make_unique<TestWebStateObserver>(web_state_.get());
web_state_->OnNavigationFinished(context.get());
ASSERT_TRUE(observer->update_favicon_url_candidates_info());
ASSERT_EQ(1U,
observer->update_favicon_url_candidates_info()->candidates.size());
const web::FaviconURL& actual_favicon_url =
observer->update_favicon_url_candidates_info()->candidates[0];
EXPECT_EQ(favicon_url.icon_url, actual_favicon_url.icon_url);
EXPECT_EQ(favicon_url.icon_type, actual_favicon_url.icon_type);
ASSERT_EQ(favicon_url.icon_sizes.size(),
actual_favicon_url.icon_sizes.size());
EXPECT_EQ(favicon_url.icon_sizes[0].width(),
actual_favicon_url.icon_sizes[0].width());
EXPECT_EQ(favicon_url.icon_sizes[0].height(),
actual_favicon_url.icon_sizes[0].height());
// Document change navigation does not call callback.
observer = std::make_unique<TestWebStateObserver>(web_state_.get());
context->SetIsSameDocument(false);
web_state_->OnNavigationFinished(context.get());
EXPECT_FALSE(observer->update_favicon_url_candidates_info());
// Previous candidates were invalidated by the document change. No callback
// if icons has not been fetched yet.
context->SetIsSameDocument(true);
web_state_->OnNavigationFinished(context.get());
EXPECT_FALSE(observer->update_favicon_url_candidates_info());
}
// Tests that BuildSessionStorage() and GetTitle() return information about the
// most recently restored session if no navigation item has been committed.
TEST_P(WebStateImplTest, UncommittedRestoreSession) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
web::features::kSlimNavigationManager);
GURL url("http://test.com");
CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init];
session_storage.lastCommittedItemIndex = 0;
CRWNavigationItemStorage* item_storage =
[[CRWNavigationItemStorage alloc] init];
item_storage.title = base::SysNSStringToUTF16(@"Title");
item_storage.virtualURL = url;
session_storage.itemStorages = @[ item_storage ];
web::WebState::CreateParams params(GetBrowserState());
WebStateImpl web_state(params, session_storage);
CRWSessionStorage* extracted_session_storage =
web_state.BuildSessionStorage();
EXPECT_EQ(0, extracted_session_storage.lastCommittedItemIndex);
EXPECT_EQ(1U, extracted_session_storage.itemStorages.count);
EXPECT_NSEQ(@"Title", base::SysUTF16ToNSString(web_state.GetTitle()));
EXPECT_EQ(url, web_state.GetVisibleURL());
}
TEST_P(WebStateImplTest, NoUncommittedRestoreSession) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
web::features::kSlimNavigationManager);
CRWSessionStorage* session_storage = web_state_->BuildSessionStorage();
EXPECT_EQ(-1, session_storage.lastCommittedItemIndex);
EXPECT_NSEQ(@[], session_storage.itemStorages);
EXPECT_TRUE(web_state_->GetTitle().empty());
EXPECT_EQ(GURL::EmptyGURL(), web_state_->GetVisibleURL());
}
// Tests showing and clearing interstitial when NavigationManager is
// empty.
TEST_P(WebStateImplTest, ShowAndClearInterstitialWithNoCommittedItems) {
web_state_->GetNavigationManagerImpl().InitializeSession();
// Existence of a pending item is a precondition for a transient item.
web_state_->GetNavigationManagerImpl().AddPendingItem(
GURL::EmptyGURL(), web::Referrer(), ui::PAGE_TRANSITION_LINK,
NavigationInitiationType::BROWSER_INITIATED,
NavigationManager::UserAgentOverrideOption::DESKTOP);
// Show the interstitial.
ASSERT_FALSE(web_state_->IsShowingWebInterstitial());
ASSERT_FALSE(web_state_->GetWebInterstitial());
WebInterstitialImpl* interstitial = ShowInterstitial();
ASSERT_EQ(interstitial, web_state_->GetWebInterstitial());
ASSERT_TRUE(web_state_->IsShowingWebInterstitial());
// Clear the interstitial.
TestWebStateObserver observer(web_state_.get());
ASSERT_FALSE(observer.did_change_visible_security_state_info());
web_state_->ClearTransientContent();
// Verify that interstitial was removed and DidChangeVisibleSecurityState was
// called.
EXPECT_FALSE(web_state_->IsShowingWebInterstitial());
EXPECT_FALSE(web_state_->GetWebInterstitial());
ASSERT_TRUE(observer.did_change_visible_security_state_info());
EXPECT_EQ(web_state_.get(),
observer.did_change_visible_security_state_info()->web_state);
}
// Tests showing and clearing interstitial when NavigationManager has a
// committed item.
TEST_P(WebStateImplTest, ShowAndClearInterstitialWithCommittedItem) {
if (GetParam() == NavigationManagerChoice::WK_BASED) {
// TODO(crbug.com/862733): This test requires injecting a committed item to
// navigation manager, which can't be done with WKBasedNavigationManager.
// Re-enable this test after switching to TestNavigationManager.
return;
}
// Add SECURITY_STYLE_AUTHENTICATED committed item to navigation manager.
AddCommittedNavigationItem();
web_state_->GetNavigationManagerImpl()
.GetLastCommittedItem()
->GetSSL()
.security_style = SECURITY_STYLE_AUTHENTICATED;
// Show the interstitial.
ASSERT_FALSE(web_state_->IsShowingWebInterstitial());
ASSERT_FALSE(web_state_->GetWebInterstitial());
WebInterstitialImpl* interstitial = ShowInterstitial();
ASSERT_TRUE(web_state_->IsShowingWebInterstitial());
ASSERT_EQ(interstitial, web_state_->GetWebInterstitial());
// Clear the interstitial.
TestWebStateObserver observer(web_state_.get());
ASSERT_FALSE(observer.did_change_visible_security_state_info());
web_state_->ClearTransientContent();
// Verify that interstitial was removed and DidChangeVisibleSecurityState was
// called.
EXPECT_FALSE(web_state_->IsShowingWebInterstitial());
EXPECT_FALSE(web_state_->GetWebInterstitial());
ASSERT_TRUE(observer.did_change_visible_security_state_info());
EXPECT_EQ(web_state_.get(),
observer.did_change_visible_security_state_info()->web_state);
}
// Tests showing and clearing interstitial when visible SSL status does not
// change.
TEST_P(WebStateImplTest, ShowAndClearInterstitialWithoutChangingSslStatus) {
if (GetParam() == NavigationManagerChoice::WK_BASED) {
// TODO(crbug.com/862733): This test requires injecting a committed item to
// navigation manager, which can't be done with WKBasedNavigationManager.
// Re-enable this test after switching to TestNavigationManager.
return;
}
// Add a committed item to navigation manager with default SSL status.
AddCommittedNavigationItem();
// Show the interstitial.
ASSERT_FALSE(web_state_->IsShowingWebInterstitial());
ASSERT_FALSE(web_state_->GetWebInterstitial());
WebInterstitialImpl* interstitial = ShowInterstitial();
ASSERT_TRUE(web_state_->IsShowingWebInterstitial());
ASSERT_EQ(interstitial, web_state_->GetWebInterstitial());
// Clear the interstitial.
TestWebStateObserver observer(web_state_.get());
ASSERT_FALSE(observer.did_change_visible_security_state_info());
web_state_->ClearTransientContent();
// Verify that interstitial was removed.
EXPECT_FALSE(web_state_->IsShowingWebInterstitial());
EXPECT_FALSE(web_state_->GetWebInterstitial());
// DidChangeVisibleSecurityState is not called, because last committed and
// transient items had the same SSL status.
EXPECT_FALSE(observer.did_change_visible_security_state_info());
}
INSTANTIATE_TEST_CASE_P(ProgrammaticWebStateImplTest,
WebStateImplTest,
::testing::Values(NavigationManagerChoice::LEGACY,
NavigationManagerChoice::WK_BASED));
} // namespace web