blob: f779d635563d521c29463ac34bb97481bfcb2e3f [file] [log] [blame]
// Copyright 2016 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/test/web_test_with_web_state.h"
#include "base/message_loop/message_loop_current.h"
#include "base/run_loop.h"
#include "base/scoped_observer.h"
#include "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "ios/testing/wait_util.h"
#import "ios/web/navigation/navigation_manager_impl.h"
#import "ios/web/public/web_client.h"
#include "ios/web/public/web_state/url_verification_constants.h"
#include "ios/web/public/web_state/web_state_observer.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/web_state_impl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using testing::WaitUntilConditionOrTimeout;
using testing::kWaitForJSCompletionTimeout;
using testing::kWaitForPageLoadTimeout;
namespace {
// Returns CRWWebController for the given |web_state|.
CRWWebController* GetWebController(web::WebState* web_state) {
web::WebStateImpl* web_state_impl =
static_cast<web::WebStateImpl*>(web_state);
return web_state_impl->GetWebController();
}
} // namespace
namespace web {
WebTestWithWebState::WebTestWithWebState() {}
WebTestWithWebState::WebTestWithWebState(
std::unique_ptr<web::WebClient> web_client)
: WebTest(std::move(web_client)) {}
WebTestWithWebState::~WebTestWithWebState() {}
void WebTestWithWebState::SetUp() {
WebTest::SetUp();
web::WebState::CreateParams params(GetBrowserState());
web_state_ = web::WebState::Create(params);
// Force generation of child views; necessary for some tests.
web_state_->GetView();
}
void WebTestWithWebState::TearDown() {
DestroyWebState();
WebTest::TearDown();
}
void WebTestWithWebState::AddPendingItem(const GURL& url,
ui::PageTransition transition) {
GetWebController(web_state())
.webStateImpl->GetNavigationManagerImpl()
.AddPendingItem(url, Referrer(), transition,
web::NavigationInitiationType::BROWSER_INITIATED,
web::NavigationManager::UserAgentOverrideOption::INHERIT);
}
void WebTestWithWebState::AddTransientItem(const GURL& url) {
GetWebController(web_state())
.webStateImpl->GetNavigationManagerImpl()
.AddTransientItem(url);
}
void WebTestWithWebState::LoadHtml(NSString* html, const GURL& url) {
// Sets MIME type to "text/html" once navigation is committed.
class MimeTypeUpdater : public WebStateObserver {
public:
MimeTypeUpdater() = default;
// WebStateObserver overrides:
void NavigationItemCommitted(WebState* web_state,
const LoadCommittedDetails&) override {
// loadHTML:forURL: does not notify web view delegate about received
// response, so web controller does not get a chance to properly update
// MIME type and it should be set manually after navigation is committed
// but before WebState signal load completion and clients will start
// checking if MIME type is in fact HTML.
static_cast<WebStateImpl*>(web_state)->SetContentsMimeType("text/html");
}
void WebStateDestroyed(WebState* web_state) override { NOTREACHED(); }
private:
DISALLOW_COPY_AND_ASSIGN(MimeTypeUpdater);
};
MimeTypeUpdater mime_type_updater;
ScopedObserver<WebState, WebStateObserver> scoped_observer(
&mime_type_updater);
scoped_observer.Add(web_state());
// Initiate asynchronous HTML load.
CRWWebController* web_controller = GetWebController(web_state());
ASSERT_EQ(PAGE_LOADED, web_controller.loadPhase);
// If the underlying WKWebView is empty, first load a placeholder about:blank
// to create a WKBackForwardListItem to store the NavigationItem associated
// with the |-loadHTML|.
// TODO(crbug.com/777884): consider changing |-loadHTML| to match WKWebView's
// |-loadHTMLString:baseURL| that doesn't create a navigation entry.
if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
!web_state()->GetNavigationManager()->GetItemCount()) {
GURL url(url::kAboutBlankURL);
NavigationManager::WebLoadParams params(url);
web_state()->GetNavigationManager()->LoadURLWithParams(params);
base::test::ios::WaitUntilCondition(^{
return web_controller.loadPhase == PAGE_LOADED;
});
}
[web_controller loadHTML:html forURL:url];
ASSERT_EQ(LOAD_REQUESTED, web_controller.loadPhase);
// Wait until the page is loaded.
base::test::ios::WaitUntilCondition(^{
return web_controller.loadPhase == PAGE_LOADED;
});
// Wait until the script execution is possible. Script execution will fail if
// WKUserScript was not jet injected by WKWebView.
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
return [ExecuteJavaScript(@"0;") isEqual:@0];
}));
}
void WebTestWithWebState::LoadHtml(NSString* html) {
GURL url("https://chromium.test/");
LoadHtml(html, url);
}
bool WebTestWithWebState::LoadHtml(const std::string& html) {
LoadHtml(base::SysUTF8ToNSString(html));
// TODO(crbug.com/780062): LoadHtml(NSString*) should return bool.
return true;
}
void WebTestWithWebState::WaitForBackgroundTasks() {
// Because tasks can add new tasks to either queue, the loop continues until
// the first pass where no activity is seen from either queue.
bool activitySeen = false;
base::MessageLoopCurrent messageLoop = base::MessageLoopCurrent::Get();
messageLoop->AddTaskObserver(this);
do {
activitySeen = false;
// Yield to the iOS message queue, e.g. [NSObject performSelector:] events.
if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) ==
kCFRunLoopRunHandledSource)
activitySeen = true;
// Yield to the Chromium message queue, e.g. WebThread::PostTask()
// events.
processed_a_task_ = false;
base::RunLoop().RunUntilIdle();
if (processed_a_task_) // Set in TaskObserver method.
activitySeen = true;
} while (activitySeen);
messageLoop->RemoveTaskObserver(this);
}
void WebTestWithWebState::WaitForCondition(ConditionBlock condition) {
base::test::ios::WaitUntilCondition(condition, true,
base::TimeDelta::FromSeconds(1000));
}
id WebTestWithWebState::ExecuteJavaScript(NSString* script) {
__block id execution_result = nil;
__block bool execution_completed = false;
SCOPED_TRACE(base::SysNSStringToUTF8(script));
[GetWebController(web_state())
executeJavaScript:script
completionHandler:^(id result, NSError* error) {
// Most of executed JS does not return the result, and there is no need
// to log WKErrorJavaScriptResultTypeIsUnsupported error code.
if (error && error.code != WKErrorJavaScriptResultTypeIsUnsupported) {
DLOG(WARNING) << base::SysNSStringToUTF8(error.localizedDescription);
}
execution_result = [result copy];
execution_completed = true;
}];
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
return execution_completed;
}));
return execution_result;
}
void WebTestWithWebState::DestroyWebState() {
web_state_.reset();
}
std::string WebTestWithWebState::BaseUrl() const {
web::URLVerificationTrustLevel unused_level;
return web_state()->GetCurrentURL(&unused_level).spec();
}
web::WebState* WebTestWithWebState::web_state() {
return web_state_.get();
}
const web::WebState* WebTestWithWebState::web_state() const {
return web_state_.get();
}
void WebTestWithWebState::WillProcessTask(const base::PendingTask&) {
// Nothing to do.
}
void WebTestWithWebState::DidProcessTask(const base::PendingTask&) {
processed_a_task_ = true;
}
} // namespace web