Commit 6783f497 authored by Gauthier Ambard's avatar Gauthier Ambard Committed by Commit Bot

[iOS] Update WebContent correctly

This CL makes sure that the WebContent is taken into account when
requesting a page. This allows the WKWebView to know how to react to the
JavaScript event. In particular, this changes the navigator.platform
property. It adds the corresponding tests.

It also adds a new utility to find a string through all the frames of a
WebState.

Bug: 1031124
Change-Id: I2a794ca2bd8c7878d0676a2a9ec5d4269d68cb18
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1951010
Commit-Queue: Gauthier Ambard <gambard@chromium.org>
Reviewed-by: default avatarAli Juma <ajuma@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#736335}
parent 3bcb0c11
......@@ -29,6 +29,7 @@ const char kUserAgentTestURL[] =
const char kMobileSiteLabel[] = "Mobile";
const char kDesktopSiteLabel[] = "Desktop";
const char kDesktopPlatformLabel[] = "MacIntel";
// Custom timeout used when waiting for a web state after requesting desktop
// or mobile mode.
......@@ -246,38 +247,34 @@ class UserAgentResponseProvider : public web::DataResponseProvider {
}
// Tests that navigator.appVersion JavaScript API returns correct string for
// desktop User Agent.
- (void)testAppVersionJSAPIWithDesktopUserAgent {
web::test::SetUpFileBasedHttpServer();
[ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kUserAgentTestURL)];
// Verify initial reception of the mobile site.
[ChromeEarlGrey waitForWebStateContainingText:kMobileSiteLabel];
// Request and verify reception of the desktop site.
[ChromeEarlGreyUI openToolsMenu];
[RequestDesktopButton() performAction:grey_tap()];
[ChromeEarlGrey waitForWebStateContainingText:kDesktopSiteLabel];
}
// Tests that navigator.appVersion JavaScript API returns correct string for
// mobile User Agent.
// mobile User Agent and the platform.
- (void)testAppVersionJSAPIWithMobileUserAgent {
web::test::SetUpFileBasedHttpServer();
[ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kUserAgentTestURL)];
// Verify initial reception of the mobile site.
[ChromeEarlGrey waitForWebStateContainingText:kMobileSiteLabel];
std::string platform =
base::SysNSStringToUTF8([[UIDevice currentDevice] model]);
[ChromeEarlGrey waitForWebStateContainingText:platform];
// Request and verify reception of the desktop site.
[ChromeEarlGreyUI openToolsMenu];
[RequestDesktopButton() performAction:grey_tap()];
[ChromeEarlGrey waitForWebStateContainingText:kDesktopSiteLabel
timeout:kWaitForUserAgentChangeTimeout];
if (@available(iOS 13, *)) {
[ChromeEarlGrey waitForWebStateContainingText:kDesktopPlatformLabel];
} else {
[ChromeEarlGrey waitForWebStateContainingText:platform];
}
// Request and verify reception of the mobile site.
[ChromeEarlGreyUI openToolsMenu];
[RequestMobileButton() performAction:grey_tap()];
[ChromeEarlGrey waitForWebStateContainingText:kMobileSiteLabel
timeout:kWaitForUserAgentChangeTimeout];
[ChromeEarlGrey waitForWebStateContainingText:platform];
}
@end
......@@ -353,6 +353,7 @@ source_set("eg_tests") {
"browsing_prevent_default_egtest.mm",
"cache_egtest.mm",
"child_window_open_by_dom_egtest.mm",
"content_mode_egtest.mm",
"error_page_egtest.mm",
"forms_egtest.mm",
"http_auth_egtest.mm",
......@@ -455,6 +456,7 @@ source_set("eg2_tests") {
"browsing_prevent_default_egtest.mm",
"cache_egtest.mm",
"child_window_open_by_dom_egtest.mm",
"content_mode_egtest.mm",
"error_page_egtest.mm",
"forms_egtest.mm",
"http_auth_egtest.mm",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/bind.h"
#include "base/strings/sys_string_conversions.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#include "ios/testing/embedded_test_server_handlers.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
const char kPage1[] = "/page1.html";
const char kPageLinkID[] = "IDForLinkInPlatformPage";
const char kLinkPage[] = "/link.html";
const char kLinkLoaded[] = "Link Page Loaded";
const char kLinkPageLinkID[] = "linkPageIDForLink";
const char kWindowOpenJSPage[] = "/window_open.html";
const char kIFramePage[] = "/iframe.html";
// Handler for the test server. Depending of the URL of the page, it can returns
// a page displaying the platform, a page with a link to the platform page, an
// iframe, or a page opening a new page with JavaScript window.open.
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
if (request.GetURL().path() == kPage1) {
auto result = std::make_unique<net::test_server::BasicHttpResponse>();
result->set_content_type("text/html");
std::string href = std::string(kLinkPage).substr(1);
result->set_content(
"<html><body><div><a id=\"" + std::string(kPageLinkID) + "\" href=\"" +
href +
"\">page with link</a></div><p id=\"platformResult\"/><script "
"type=\"text/javascript\">"
"document.getElementById(\"platformResult\").innerHTML = "
"navigator.platform;</script></body></html>");
return std::move(result);
}
if (request.GetURL().path() == kLinkPage) {
auto result = std::make_unique<net::test_server::BasicHttpResponse>();
result->set_content_type("text/html");
std::string href = std::string(kPage1).substr(1);
result->set_content("<html><body>" + std::string(kLinkLoaded) + "<a id=\"" +
kLinkPageLinkID + "\" href=\"" + href +
"\">page with platform</a></body></html>");
return std::move(result);
}
if (request.GetURL().path() == kWindowOpenJSPage) {
auto result = std::make_unique<net::test_server::BasicHttpResponse>();
result->set_content_type("text/html");
std::string href = std::string(kPage1).substr(1);
result->set_content(
"<html><body><button id=\"button\" "
"onclick=\"openWithJS()\">button</button><script "
"type=\"text/javascript\">function openWithJS() {window.open(\"" +
href + "\");}</script></body></html>");
return std::move(result);
}
if (request.GetURL().path() == kIFramePage) {
auto result = std::make_unique<net::test_server::BasicHttpResponse>();
result->set_content_type("text/html");
std::string href = std::string(kLinkPage).substr(1);
result->set_content("<html><body><iframe src=\"" + href +
"\"/></body></html>");
return std::move(result);
}
return nullptr;
}
// Returns the platform name of the current device.
std::string platform() {
return base::SysNSStringToUTF8([[UIDevice currentDevice] model]);
}
} // namespace
// Test cases to make sure that the platform advertised by the navigator is the
// correct one.
@interface ContentModeTestCase : ChromeTestCase
@end
@implementation ContentModeTestCase
- (void)setUp {
[super setUp];
self.testServer->RegisterRequestHandler(base::BindRepeating(&HandleRequest));
GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
}
// Tests the platform when the page is directly loaded.
- (void)testPageLoad {
[ChromeEarlGrey loadURL:self.testServer->GetURL(kPage1)];
[ChromeEarlGrey waitForWebStateContainingText:platform()];
}
// Tests the platform after a back navigation on a loaded page.
- (void)testBackForward {
[ChromeEarlGrey loadURL:self.testServer->GetURL(kPage1)];
[ChromeEarlGrey waitForWebStateContainingText:platform()];
[ChromeEarlGrey loadURL:self.testServer->GetURL(kLinkPage)];
[ChromeEarlGrey waitForWebStateContainingText:kLinkLoaded];
[ChromeEarlGrey goBack];
[ChromeEarlGrey waitForWebStateContainingText:platform()];
}
// Tests the platform when the page is loaded after a rendered navigation.
- (void)testRendererLoad {
// Load the first page and do a renderer-initialized navigation to the second
// page.
[ChromeEarlGrey loadURL:self.testServer->GetURL(kLinkPage)];
[ChromeEarlGrey
tapWebStateElementWithID:base::SysUTF8ToNSString(kLinkPageLinkID)];
[ChromeEarlGrey waitForWebStateContainingText:platform()];
}
// Tests the platform when the page is opened after a window.open JS event.
- (void)testJSWindowOpenPage {
[ChromeEarlGrey loadURL:self.testServer->GetURL(kWindowOpenJSPage)];
[ChromeEarlGrey tapWebStateElementWithID:@"button"];
[ChromeEarlGrey waitForWebStateContainingText:platform()];
}
// Tests the platform when the page is inside an iframe.
- (void)testIFrameNavigation {
[ChromeEarlGrey loadURL:self.testServer->GetURL(kIFramePage)];
[ChromeEarlGrey tapWebStateElementInIFrameWithID:kLinkPageLinkID];
[ChromeEarlGrey waitForWebStateFrameContainingText:platform()];
[ChromeEarlGrey tapWebStateElementInIFrameWithID:kPageLinkID];
[ChromeEarlGrey waitForWebStateFrameContainingText:kLinkLoaded];
[ChromeEarlGrey goBack];
[ChromeEarlGrey waitForWebStateFrameContainingText:platform()];
}
@end
......@@ -352,6 +352,10 @@ id ExecuteJavaScript(NSString* javascript, NSError* __autoreleasing* out_error);
// not met within a timeout a GREYAssert is induced.
- (void)waitForWebStateContainingText:(const std::string&)UTF8Text;
// Waits for the main frame or an iframe to contain |UTF8Text|. If the condition
// is not met within a timeout a GREYAssert is induced.
- (void)waitForWebStateFrameContainingText:(const std::string&)UTF8Text;
// Waits for the current web state to contain |UTF8Text|. If the condition is
// not met within the given |timeout| a GREYAssert is induced.
- (void)waitForWebStateContainingText:(const std::string&)UTF8Text
......
......@@ -501,6 +501,12 @@ GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(ChromeEarlGreyAppInterface)
timeout:kWaitForUIElementTimeout];
}
- (void)waitForWebStateFrameContainingText:(const std::string&)UTF8Text {
NSString* text = base::SysUTF8ToNSString(UTF8Text);
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface waitForWebStateContainingTextInIFrame:text]);
}
- (void)waitForWebStateContainingText:(const std::string&)UTF8Text
timeout:(NSTimeInterval)timeout {
NSString* text = base::SysUTF8ToNSString(UTF8Text);
......
......@@ -168,6 +168,11 @@
// otherwise nil.
+ (NSError*)waitForWebStateContainingElement:(ElementSelector*)selector;
// Waits for the current web state's frames to contain |text|.
// If not succeed returns an NSError indicating why the operation failed,
// otherwise nil.
+ (NSError*)waitForWebStateContainingTextInIFrame:(NSString*)text;
// Attempts to submit form with |formID| in the current WebState.
// Returns nil on success, or else an NSError indicating why the operation
// failed.
......
......@@ -297,6 +297,21 @@ using chrome_test_util::BrowserCommandDispatcherForMainBVC;
return nil;
}
+ (NSError*)waitForWebStateContainingTextInIFrame:(NSString*)text {
std::string stringText = base::SysNSStringToUTF8(text);
bool success = WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
return web::test::IsWebViewContainingTextInFrame(
chrome_test_util::GetCurrentWebState(), stringText);
});
if (!success) {
NSString* NSErrorDescription = [NSString
stringWithFormat:
@"Failed waiting for web state's iframes containing text %@", text];
return testing::NSErrorWithLocalizedDescription(NSErrorDescription);
}
return nil;
}
+ (NSError*)submitWebStateFormWithID:(NSString*)formID {
bool success = web::test::SubmitWebViewFormWithId(
chrome_test_util::GetCurrentWebState(), base::SysNSStringToUTF8(formID));
......
......@@ -10,11 +10,17 @@ found in the LICENSE file. -->
<title>Test Request Desktop/Mobile Script</title>
</head>
<body>
<p id="userAgentResult"/>
<p id="platformResult"/>
<script type="text/javascript">
var userAgent = "";
if (navigator.appVersion.toLowerCase().indexOf("mobile") > -1)
document.write('Mobile');
userAgent = 'Mobile';
else
document.write('Desktop');
userAgent = 'Desktop';
document.getElementById("userAgentResult").innerHTML = userAgent;
document.getElementById("platformResult").innerHTML = navigator.platform;
</script>
</body>
</html>
......@@ -13,6 +13,7 @@
#import "ios/web/common/url_scheme_util.h"
#import "ios/web/js_messaging/crw_js_injector.h"
#import "ios/web/js_messaging/web_frames_manager_impl.h"
#import "ios/web/navigation/crw_navigation_item_holder.h"
#import "ios/web/navigation/crw_pending_navigation_info.h"
#import "ios/web/navigation/crw_wk_navigation_states.h"
#import "ios/web/navigation/error_page_helper.h"
......@@ -133,6 +134,64 @@ void ReportOutOfSyncURLInDidStartProvisionalNavigation(
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView*)webView
decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction
preferences:(WKWebpagePreferences*)preferences
decisionHandler:
(void (^)(WKNavigationActionPolicy,
WKWebpagePreferences*))decisionHandler
API_AVAILABLE(ios(13)) {
web::NavigationItem* item = nullptr;
if (navigationAction.navigationType == WKNavigationTypeBackForward) {
// Use the item associated with the back/forward item to have the same user
// agent as the one used the first time.
item = [[CRWNavigationItemHolder
holderForBackForwardListItem:webView.backForwardList.currentItem]
navigationItem];
} else {
// There is no guarantee that the pending item belongs to this navigation
// but it is very likely that it is the case.
item = self.navigationManagerImpl->GetPendingItem();
}
// Don't initialize the userAgentType to have compilation error if there is a
// code path leaving it uninitialized.
web::UserAgentType userAgentType;
if (item) {
userAgentType = item->GetUserAgentType();
} else {
// Probably a renderer-initiated navigation. Use the UserAgent of the
// previous navigation.
item = self.webStateImpl->GetNavigationManager()->GetLastCommittedItem();
if (item) {
userAgentType = item->GetUserAgentForInheritance();
} else {
// It is possible that there isn't a last committed item, for example if a
// new tab is being opened via JavaScript.
if (base::FeatureList::IsEnabled(
web::features::kUseDefaultUserAgentInWebClient)) {
userAgentType = web::UserAgentType::AUTOMATIC;
} else {
userAgentType = web::UserAgentType::MOBILE;
}
}
if (userAgentType == web::UserAgentType::AUTOMATIC) {
userAgentType = web::GetWebClient()->GetDefaultUserAgent(webView);
}
}
WKContentMode contentMode = userAgentType == web::UserAgentType::DESKTOP
? WKContentModeDesktop
: WKContentModeMobile;
[self webView:webView
decidePolicyForNavigationAction:navigationAction
decisionHandler:^(WKNavigationActionPolicy policy) {
preferences.preferredContentMode = contentMode;
decisionHandler(policy, preferences);
}];
}
- (void)webView:(WKWebView*)webView
decidePolicyForNavigationAction:(WKNavigationAction*)action
decisionHandler:
......
......@@ -66,6 +66,7 @@ source_set("util") {
"//ios/web/js_messaging",
"//ios/web/public:public",
"//ios/web/public/deprecated",
"//ios/web/public/js_messaging",
"//ios/web/web_state:web_state_impl_header",
"//ios/web/web_state/ui:ui",
"//ios/web/web_view:util",
......
......@@ -25,6 +25,11 @@ enum ImageStateElement {
// Otherwise, returns false.
bool IsWebViewContainingText(web::WebState* web_state, const std::string& text);
// Returns true if there is a a frame from |web_state| that contains |text|.
// This method is waiting for the duration of the JavaScript messages exchange.
bool IsWebViewContainingTextInFrame(web::WebState* web_state,
const std::string& text);
// Waits for the given web state to contain |text|. If the condition is not met
// within |timeout| false is returned.
bool WaitForWebViewContainingText(
......
......@@ -7,11 +7,15 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/values.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/test/web_view_interaction_test_util.h"
#import "ios/web/public/web_state.h"
#import "net/base/mac/url_conversions.h"
#include "url/gurl.h"
......@@ -79,6 +83,7 @@ UIImage* LoadImage(const GURL& image_url) {
}
using base::test::ios::WaitUntilConditionOrTimeout;
using base::test::ios::kWaitForJSCompletionTimeout;
using base::test::ios::kWaitForUIElementTimeout;
namespace web {
......@@ -95,6 +100,36 @@ bool IsWebViewContainingText(web::WebState* web_state,
return false;
}
bool IsWebViewContainingTextInFrame(web::WebState* web_state,
const std::string& text) {
WebFramesManager* frames_manager = web_state->GetWebFramesManager();
const base::TimeDelta kCallJavascriptFunctionTimeout =
base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
__block NSInteger number_frames_processing = 0;
__block bool text_found = false;
for (WebFrame* frame : frames_manager->GetAllWebFrames()) {
number_frames_processing++;
std::vector<base::Value> parameters;
parameters.push_back(base::Value(text));
parameters.push_back(base::Value(100.0));
frame->CallJavaScriptFunction("findInPage.findString", parameters,
base::BindOnce(^(const base::Value* value) {
if (value) {
text_found =
text_found || value->GetDouble() != 0;
}
number_frames_processing--;
}),
kCallJavascriptFunctionTimeout);
}
bool success = WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
if (text_found)
return true;
return number_frames_processing == 0;
});
return text_found && success;
}
bool WaitForWebViewContainingText(web::WebState* web_state,
std::string text,
NSTimeInterval timeout) {
......
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