| // 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. |
| |
| #import "ios/web/public/web_state/web_state.h" |
| |
| #import <UIKit/UIKit.h> |
| |
| #include "base/bind.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #import "base/test/ios/wait_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.h" |
| #include "ios/web/common/features.h" |
| #import "ios/web/navigation/navigation_manager_impl.h" |
| #import "ios/web/navigation/wk_based_navigation_manager_impl.h" |
| #import "ios/web/navigation/wk_navigation_util.h" |
| #include "ios/web/public/js_messaging/web_frame.h" |
| #import "ios/web/public/navigation/navigation_item.h" |
| #import "ios/web/public/navigation/navigation_manager.h" |
| #import "ios/web/public/session/crw_navigation_item_storage.h" |
| #import "ios/web/public/session/crw_session_storage.h" |
| #import "ios/web/public/test/fakes/test_web_client.h" |
| #import "ios/web/public/test/fakes/test_web_state_delegate.h" |
| #import "ios/web/public/test/web_test_with_web_state.h" |
| #import "ios/web/public/test/web_view_content_test_util.h" |
| #import "ios/web/public/web_client.h" |
| #import "ios/web/public/web_state/web_state_observer.h" |
| #include "ios/web/test/test_url_constants.h" |
| #include "net/test/embedded_test_server/default_handlers.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using base::test::ios::WaitUntilConditionOrTimeout; |
| using base::test::ios::kWaitForJSCompletionTimeout; |
| using base::test::ios::kWaitForPageLoadTimeout; |
| |
| namespace web { |
| namespace { |
| |
| // Error when loading an app specific page. |
| const char kUnsupportedUrlErrorPage[] = "NSURLErrorDomain error -1002."; |
| |
| // A text string from the test HTML page in the session storage returned by |
| // GetTestSessionStorage(). |
| const char kTestSessionStoragePageText[] = "pony"; |
| |
| // A text string that is included in |kTestPageHTML|. |
| const char kTextInTestPageHTML[] = "test"; |
| |
| // A test page HTML containing |kTextInTestPageHTML|. |
| const char kTestPageHTML[] = "<html><body>test</body><html>"; |
| |
| // Returns a session storage with a single committed entry of a test HTML page. |
| CRWSessionStorage* GetTestSessionStorage() { |
| base::FilePath path; |
| base::PathService::Get(base::DIR_MODULE, &path); |
| path = path.Append( |
| FILE_PATH_LITERAL("ios/testing/data/http_server_files/pony.html")); |
| GURL testFileUrl(base::StringPrintf("file://%s", path.value().c_str())); |
| |
| CRWSessionStorage* result = [[CRWSessionStorage alloc] init]; |
| result.lastCommittedItemIndex = 0; |
| CRWNavigationItemStorage* item = [[CRWNavigationItemStorage alloc] init]; |
| [item setVirtualURL:testFileUrl]; |
| [result setItemStorages:@[ item ]]; |
| return result; |
| } |
| } // namespace |
| |
| using wk_navigation_util::IsWKInternalUrl; |
| |
| // WebStateTest is parameterized on this enum to test both the legacy |
| // implementation of navigation manager and the experimental implementation. |
| enum NavigationManagerChoice { |
| TEST_LEGACY_NAVIGATION_MANAGER, |
| TEST_WK_BASED_NAVIGATION_MANAGER, |
| }; |
| |
| // Test fixture for web::WebTest class. |
| class WebStateTest |
| : public TestWebClient, |
| public WebTestWithWebState, |
| public ::testing::WithParamInterface<NavigationManagerChoice> { |
| protected: |
| WebStateTest() { |
| if (GetParam() == TEST_LEGACY_NAVIGATION_MANAGER) { |
| feature_list_.InitAndDisableFeature( |
| web::features::kSlimNavigationManager); |
| } else { |
| feature_list_.InitAndEnableFeature(web::features::kSlimNavigationManager); |
| } |
| } |
| |
| base::HistogramTester histogram_tester_; |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Tests script execution with and without callback. |
| TEST_P(WebStateTest, ScriptExecution) { |
| ASSERT_TRUE(LoadHtml("<html></html>")); |
| |
| // Execute script without callback. |
| web_state()->ExecuteJavaScript(base::UTF8ToUTF16("window.foo = 'bar'")); |
| |
| // Execute script with callback. |
| __block std::unique_ptr<base::Value> execution_result; |
| __block bool execution_complete = false; |
| web_state()->ExecuteJavaScript(base::UTF8ToUTF16("window.foo"), |
| base::BindOnce(^(const base::Value* value) { |
| execution_result = value->CreateDeepCopy(); |
| execution_complete = true; |
| })); |
| WaitForCondition(^{ |
| return execution_complete; |
| }); |
| |
| ASSERT_TRUE(execution_result); |
| std::string string_result; |
| execution_result->GetAsString(&string_result); |
| EXPECT_EQ("bar", string_result); |
| } |
| |
| // Tests that executing user JavaScript registers user interaction. |
| TEST_P(WebStateTest, UserScriptExecution) { |
| web::TestWebStateDelegate delegate; |
| web_state()->SetDelegate(&delegate); |
| ASSERT_TRUE(delegate.child_windows().empty()); |
| |
| ASSERT_TRUE(LoadHtml("<html></html>")); |
| web_state()->ExecuteUserJavaScript(@"window.open('', target='_blank');"); |
| |
| web::TestWebStateDelegate* delegate_ptr = &delegate; |
| bool suceess = WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| // Child window can only be open if the user interaction was registered. |
| return delegate_ptr->child_windows().size() == 1; |
| }); |
| |
| ASSERT_TRUE(suceess); |
| EXPECT_TRUE(delegate.child_windows()[0]); |
| } |
| |
| // Tests loading progress. |
| TEST_P(WebStateTest, LoadingProgress) { |
| EXPECT_FLOAT_EQ(0.0, web_state()->GetLoadingProgress()); |
| ASSERT_TRUE(LoadHtml("<html></html>")); |
| WaitForCondition(^bool() { |
| return web_state()->GetLoadingProgress() == 1.0; |
| }); |
| } |
| |
| // Tests that page which overrides window.webkit object does not break the |
| // messaging system. |
| TEST_P(WebStateTest, OverridingWebKitObject) { |
| // Add a script command handler. |
| __block bool message_received = false; |
| const web::WebState::ScriptCommandCallback callback = base::BindRepeating( |
| ^(const base::DictionaryValue&, const GURL&, |
| /*interacted*/ bool, /*is_main_frame*/ web::WebFrame*) { |
| message_received = true; |
| }); |
| auto subscription = web_state()->AddScriptCommandCallback(callback, "test"); |
| |
| // Load the page which overrides window.webkit object and wait until the |
| // test message is received. |
| ASSERT_TRUE(LoadHtml( |
| "<script>" |
| " webkit = undefined;" |
| " __gCrWeb.message.invokeOnHost({'command': 'test.webkit-overriding'});" |
| "</script>")); |
| |
| WaitForCondition(^{ |
| return message_received; |
| }); |
| } |
| |
| // Tests that reload with web::ReloadType::NORMAL is no-op when navigation |
| // manager is empty. |
| TEST_P(WebStateTest, ReloadWithNormalTypeWithEmptyNavigationManager) { |
| NavigationManager* navigation_manager = web_state()->GetNavigationManager(); |
| ASSERT_FALSE(navigation_manager->GetTransientItem()); |
| ASSERT_FALSE(navigation_manager->GetPendingItem()); |
| ASSERT_FALSE(navigation_manager->GetLastCommittedItem()); |
| |
| navigation_manager->Reload(web::ReloadType::NORMAL, |
| false /* check_for_repost */); |
| |
| ASSERT_FALSE(navigation_manager->GetTransientItem()); |
| ASSERT_FALSE(navigation_manager->GetPendingItem()); |
| ASSERT_FALSE(navigation_manager->GetLastCommittedItem()); |
| } |
| |
| // Tests that reload with web::ReloadType::ORIGINAL_REQUEST_URL is no-op when |
| // navigation manager is empty. |
| TEST_P(WebStateTest, ReloadWithOriginalTypeWithEmptyNavigationManager) { |
| NavigationManager* navigation_manager = web_state()->GetNavigationManager(); |
| ASSERT_FALSE(navigation_manager->GetTransientItem()); |
| ASSERT_FALSE(navigation_manager->GetPendingItem()); |
| ASSERT_FALSE(navigation_manager->GetLastCommittedItem()); |
| |
| navigation_manager->Reload(web::ReloadType::ORIGINAL_REQUEST_URL, |
| false /* check_for_repost */); |
| |
| ASSERT_FALSE(navigation_manager->GetTransientItem()); |
| ASSERT_FALSE(navigation_manager->GetPendingItem()); |
| ASSERT_FALSE(navigation_manager->GetLastCommittedItem()); |
| } |
| |
| // Tests that the snapshot method returns an image of a rendered html page. |
| TEST_P(WebStateTest, Snapshot) { |
| ASSERT_TRUE( |
| LoadHtml("<html><div style='background-color:#FF0000; width:50%; " |
| "height:100%;'></div></html>")); |
| __block bool snapshot_complete = false; |
| [[[UIApplication sharedApplication] keyWindow] |
| addSubview:web_state()->GetView()]; |
| // The subview is added but not immediately painted, so a small delay is |
| // necessary. |
| CGRect rect = [web_state()->GetView() bounds]; |
| base::test::ios::SpinRunLoopWithMinDelay(base::TimeDelta::FromSecondsD(0.2)); |
| web_state()->TakeSnapshot( |
| gfx::RectF(rect), base::BindOnce(^(const gfx::Image& snapshot) { |
| ASSERT_FALSE(snapshot.IsEmpty()); |
| EXPECT_GT(snapshot.Width(), 0); |
| EXPECT_GT(snapshot.Height(), 0); |
| int red_pixel_x = (snapshot.Width() / 2) - 10; |
| int white_pixel_x = (snapshot.Width() / 2) + 10; |
| // Test a pixel on the left (red) side. |
| gfx::test::CheckColors( |
| gfx::test::GetPlatformImageColor( |
| gfx::test::ToPlatformType(snapshot), red_pixel_x, 50), |
| SK_ColorRED); |
| // Test a pixel on the right (white) side. |
| gfx::test::CheckColors( |
| gfx::test::GetPlatformImageColor( |
| gfx::test::ToPlatformType(snapshot), white_pixel_x, 50), |
| SK_ColorWHITE); |
| snapshot_complete = true; |
| })); |
| WaitForCondition(^{ |
| return snapshot_complete; |
| }); |
| } |
| |
| // Tests that message sent from main frame triggers the ScriptCommandCallback |
| // with |is_main_frame| = true. |
| TEST_P(WebStateTest, MessageFromMainFrame) { |
| // Add a script command handler. |
| __block bool message_received = false; |
| __block bool message_from_main_frame = false; |
| __block base::Value message_value; |
| const web::WebState::ScriptCommandCallback callback = |
| base::BindRepeating(^(const base::DictionaryValue& value, const GURL&, |
| bool user_interacted, WebFrame* sender_frame) { |
| message_received = true; |
| message_from_main_frame = sender_frame->IsMainFrame(); |
| message_value = value.Clone(); |
| }); |
| auto subscription = web_state()->AddScriptCommandCallback(callback, "test"); |
| |
| ASSERT_TRUE(LoadHtml( |
| "<script>" |
| " __gCrWeb.message.invokeOnHost({'command': 'test.from-main-frame'});" |
| "</script>")); |
| |
| WaitForCondition(^{ |
| return message_received; |
| }); |
| EXPECT_TRUE(message_from_main_frame); |
| EXPECT_TRUE(message_value.is_dict()); |
| EXPECT_EQ(message_value.DictSize(), size_t(1)); |
| base::Value* command = message_value.FindKey("command"); |
| EXPECT_NE(command, nullptr); |
| EXPECT_TRUE(command->is_string()); |
| EXPECT_EQ(command->GetString(), "test.from-main-frame"); |
| } |
| |
| // Tests that message sent from main frame triggers the ScriptCommandCallback |
| // with |is_main_frame| = false. |
| TEST_P(WebStateTest, MessageFromIFrame) { |
| // Add a script command handler. |
| __block bool message_received = false; |
| __block bool message_from_main_frame = false; |
| __block base::Value message_value; |
| const web::WebState::ScriptCommandCallback callback = |
| base::BindRepeating(^(const base::DictionaryValue& value, const GURL&, |
| bool user_interacted, WebFrame* sender_frame) { |
| message_received = true; |
| message_from_main_frame = sender_frame->IsMainFrame(); |
| message_value = value.Clone(); |
| }); |
| auto subscription = web_state()->AddScriptCommandCallback(callback, "test"); |
| |
| ASSERT_TRUE(LoadHtml( |
| "<iframe srcdoc='" |
| "<script>" |
| " __gCrWeb.message.invokeOnHost({\"command\": \"test.from-iframe\"});" |
| "</script>" |
| "'/>")); |
| |
| WaitForCondition(^{ |
| return message_received; |
| }); |
| EXPECT_FALSE(message_from_main_frame); |
| EXPECT_TRUE(message_value.is_dict()); |
| EXPECT_EQ(message_value.DictSize(), size_t(1)); |
| base::Value* command = message_value.FindKey("command"); |
| EXPECT_NE(command, nullptr); |
| EXPECT_TRUE(command->is_string()); |
| EXPECT_EQ(command->GetString(), "test.from-iframe"); |
| } |
| |
| // Tests that the web state has an opener after calling SetHasOpener(). |
| TEST_P(WebStateTest, SetHasOpener) { |
| ASSERT_FALSE(web_state()->HasOpener()); |
| web_state()->SetHasOpener(true); |
| EXPECT_TRUE(web_state()->HasOpener()); |
| } |
| |
| // Verifies that large session can be restored. SlimNavigationManagder has max |
| // session size limit of |wk_navigation_util::kMaxSessionSize|. |
| TEST_P(WebStateTest, RestoreLargeSession) { |
| // Create session storage with large number of items. |
| const int kItemCount = 150; |
| NSMutableArray<CRWNavigationItemStorage*>* item_storages = |
| [NSMutableArray arrayWithCapacity:kItemCount]; |
| for (unsigned int i = 0; i < kItemCount; i++) { |
| CRWNavigationItemStorage* item = [[CRWNavigationItemStorage alloc] init]; |
| item.virtualURL = GURL(base::StringPrintf("http://www.%u.com", i)); |
| item.title = base::ASCIIToUTF16(base::StringPrintf("Test%u", i)); |
| [item_storages addObject:item]; |
| } |
| |
| // Restore the session. |
| WebState::CreateParams params(GetBrowserState()); |
| CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init]; |
| session_storage.itemStorages = item_storages; |
| auto web_state = WebState::CreateWithStorageSession(params, session_storage); |
| web_state->SetKeepRenderProcessAlive(true); |
| WebState* web_state_ptr = web_state.get(); |
| NavigationManager* navigation_manager = web_state->GetNavigationManager(); |
| // TODO(crbug.com/873729): The session will not be restored until |
| // LoadIfNecessary call. Fix the bug and remove extra call. |
| navigation_manager->LoadIfNecessary(); |
| |
| // Verify that session was fully restored. |
| int kExpectedItemCount = web::GetWebClient()->IsSlimNavigationManagerEnabled() |
| ? wk_navigation_util::kMaxSessionSize |
| : kItemCount; |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| bool restored = navigation_manager->GetItemCount() == kExpectedItemCount && |
| navigation_manager->CanGoForward(); |
| EXPECT_EQ(restored, !navigation_manager->IsRestoreSessionInProgress()); |
| if (!restored) { |
| EXPECT_FALSE(navigation_manager->GetLastCommittedItem()); |
| EXPECT_EQ(-1, navigation_manager->GetLastCommittedItemIndex()); |
| EXPECT_TRUE(web_state_ptr->GetLastCommittedURL().is_empty()); |
| EXPECT_FALSE(navigation_manager->CanGoForward()); |
| EXPECT_TRUE(navigation_manager->GetBackwardItems().empty()); |
| EXPECT_TRUE(navigation_manager->GetForwardItems().empty()); |
| EXPECT_EQ("Test0", base::UTF16ToASCII(web_state_ptr->GetTitle())); |
| EXPECT_EQ(0.0, web_state_ptr->GetLoadingProgress()); |
| EXPECT_EQ(-1, navigation_manager->GetPendingItemIndex()); |
| EXPECT_FALSE(navigation_manager->GetPendingItem()); |
| } else { |
| EXPECT_EQ("Test0", base::UTF16ToASCII(web_state_ptr->GetTitle())); |
| NavigationItem* last_committed_item = |
| navigation_manager->GetLastCommittedItem(); |
| // After restoration is complete GetLastCommittedItem() will return null |
| // until fist post-restore navigation is finished. |
| if (last_committed_item) { |
| EXPECT_EQ("http://www.0.com/", last_committed_item->GetURL()); |
| EXPECT_EQ("http://www.0.com/", web_state_ptr->GetLastCommittedURL()); |
| EXPECT_EQ(0, navigation_manager->GetLastCommittedItemIndex()); |
| } else { |
| EXPECT_EQ("", web_state_ptr->GetLastCommittedURL()); |
| EXPECT_EQ(-1, navigation_manager->GetLastCommittedItemIndex()); |
| NavigationItem* pending_item = navigation_manager->GetPendingItem(); |
| EXPECT_TRUE(pending_item); |
| if (pending_item) { |
| EXPECT_EQ("http://www.0.com/", pending_item->GetURL()); |
| } |
| } |
| EXPECT_TRUE(navigation_manager->GetBackwardItems().empty()); |
| EXPECT_EQ(std::max(navigation_manager->GetItemCount() - 1, 0), |
| static_cast<int>(navigation_manager->GetForwardItems().size())); |
| } |
| EXPECT_FALSE(web_state_ptr->IsCrashed()); |
| EXPECT_FALSE(web_state_ptr->IsEvicted()); |
| EXPECT_EQ("http://www.0.com/", web_state_ptr->GetVisibleURL()); |
| NavigationItem* visible_item = navigation_manager->GetVisibleItem(); |
| EXPECT_TRUE(visible_item); |
| EXPECT_TRUE(visible_item && visible_item->GetURL() == "http://www.0.com/"); |
| EXPECT_FALSE(navigation_manager->CanGoBack()); |
| EXPECT_FALSE(navigation_manager->GetTransientItem()); |
| EXPECT_FALSE(IsWKInternalUrl(web_state_ptr->GetVisibleURL())); |
| |
| return restored; |
| })); |
| EXPECT_EQ(kExpectedItemCount, navigation_manager->GetItemCount()); |
| EXPECT_TRUE(navigation_manager->CanGoForward()); |
| |
| histogram_tester_.ExpectTotalCount(kRestoreNavigationItemCount, 1); |
| histogram_tester_.ExpectBucketCount(kRestoreNavigationItemCount, 100, 1); |
| if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) { |
| histogram_tester_.ExpectTotalCount(kRestoreNavigationTime, 1); |
| } |
| |
| // Now wait until the last committed item is fully loaded. |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| EXPECT_FALSE(IsWKInternalUrl(web_state_ptr->GetVisibleURL())); |
| |
| return !navigation_manager->GetPendingItem() && |
| !web_state_ptr->IsLoading() && |
| web_state_ptr->GetLoadingProgress() == 1.0; |
| })); |
| } |
| |
| // Verifies that calling WebState::Stop() does not stop the session restoration. |
| // Session restoration should be opaque to the user and embedder, so calling |
| // Stop() is no-op. |
| TEST_P(WebStateTest, CallStopDuringSessionRestore) { |
| // Create session storage with large number of items. |
| const int kItemCount = 10; |
| NSMutableArray<CRWNavigationItemStorage*>* item_storages = |
| [NSMutableArray arrayWithCapacity:kItemCount]; |
| for (unsigned int i = 0; i < kItemCount; i++) { |
| CRWNavigationItemStorage* item = [[CRWNavigationItemStorage alloc] init]; |
| item.virtualURL = GURL(base::StringPrintf("http://www.%u.com", i)); |
| [item_storages addObject:item]; |
| } |
| |
| // Restore the session. |
| WebState::CreateParams params(GetBrowserState()); |
| CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init]; |
| session_storage.itemStorages = item_storages; |
| auto web_state = WebState::CreateWithStorageSession(params, session_storage); |
| web_state->SetKeepRenderProcessAlive(true); |
| WebState* web_state_ptr = web_state.get(); |
| NavigationManager* navigation_manager = web_state->GetNavigationManager(); |
| // TODO(crbug.com/873729): The session will not be restored until |
| // LoadIfNecessary call. Fix the bug and remove extra call. |
| navigation_manager->LoadIfNecessary(); |
| |
| // Verify that session was fully restored. |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| bool restored = navigation_manager->GetItemCount() == kItemCount && |
| navigation_manager->CanGoForward(); |
| if (!restored) { |
| web_state_ptr->Stop(); // Attempt to interrupt the session restoration. |
| } |
| return restored; |
| })); |
| EXPECT_EQ(kItemCount, navigation_manager->GetItemCount()); |
| EXPECT_TRUE(navigation_manager->CanGoForward()); |
| |
| // Now wait until the last committed item is fully loaded. |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return !navigation_manager->GetPendingItem() && !web_state_ptr->IsLoading(); |
| })); |
| } |
| |
| // Verifies that calling NavigationManager::LoadURLWithParams() does not stop |
| // the session restoration and eventually loads the requested URL. |
| TEST_P(WebStateTest, CallLoadURLWithParamsDuringSessionRestore) { |
| // Create session storage with large number of items. |
| const int kItemCount = 10; |
| NSMutableArray<CRWNavigationItemStorage*>* item_storages = |
| [NSMutableArray arrayWithCapacity:kItemCount]; |
| for (unsigned int i = 0; i < kItemCount; i++) { |
| CRWNavigationItemStorage* item = [[CRWNavigationItemStorage alloc] init]; |
| item.virtualURL = GURL(base::StringPrintf("http://www.%u.test", i)); |
| item.userAgentType = UserAgentType::MOBILE; |
| [item_storages addObject:item]; |
| } |
| |
| // Restore the session. |
| WebState::CreateParams params(GetBrowserState()); |
| CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init]; |
| session_storage.itemStorages = item_storages; |
| auto web_state = WebState::CreateWithStorageSession(params, session_storage); |
| web_state->SetKeepRenderProcessAlive(true); |
| WebState* web_state_ptr = web_state.get(); |
| NavigationManager* navigation_manager = web_state->GetNavigationManager(); |
| // TODO(crbug.com/873729): The session will not be restored until |
| // LoadIfNecessary call. Fix the bug and remove extra call. |
| navigation_manager->LoadIfNecessary(); |
| |
| // Attempt to interrupt the session restoration. |
| GURL url("http://foo.test/"); |
| NavigationManager::WebLoadParams load_params(url); |
| navigation_manager->LoadURLWithParams(load_params); |
| |
| // Verify that session was fully restored. |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| bool restored = navigation_manager->GetItemCount() == kItemCount && |
| navigation_manager->CanGoForward(); |
| if (!restored) { |
| // Attempt to interrupt the session restoration multiple times, which is |
| // something that the user can do on the slow network. |
| navigation_manager->LoadURLWithParams(load_params); |
| } |
| return restored; |
| })); |
| EXPECT_EQ(kItemCount, navigation_manager->GetItemCount()); |
| EXPECT_TRUE(navigation_manager->CanGoForward()); |
| |
| // Now wait until the last committed item is fully loaded. |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return web_state_ptr->GetLastCommittedURL() == url; |
| })); |
| } |
| |
| // Verifies that calling NavigationManager::Reload() does not stop the session |
| // restoration. Session restoration should be opaque to the user and embedder, |
| // so calling Reload() is no-op. |
| TEST_P(WebStateTest, CallReloadDuringSessionRestore) { |
| // Create session storage with large number of items. |
| const int kItemCount = 10; |
| NSMutableArray<CRWNavigationItemStorage*>* item_storages = |
| [NSMutableArray arrayWithCapacity:kItemCount]; |
| for (unsigned int i = 0; i < kItemCount; i++) { |
| CRWNavigationItemStorage* item = [[CRWNavigationItemStorage alloc] init]; |
| item.virtualURL = GURL(base::StringPrintf("http://www.%u.com", i)); |
| [item_storages addObject:item]; |
| } |
| |
| // Restore the session. |
| WebState::CreateParams params(GetBrowserState()); |
| CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init]; |
| session_storage.itemStorages = item_storages; |
| auto web_state = WebState::CreateWithStorageSession(params, session_storage); |
| web_state->SetKeepRenderProcessAlive(true); |
| WebState* web_state_ptr = web_state.get(); |
| NavigationManager* navigation_manager = web_state->GetNavigationManager(); |
| // TODO(crbug.com/873729): The session will not be restored until |
| // LoadIfNecessary call. Fix the bug and remove extra call. |
| navigation_manager->LoadIfNecessary(); |
| |
| // Verify that session was fully restored. |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| bool restored = navigation_manager->GetItemCount() == kItemCount && |
| navigation_manager->CanGoForward(); |
| if (!restored) { |
| // Attempt to interrupt the session restoration. |
| navigation_manager->Reload(web::ReloadType::NORMAL, |
| /*check_for_repost=*/false); |
| } |
| return restored; |
| })); |
| EXPECT_EQ(kItemCount, navigation_manager->GetItemCount()); |
| EXPECT_TRUE(navigation_manager->CanGoForward()); |
| |
| // Now wait until the last committed item is fully loaded. |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return !navigation_manager->GetPendingItem() && !web_state_ptr->IsLoading(); |
| })); |
| } |
| |
| // Tests that if a saved session is provided when creating a new WebState, it is |
| // restored after the first NavigationManager::LoadIfNecessary() call. |
| TEST_P(WebStateTest, RestoredFromHistory) { |
| auto web_state = WebState::CreateWithStorageSession( |
| WebState::CreateParams(GetBrowserState()), GetTestSessionStorage()); |
| |
| ASSERT_FALSE(test::IsWebViewContainingText(web_state.get(), |
| kTestSessionStoragePageText)); |
| web_state->GetNavigationManager()->LoadIfNecessary(); |
| EXPECT_TRUE(test::WaitForWebViewContainingText(web_state.get(), |
| kTestSessionStoragePageText)); |
| } |
| |
| // Verifies that each page title is restored. |
| TEST_P(WebStateTest, RestorePageTitles) { |
| // Create session storage. |
| const int kItemCount = 3; |
| NSMutableArray<CRWNavigationItemStorage*>* item_storages = |
| [NSMutableArray arrayWithCapacity:kItemCount]; |
| for (unsigned int i = 0; i < kItemCount; i++) { |
| CRWNavigationItemStorage* item = [[CRWNavigationItemStorage alloc] init]; |
| item.virtualURL = GURL(base::StringPrintf("http://www.%u.com", i)); |
| item.title = base::ASCIIToUTF16(base::StringPrintf("Test%u", i)); |
| [item_storages addObject:item]; |
| } |
| |
| // Restore the session. |
| WebState::CreateParams params(GetBrowserState()); |
| CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init]; |
| session_storage.itemStorages = item_storages; |
| auto web_state = WebState::CreateWithStorageSession(params, session_storage); |
| NavigationManager* navigation_manager = web_state->GetNavigationManager(); |
| // TODO(crbug.com/873729): The session will not be restored until |
| // LoadIfNecessary call. Fix the bug and remove extra call. |
| navigation_manager->LoadIfNecessary(); |
| |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return navigation_manager->GetItemCount() == kItemCount; |
| })); |
| |
| for (unsigned int i = 0; i < kItemCount; i++) { |
| NavigationItem* item = navigation_manager->GetItemAtIndex(i); |
| EXPECT_EQ(GURL(base::StringPrintf("http://www.%u.com", i)), |
| item->GetVirtualURL()); |
| EXPECT_EQ(base::ASCIIToUTF16(base::StringPrintf("Test%u", i)), |
| item->GetTitle()); |
| } |
| } |
| |
| // Tests that NavigationManager::LoadIfNecessary() restores the page after |
| // disabling and re-enabling web usage. |
| TEST_P(WebStateTest, DisableAndReenableWebUsage) { |
| auto web_state = WebState::CreateWithStorageSession( |
| WebState::CreateParams(GetBrowserState()), GetTestSessionStorage()); |
| web_state->GetNavigationManager()->LoadIfNecessary(); |
| ASSERT_TRUE(test::WaitForWebViewContainingText(web_state.get(), |
| kTestSessionStoragePageText)); |
| |
| web_state->SetWebUsageEnabled(false); |
| web_state->SetWebUsageEnabled(true); |
| |
| // NavigationManager::LoadIfNecessary() should restore the page. |
| ASSERT_FALSE(test::IsWebViewContainingText(web_state.get(), |
| kTestSessionStoragePageText)); |
| web_state->GetNavigationManager()->LoadIfNecessary(); |
| EXPECT_TRUE(test::WaitForWebViewContainingText(web_state.get(), |
| kTestSessionStoragePageText)); |
| } |
| |
| // Tests that loading an HTML page after a failed navigation works. |
| TEST_P(WebStateTest, LoadChromeThenHTML) { |
| GURL app_specific_url( |
| base::StringPrintf("%s://app_specific_url", kTestAppSpecificScheme)); |
| web::NavigationManager::WebLoadParams load_params(app_specific_url); |
| web_state()->GetNavigationManager()->LoadURLWithParams(load_params); |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return !web_state()->IsLoading(); |
| })); |
| // Wait for the error loading. |
| EXPECT_TRUE(test::WaitForWebViewContainingText(web_state(), |
| kUnsupportedUrlErrorPage)); |
| NSString* data_html = @(kTestPageHTML); |
| web_state()->LoadData([data_html dataUsingEncoding:NSUTF8StringEncoding], |
| @"text/html", GURL("https://www.chromium.org")); |
| EXPECT_TRUE( |
| test::WaitForWebViewContainingText(web_state(), kTextInTestPageHTML)); |
| } |
| |
| // Tests that reloading after loading HTML page will load the online page. |
| TEST_P(WebStateTest, LoadChromeThenWaitThenHTMLThenReload) { |
| net::EmbeddedTestServer server; |
| net::test_server::RegisterDefaultHandlers(&server); |
| ASSERT_TRUE(server.Start()); |
| GURL echo_url = server.GetURL("/echo"); |
| |
| GURL app_specific_url( |
| base::StringPrintf("%s://app_specific_url", kTestAppSpecificScheme)); |
| web::NavigationManager::WebLoadParams load_params(app_specific_url); |
| web_state()->GetNavigationManager()->LoadURLWithParams(load_params); |
| // Wait for the error loading. |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return !web_state()->IsLoading(); |
| })); |
| EXPECT_TRUE(test::WaitForWebViewContainingText(web_state(), |
| kUnsupportedUrlErrorPage)); |
| NSString* data_html = @(kTestPageHTML); |
| web_state()->LoadData([data_html dataUsingEncoding:NSUTF8StringEncoding], |
| @"text/html", echo_url); |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return !web_state()->IsLoading(); |
| })); |
| EXPECT_TRUE( |
| test::WaitForWebViewContainingText(web_state(), kTextInTestPageHTML)); |
| |
| web_state()->GetNavigationManager()->Reload(web::ReloadType::NORMAL, true); |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return !web_state()->IsLoading(); |
| })); |
| |
| EXPECT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo")); |
| web_state()->GetNavigationManager()->Reload(web::ReloadType::NORMAL, true); |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return !web_state()->IsLoading(); |
| })); |
| EXPECT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo")); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| ProgrammaticWebStateTest, |
| WebStateTest, |
| ::testing::Values( |
| NavigationManagerChoice::TEST_LEGACY_NAVIGATION_MANAGER, |
| NavigationManagerChoice::TEST_WK_BASED_NAVIGATION_MANAGER)); |
| |
| } // namespace web |