Commit 3bafc4d9 authored by Olivier Robin's avatar Olivier Robin Committed by Commit Bot

Add loadData in webState

This method allows to replace the content of the page with
given data and MIME type.
The method does not trigger a navigation.

The method also takes a URL that will replace the current
URL in the navigation stack.

Bug: 929492
Change-Id: If1295915718d8dd9e582d2e88035eb63ce7341ea
Reviewed-on: https://chromium-review.googlesource.com/c/1436359
Auto-Submit: Olivier Robin <olivierrobin@chromium.org>
Commit-Queue: Eugene But <eugenebut@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Reviewed-by: default avatarDanyao Wang <danyao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#632019}
parent e1bcb0cc
......@@ -176,6 +176,10 @@ class NavigationManagerImpl : public NavigationManager {
// out of CRWWebController.
NavigationItemImpl* GetCurrentItemImpl() const;
// Returns the last committed NavigationItem, which may be null if there
// are no committed entries or session restoration is in-progress.
NavigationItemImpl* GetLastCommittedItemImpl() const;
// Updates the pending or last committed navigation item after replaceState.
// TODO(crbug.com/783382): This is a legacy method to maintain backward
// compatibility for PageLoad stat. Remove this method once PageLoad no longer
......
......@@ -177,6 +177,25 @@ NavigationItemImpl* NavigationManagerImpl::GetCurrentItemImpl() const {
return GetLastCommittedItemInCurrentOrRestoredSession();
}
NavigationItemImpl* NavigationManagerImpl::GetLastCommittedItemImpl() const {
// GetLastCommittedItemImpl() should return null while session restoration is
// in progress and real item after the first post-restore navigation is
// finished. IsRestoreSessionInProgress(), will return true until the first
// post-restore is started.
if (IsRestoreSessionInProgress())
return nullptr;
NavigationItemImpl* result = GetLastCommittedItemInCurrentOrRestoredSession();
if (!result || wk_navigation_util::IsRestoreSessionUrl(result->GetURL())) {
// Session restoration has completed, but the first post-restore navigation
// has not finished yet, so there is no committed URLs in the navigation
// stack.
return nullptr;
}
return result;
}
void NavigationManagerImpl::UpdateCurrentItemForReplaceState(
const GURL& url,
NSString* state_object) {
......@@ -224,22 +243,7 @@ void NavigationManagerImpl::GoToIndex(int index) {
}
NavigationItem* NavigationManagerImpl::GetLastCommittedItem() const {
// GetLastCommittedItem() should return null while session restoration is in
// progress and real item after the first post-restore navigation is
// finished. IsRestoreSessionInProgress(), will return true until the first
// post-restore is started.
if (IsRestoreSessionInProgress())
return nullptr;
NavigationItem* result = GetLastCommittedItemInCurrentOrRestoredSession();
if (!result || wk_navigation_util::IsRestoreSessionUrl(result->GetURL())) {
// Session restoration has completed, but the first post-restore navigation
// has not finished yet, so there is no committed URLs in the navigation
// stack.
return nullptr;
}
return result;
return GetLastCommittedItemImpl();
}
int NavigationManagerImpl::GetLastCommittedItemIndex() const {
......
......@@ -38,6 +38,7 @@ class TestWebState : public WebState {
UIView* GetView() override;
void WasShown() override;
void WasHidden() override;
void LoadData(NSData* data, NSString* mime_type, const GURL& url) override;
BrowserState* GetBrowserState() const override;
void OpenURL(const OpenURLParams& params) override {}
void Stop() override {}
......
......@@ -378,4 +378,8 @@ void TestWebState::TakeSnapshot(const gfx::RectF& rect,
std::move(callback).Run(gfx::Image([[UIImage alloc] init]));
}
void TestWebState::LoadData(NSData* data,
NSString* mime_type,
const GURL& url) {}
} // namespace web
......@@ -152,6 +152,10 @@ class WebState : public base::SupportsUserData {
// Gets the CRWJSInjectionReceiver associated with this WebState.
virtual CRWJSInjectionReceiver* GetJSInjectionReceiver() const = 0;
// Loads |data| of type |mime_type| and replaces last committed URL with the
// given |url|.
virtual void LoadData(NSData* data, NSString* mime_type, const GURL& url) = 0;
// DISCOURAGED. Prefer using |WebFrame CallJavaScriptFunction| instead because
// it restricts JavaScript execution to functions within __gCrWeb and can also
// call those functions on any frame in the page. ExecuteJavaScript here can
......
......@@ -164,6 +164,12 @@ class WebStateImpl;
// NavigationManager::LoadIfNecessary() instead.
- (void)loadCurrentURLIfNecessary;
// Loads |data| of type |MIMEType| and replaces last committed URL with the
// given |URL|.
- (void)loadData:(NSData*)data
MIMEType:(NSString*)MIMEType
forURL:(const GURL&)URL;
// Loads HTML in the page and presents it as if it was originating from an
// application specific URL. |HTML| must not be empty.
- (void)loadHTML:(NSString*)HTML forAppSpecificURL:(const GURL&)URL;
......
......@@ -20,6 +20,7 @@
#include "base/callback.h"
#include "base/containers/mru_cache.h"
#include "base/feature_list.h"
#include "base/i18n/i18n_constants.h"
#import "base/ios/block_types.h"
#include "base/ios/ios_util.h"
#import "base/ios/ns_error_util.h"
......@@ -1524,6 +1525,47 @@ GURL URLEscapedForHistory(const GURL& url) {
}
}
- (void)loadData:(NSData*)data
MIMEType:(NSString*)MIMEType
forURL:(const GURL&)URL {
web::NavigationItemImpl* item =
self.navigationManagerImpl->GetLastCommittedItemImpl();
auto navigationContext = web::NavigationContextImpl::CreateNavigationContext(
_webStateImpl, URL,
/*has_user_gesture=*/true, item->GetTransitionType(),
/*is_renderer_initiated=*/false);
_loadPhase = web::LOAD_REQUESTED;
navigationContext->SetNavigationItemUniqueID(item->GetUniqueID());
item->SetNavigationInitiationType(
web::NavigationInitiationType::BROWSER_INITIATED);
// The error_retry_state_machine may still be in the
// |kDisplayingWebErrorForFailedNavigation| from the navigation that is being
// replaced. As the navigation is now successful, the error can be cleared.
item->error_retry_state_machine().SetNoNavigationError();
// The load data call will replace the current navigation and the webView URL
// of the navigation will be replaced by |URL|. Set the URL of the
// navigationItem to keep them synced.
// Note: it is possible that the URL in item already match |url|. But item can
// also contain a placeholder URL intended to be replaced.
item->SetURL(URL);
if (item->GetUserAgentType() == web::UserAgentType::NONE &&
web::wk_navigation_util::URLNeedsUserAgentType(URL)) {
item->SetUserAgentType(web::UserAgentType::MOBILE);
}
WKNavigation* navigation =
[_webView loadData:data
MIMEType:MIMEType
characterEncodingName:base::SysUTF8ToNSString(base::kCodepageUTF8)
baseURL:net::NSURLWithGURL(URL)];
[_navigationStates setContext:std::move(navigationContext)
forNavigation:navigation];
[_navigationStates setState:web::WKNavigationState::REQUESTED
forNavigation:navigation];
}
// TODO(crbug.com/788465): Verify that the history state management here are not
// needed for WKBasedNavigationManagerImpl and delete this method. The
// OnNavigationItemCommitted() call is likely the only thing that needs to be
......
......@@ -198,6 +198,7 @@ class WebStateImpl : public WebState, public NavigationManagerDelegate {
SessionCertificatePolicyCache* GetSessionCertificatePolicyCache() override;
CRWSessionStorage* BuildSessionStorage() override;
CRWJSInjectionReceiver* GetJSInjectionReceiver() const override;
void LoadData(NSData* data, NSString* mime_type, const GURL& url) override;
void ExecuteJavaScript(const base::string16& javascript) override;
void ExecuteJavaScript(const base::string16& javascript,
JavaScriptResultCallback callback) override;
......
......@@ -631,6 +631,12 @@ CRWSessionStorage* WebStateImpl::BuildSessionStorage() {
return session_storage_builder.BuildStorage(this);
}
void WebStateImpl::LoadData(NSData* data,
NSString* mime_type,
const GURL& url) {
[web_controller_ loadData:data MIMEType:mime_type forURL:url];
}
CRWJSInjectionReceiver* WebStateImpl::GetJSInjectionReceiver() const {
return [web_controller_ jsInjectionReceiver];
}
......
......@@ -118,6 +118,39 @@ ACTION_P5(VerifyPageStartedContext,
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) for data navigation
// passed to |DidStartNavigation|. Stores |NavigationContext| in |context|
// pointer.
ACTION_P5(VerifyDataStartedContext,
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_FALSE(web_state->IsLoading());
NavigationManager* navigation_manager = web_state->GetNavigationManager();
NavigationItem* item = navigation_manager->GetLastCommittedItem();
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) for navigation for
// stopped load. Stores |NavigationContext| in |context| pointer.
ACTION_P5(VerifyAbortedNavigationStartedContext,
......@@ -218,6 +251,39 @@ ACTION_P4(VerifyPdfFileUrlFinishedContext, web_state, url, context, nav_id) {
EXPECT_EQ(url, item->GetURL());
}
// Verifies correctness of |NavigationContext| (|arg1|) for data navigation
// passed to |DidFinishNavigation|. Asserts that |NavigationContext| the same as
// |context|.
ACTION_P5(VerifyDataFinishedContext,
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_FALSE((*context)->HasCommitted());
EXPECT_FALSE((*context)->IsDownload());
EXPECT_FALSE((*context)->IsPost());
EXPECT_FALSE((*context)->GetError());
EXPECT_FALSE((*context)->IsRendererInitiated());
ASSERT_FALSE(web_state->ContentIsHTML());
ASSERT_FALSE(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 failed navigation
// passed to |DidFinishNavigation|. Asserts that |NavigationContext| the same as
// |context|.
......@@ -2451,6 +2517,58 @@ TEST_P(WebStateObserverTest, PdfFileUrlNavigation) {
ASSERT_TRUE(LoadWithParams(params));
}
// Tests loading Data in place of a normal URL.
TEST_P(WebStateObserverTest, LoadData) {
// Perform first navigation.
const GURL first_url = test_server_->GetURL("/echoall");
EXPECT_CALL(observer_, DidStartLoading(web_state()));
WebStatePolicyDecider::RequestInfo expected_request_info(
ui::PageTransition::PAGE_TRANSITION_TYPED,
/*target_main_frame=*/true, /*has_user_gesture=*/false);
EXPECT_CALL(*decider_,
ShouldAllowRequest(_, RequestInfoMatch(expected_request_info)))
.WillOnce(Return(true));
NavigationContext* context = nullptr;
int32_t nav_id = 0;
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyPageStartedContext(
web_state(), first_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(), first_url, kExpectedMimeType, /*content_is_html=*/true,
&context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()))
.WillOnce(VerifyTitle(first_url.GetContent()));
EXPECT_CALL(observer_, TitleWasSet(web_state()))
.WillOnce(VerifyTitle("EmbeddedTestServer - EchoAll"));
EXPECT_CALL(observer_, DidStopLoading(web_state()));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
ASSERT_TRUE(LoadUrl(first_url));
NSString* html = @"<html><body>foo</body></html>";
GURL data_url("https://www.chromium.test");
EXPECT_CALL(*decider_, ShouldAllowRequest(_, _)).WillOnce(Return(true));
// ShouldAllowResponse is not called on loadData navigation.
EXPECT_CALL(observer_, DidStartNavigation(web_state(), _))
.WillOnce(VerifyDataStartedContext(
web_state(), data_url, ui::PageTransition::PAGE_TRANSITION_TYPED,
&context, &nav_id));
EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
.WillOnce(VerifyDataFinishedContext(web_state(), data_url, "text/html",
&context, &nav_id));
EXPECT_CALL(observer_, TitleWasSet(web_state()))
.WillOnce(VerifyTitle("https://www.chromium.test"));
EXPECT_CALL(observer_,
PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
web_state()->LoadData([html dataUsingEncoding:NSUTF8StringEncoding],
@"text/html", data_url);
EXPECT_TRUE(test::WaitForWebViewContainingText(web_state(), "foo"));
}
INSTANTIATE_TEST_SUITE_P(
ProgrammaticWebStateObserverTest,
WebStateObserverTest,
......
......@@ -21,10 +21,15 @@
#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_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"
......@@ -44,6 +49,12 @@ namespace {
// 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;
......@@ -625,6 +636,64 @@ TEST_P(WebStateTest, DisableAndReenableWebUsage) {
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(), "unsupported URL"));
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(), "unsupported URL"));
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,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment