Commit c5cf90b9 authored by Jiajun Ou's avatar Jiajun Ou Committed by Commit Bot

Enable initialization of CWVWebView from WKWebViewConfiguration

The primary use case is to allow opening CWVWebView from WKWebView on window.open().
Background: https://docs.google.com/document/d/1IjJho7tGNp8azsA075VWBowrsi0m4lHAQarCvfF286I/edit#heading=h.bj843njsw7va
Design doc: https://docs.google.com/document/d/1cVySp7_L-kRqP2Q_R3YrS8d7XsbVqTjUbb7UhIMni0k/

Change-Id: I95753c511affaa91f355cf9e75d336933850d854
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1864750Reviewed-by: default avatarHiroshi Ichikawa <ichikawa@chromium.org>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Commit-Queue: Jiajun Ou <garzonou@google.com>
Auto-Submit: Jiajun Ou <garzonou@google.com>
Cr-Commit-Position: refs/heads/master@{#714423}
parent d0cde2fa
...@@ -25,6 +25,10 @@ include_rules = [ ...@@ -25,6 +25,10 @@ include_rules = [
"+ios/web/common", "+ios/web/common",
"+ios/web/public", "+ios/web/public",
# Chrome cannot use any ios/web APIs inside ios/web/public that are
# designed only for ios/web_view.
"-ios/web/public/web_view_only",
# All code in ios/chrome assumes that web::BrowserState* can be safely # All code in ios/chrome assumes that web::BrowserState* can be safely
# casted to ios::ChromeBrowserState*. This mean that no code should use # casted to ios::ChromeBrowserState*. This mean that no code should use
# web::TestBrowserState in ios/chrome. # web::TestBrowserState in ios/chrome.
......
...@@ -204,6 +204,7 @@ test("ios_web_unittests") { ...@@ -204,6 +204,7 @@ test("ios_web_unittests") {
"//ios/web/favicon:unittests", "//ios/web/favicon:unittests",
"//ios/web/find_in_page:find_in_page_unittests", "//ios/web/find_in_page:find_in_page_unittests",
"//ios/web/js_messaging:unittests", "//ios/web/js_messaging:unittests",
"//ios/web/public/web_view_only:unittests",
"//ios/web/security:unittests", "//ios/web/security:unittests",
"//ios/web/session:unittests", "//ios/web/session:unittests",
"//ios/web/test:packed_resources", "//ios/web/test:packed_resources",
......
# Copyright 2019 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/build/config.gni")
source_set("web_view_only") {
configs += [ "//build/config/compiler:enable_arc" ]
public_deps = [
":web_view_only_api_header",
]
deps = [
"//ios/web/web_view_only",
]
visibility = [
"//ios/web_view:*",
"//ios/web/web_view_only:unittests",
]
}
source_set("web_view_only_api_header") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"wk_web_view_configuration_util.h",
]
visibility = [
":*",
"//ios/web/web_view_only:*",
]
}
source_set("unittests") {
configs += [ "//build/config/compiler:enable_arc" ]
testonly = true
deps = [
"//ios/web/web_view_only:unittests",
]
}
This directory contains the APIs that is only allowed to be used in //ios/web_view.
\ No newline at end of file
// Copyright 2019 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.
#ifndef IOS_WEB_PUBLIC_WEB_VIEW_ONLY_WK_WEB_VIEW_CONFIGURATION_UTIL_H_
#define IOS_WEB_PUBLIC_WEB_VIEW_ONLY_WK_WEB_VIEW_CONFIGURATION_UTIL_H_
@class WKWebView;
@class WKWebViewConfiguration;
namespace web {
class WebState;
// Creates the web view of |web_state| with given |configuration|.
// Returns the created web view.
// This must be called immediately after |web_state| is created
// e.g., with web::WebState::Create().
//
// The goal of writing this function is to make it possible to construct
// CWVWebView from a WKWebViewConfiguration that is not originated from a
// //ios/web managed WKWebView (e.g. when handling window.open() from a normal
// WKWebView that is not //ios/web, such a WKWebViewConfiguration will be
// provided and necessary for creating a new web view).
WKWebView* EnsureWebViewCreatedWithConfiguration(
WebState* web_state,
WKWebViewConfiguration* configuration);
} // namespace web
#endif // IOS_WEB_PUBLIC_WEB_VIEW_ONLY_WK_WEB_VIEW_CONFIGURATION_UTIL_H_
...@@ -26,6 +26,7 @@ enum class WKNavigationState; ...@@ -26,6 +26,7 @@ enum class WKNavigationState;
@class CRWWebViewContentView; @class CRWWebViewContentView;
@protocol CRWWebViewProxy; @protocol CRWWebViewProxy;
class GURL; class GURL;
@class WKWebView;
namespace web { namespace web {
class NavigationItem; class NavigationItem;
...@@ -189,6 +190,9 @@ class WebStateImpl; ...@@ -189,6 +190,9 @@ class WebStateImpl;
- (void)takeSnapshotWithRect:(CGRect)rect - (void)takeSnapshotWithRect:(CGRect)rect
completion:(void (^)(UIImage* snapshot))completion; completion:(void (^)(UIImage* snapshot))completion;
// Creates a web view if it's not yet created. Returns the web view.
- (WKWebView*)ensureWebViewCreated;
@end @end
#pragma mark Testing #pragma mark Testing
......
...@@ -1599,15 +1599,21 @@ typedef void (^ViewportStateCompletion)(const web::PageViewportState*); ...@@ -1599,15 +1599,21 @@ typedef void (^ViewportStateCompletion)(const web::PageViewportState*);
} }
// Creates a web view if it's not yet created. // Creates a web view if it's not yet created.
- (void)ensureWebViewCreated { - (WKWebView*)ensureWebViewCreated {
WKWebViewConfiguration* config = WKWebViewConfiguration* config =
[self webViewConfigurationProvider].GetWebViewConfiguration(); [self webViewConfigurationProvider].GetWebViewConfiguration();
[self ensureWebViewCreatedWithConfiguration:config]; return [self ensureWebViewCreatedWithConfiguration:config];
} }
// Creates a web view with given |config|. No-op if web view is already created. // Creates a web view with given |config|. No-op if web view is already created.
- (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config { - (WKWebView*)ensureWebViewCreatedWithConfiguration:
(WKWebViewConfiguration*)config {
if (!self.webView) { if (!self.webView) {
// This has to be called to ensure the container view of `self.webView` is
// created. Otherwise `self.webView.frame.size` will be CGSizeZero which
// fails a DCHECK later.
[self ensureContainerViewCreated];
[self setWebView:[self webViewWithConfiguration:config]]; [self setWebView:[self webViewWithConfiguration:config]];
// The following is not called in -setWebView: as the latter used in unit // The following is not called in -setWebView: as the latter used in unit
// tests with fake web view, which cannot be added to view hierarchy. // tests with fake web view, which cannot be added to view hierarchy.
...@@ -1651,6 +1657,8 @@ typedef void (^ViewportStateCompletion)(const web::PageViewportState*); ...@@ -1651,6 +1657,8 @@ typedef void (^ViewportStateCompletion)(const web::PageViewportState*);
if (![self.legacyNativeController shouldLoadURLInNativeView:visibleURL]) if (![self.legacyNativeController shouldLoadURLInNativeView:visibleURL])
[self displayWebView]; [self displayWebView];
} }
return self.webView;
} }
// Returns a new autoreleased web view created with given configuration. // Returns a new autoreleased web view created with given configuration.
......
...@@ -415,6 +415,14 @@ TEST_P(CRWWebControllerTest, BackForwardWithPendingNavigation) { ...@@ -415,6 +415,14 @@ TEST_P(CRWWebControllerTest, BackForwardWithPendingNavigation) {
EXPECT_EQ(web::WKNavigationState::FINISHED, web_controller().navigationState); EXPECT_EQ(web::WKNavigationState::FINISHED, web_controller().navigationState);
} }
// Tests that a web view is created after calling -[ensureWebViewCreated].
TEST_P(CRWWebControllerTest, WebViewCreatedAfterEnsureWebViewCreated) {
[web_controller() removeWebView];
WKWebView* web_view = [web_controller() ensureWebViewCreated];
EXPECT_TRUE(web_view);
EXPECT_NSEQ(web_view, web_controller().jsInjector.webView);
}
INSTANTIATE_TEST_SUITES(CRWWebControllerTest); INSTANTIATE_TEST_SUITES(CRWWebControllerTest);
// Test fixture to test JavaScriptDialogPresenter. // Test fixture to test JavaScriptDialogPresenter.
......
...@@ -31,6 +31,25 @@ class WKWebViewConfigurationProvider : public base::SupportsUserData::Data { ...@@ -31,6 +31,25 @@ class WKWebViewConfigurationProvider : public base::SupportsUserData::Data {
static web::WKWebViewConfigurationProvider& FromBrowserState( static web::WKWebViewConfigurationProvider& FromBrowserState(
web::BrowserState* browser_state); web::BrowserState* browser_state);
// Resets the configuration saved in this WKWebViewConfigurationProvider
// using the given |configuration|. First |configuration| is shallow cloned
// and then Chrome's configuration initialization logic will be applied to
// make it work for //ios/web. If |configuration| is nil, a new
// WKWebViewConfiguration object will be created and set.
//
// WARNING: This method should NOT be used
// for any |configuration| that is originated from a //ios/web managed
// WKWebView (e.g. you will get a WKWebViewConfiguration from a delegate
// method when window.open() is called in a //ios/web managed WKWebView),
// because such a |configuration| is based on the current //ios/web
// configuration which has already been initialized with the Chrome's
// configuration initialization logic when it was passed to a initializer of
// //ios/web. Which means, this method should only be used for a newly created
// |configuration| or a |configuration| originated from somewhere outside
// //ios/web. This method is mainly used by
// WKWebViewConfigurationProvider::GetWebViewConfiguration().
void ResetWithWebViewConfiguration(WKWebViewConfiguration* configuration);
// Returns an autoreleased shallow copy of WKWebViewConfiguration associated // Returns an autoreleased shallow copy of WKWebViewConfiguration associated
// with browser state. Lazily creates the config. Configuration's // with browser state. Lazily creates the config. Configuration's
// |preferences| will have scriptCanOpenWindowsAutomatically property set to // |preferences| will have scriptCanOpenWindowsAutomatically property set to
...@@ -59,7 +78,7 @@ class WKWebViewConfigurationProvider : public base::SupportsUserData::Data { ...@@ -59,7 +78,7 @@ class WKWebViewConfigurationProvider : public base::SupportsUserData::Data {
explicit WKWebViewConfigurationProvider(BrowserState* browser_state); explicit WKWebViewConfigurationProvider(BrowserState* browser_state);
WKWebViewConfigurationProvider() = delete; WKWebViewConfigurationProvider() = delete;
CRWWebUISchemeHandler* scheme_handler_ = nil; CRWWebUISchemeHandler* scheme_handler_ = nil;
WKWebViewConfiguration* configuration_; WKWebViewConfiguration* configuration_ = nil;
CRWWKScriptMessageRouter* router_; CRWWKScriptMessageRouter* router_;
BrowserState* browser_state_; BrowserState* browser_state_;
......
...@@ -92,74 +92,85 @@ WKWebViewConfigurationProvider::WKWebViewConfigurationProvider( ...@@ -92,74 +92,85 @@ WKWebViewConfigurationProvider::WKWebViewConfigurationProvider(
WKWebViewConfigurationProvider::~WKWebViewConfigurationProvider() = default; WKWebViewConfigurationProvider::~WKWebViewConfigurationProvider() = default;
WKWebViewConfiguration* void WKWebViewConfigurationProvider::ResetWithWebViewConfiguration(
WKWebViewConfigurationProvider::GetWebViewConfiguration() { WKWebViewConfiguration* configuration) {
DCHECK([NSThread isMainThread]); DCHECK([NSThread isMainThread]);
if (!configuration_) {
configuration_ = [[WKWebViewConfiguration alloc] init];
if (browser_state_->IsOffTheRecord()) {
[configuration_
setWebsiteDataStore:[WKWebsiteDataStore nonPersistentDataStore]];
}
if (base::FeatureList::IsEnabled( if (!configuration) {
web::features::kIgnoresViewportScaleLimits)) { configuration = [[WKWebViewConfiguration alloc] init];
[configuration_ setIgnoresViewportScaleLimits:YES]; } else {
} configuration = [configuration copy];
}
configuration_ = configuration;
if (@available(iOS 13, *)) { if (browser_state_->IsOffTheRecord()) {
@try { [configuration_
// Disable system context menu on iOS 13 and later. Disabling setWebsiteDataStore:[WKWebsiteDataStore nonPersistentDataStore]];
// "longPressActions" prevents the WKWebView ContextMenu from being }
// displayed.
// https://github.com/WebKit/webkit/blob/1233effdb7826a5f03b3cdc0f67d713741e70976/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfiguration.mm#L307
[configuration_ setValue:@NO forKey:@"longPressActionsEnabled"];
} @catch (NSException* exception) {
NOTREACHED() << "Error setting value for longPressActionsEnabled";
}
}
[configuration_ setAllowsInlineMediaPlayback:YES]; if (base::FeatureList::IsEnabled(
// setJavaScriptCanOpenWindowsAutomatically is required to support popups. web::features::kIgnoresViewportScaleLimits)) {
[[configuration_ preferences] setJavaScriptCanOpenWindowsAutomatically:YES]; [configuration_ setIgnoresViewportScaleLimits:YES];
// Main frame script depends upon scripts injected into all frames, so the }
// "AllFrames" scripts must be injected first.
[[configuration_ userContentController] if (@available(iOS 13, *)) {
addUserScript:InternalGetDocumentStartScriptForAllFrames( @try {
browser_state_)]; // Disable system context menu on iOS 13 and later. Disabling
[[configuration_ userContentController] // "longPressActions" prevents the WKWebView ContextMenu from being
addUserScript:InternalGetDocumentStartScriptForMainFrame( // displayed.
browser_state_)]; // https://github.com/WebKit/webkit/blob/1233effdb7826a5f03b3cdc0f67d713741e70976/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfiguration.mm#L307
[[configuration_ userContentController] [configuration_ setValue:@NO forKey:@"longPressActionsEnabled"];
addUserScript:InternalGetDocumentEndScriptForAllFrames(browser_state_)]; } @catch (NSException* exception) {
[[configuration_ userContentController] NOTREACHED() << "Error setting value for longPressActionsEnabled";
addUserScript:InternalGetDocumentEndScriptForMainFrame(browser_state_)];
if (!scheme_handler_) {
scoped_refptr<network::SharedURLLoaderFactory> shared_loader_factory =
browser_state_->GetSharedURLLoaderFactory();
scheme_handler_ = [[CRWWebUISchemeHandler alloc]
initWithURLLoaderFactory:shared_loader_factory];
}
WebClient::Schemes schemes;
GetWebClient()->AddAdditionalSchemes(&schemes);
GetWebClient()->GetAdditionalWebUISchemes(&(schemes.standard_schemes));
for (std::string scheme : schemes.standard_schemes) {
[configuration_ setURLSchemeHandler:scheme_handler_
forURLScheme:base::SysUTF8ToNSString(scheme)];
} }
}
[configuration_ setAllowsInlineMediaPlayback:YES];
// setJavaScriptCanOpenWindowsAutomatically is required to support popups.
[[configuration_ preferences] setJavaScriptCanOpenWindowsAutomatically:YES];
// Main frame script depends upon scripts injected into all frames, so the
// "AllFrames" scripts must be injected first.
[[configuration_ userContentController]
addUserScript:InternalGetDocumentStartScriptForAllFrames(browser_state_)];
[[configuration_ userContentController]
addUserScript:InternalGetDocumentStartScriptForMainFrame(browser_state_)];
[[configuration_ userContentController]
addUserScript:InternalGetDocumentEndScriptForAllFrames(browser_state_)];
[[configuration_ userContentController]
addUserScript:InternalGetDocumentEndScriptForMainFrame(browser_state_)];
if (!scheme_handler_) {
scoped_refptr<network::SharedURLLoaderFactory> shared_loader_factory =
browser_state_->GetSharedURLLoaderFactory();
scheme_handler_ = [[CRWWebUISchemeHandler alloc]
initWithURLLoaderFactory:shared_loader_factory];
}
WebClient::Schemes schemes;
GetWebClient()->AddAdditionalSchemes(&schemes);
GetWebClient()->GetAdditionalWebUISchemes(&(schemes.standard_schemes));
for (std::string scheme : schemes.standard_schemes) {
[configuration_ setURLSchemeHandler:scheme_handler_
forURLScheme:base::SysUTF8ToNSString(scheme)];
}
for (auto& observer : observers_)
observer.DidCreateNewConfiguration(this, configuration_);
// Workaround to force the creation of the WKWebsiteDataStore. This
// workaround need to be done here, because this method returns a copy of
// the already created configuration.
NSSet* data_types = [NSSet setWithObject:WKWebsiteDataTypeCookies];
[configuration_.websiteDataStore
fetchDataRecordsOfTypes:data_types
completionHandler:^(NSArray<WKWebsiteDataRecord*>* records){
}];
}
for (auto& observer : observers_) WKWebViewConfiguration*
observer.DidCreateNewConfiguration(this, configuration_); WKWebViewConfigurationProvider::GetWebViewConfiguration() {
DCHECK([NSThread isMainThread]);
// Workaround to force the creation of the WKWebsiteDataStore. This if (!configuration_) {
// workaround need to be done here, because this method returns a copy of ResetWithWebViewConfiguration(nil);
// the already created configuration.
NSSet* data_types = [NSSet setWithObject:WKWebsiteDataTypeCookies];
[configuration_.websiteDataStore
fetchDataRecordsOfTypes:data_types
completionHandler:^(NSArray<WKWebsiteDataRecord*>* records){
}];
} }
// This is a shallow copy to prevent callers from changing the internals of // This is a shallow copy to prevent callers from changing the internals of
......
...@@ -182,5 +182,34 @@ TEST_F(WKWebViewConfigurationProviderTest, Observers) { ...@@ -182,5 +182,34 @@ TEST_F(WKWebViewConfigurationProviderTest, Observers) {
EXPECT_FALSE(observer.GetLastCreatedWKConfiguration()); EXPECT_FALSE(observer.GetLastCreatedWKConfiguration());
} }
// Tests that if -[ResetWithWebViewConfiguration:] copies and applies Chrome's
// initialization logic to the |config| that passed into that method
TEST_F(WKWebViewConfigurationProviderTest, ResetConfiguration) {
std::unique_ptr<TestBrowserState> browser_state =
std::make_unique<TestBrowserState>();
WKWebViewConfigurationProvider* provider = &GetProvider(browser_state.get());
FakeWKConfigurationProviderObserver observer(provider);
ASSERT_FALSE(observer.GetLastCreatedWKConfiguration());
WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
config.allowsInlineMediaPlayback = NO;
provider->ResetWithWebViewConfiguration(config);
WKWebViewConfiguration* actual = observer.GetLastCreatedWKConfiguration();
ASSERT_TRUE(actual);
// To check the configuration inside is reset.
EXPECT_EQ(config.preferences, actual.preferences);
// To check Chrome's initialization logic has been applied to |actual|,
// where the |actual.allowsInlineMediaPlayback| should be overwriten by YES.
EXPECT_EQ(NO, config.allowsInlineMediaPlayback);
EXPECT_EQ(YES, actual.allowsInlineMediaPlayback);
// Compares the POINTERS to make sure the |config| has been shallow cloned
// inside the |provider|.
EXPECT_NE(config, actual);
}
} // namespace } // namespace
} // namespace web } // namespace web
# Copyright 2019 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/build/config.gni")
source_set("web_view_only") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"wk_web_view_configuration_util.mm",
]
deps = [
"//base",
"//ios/web/public/web_view_only:web_view_only_api_header",
"//ios/web/web_state:web_state_impl_header",
"//ios/web/web_state/ui",
"//ios/web/web_state/ui:wk_web_view_configuration_provider",
]
visibility = [
":*",
"//ios/web/public/web_view_only:*",
]
}
source_set("unittests") {
configs += [ "//build/config/compiler:enable_arc" ]
testonly = true
sources = [
"wk_web_view_configuration_util_unittests.mm",
]
deps = [
"//base",
"//ios/web/public/web_view_only",
"//ios/web/test:test_support",
"//third_party/webrtc/rtc_base",
]
visibility = [
":*",
"//ios/web/public/web_view_only:*",
]
}
// Copyright 2019 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_view_only/wk_web_view_configuration_util.h"
#import <WebKit/WebKit.h>
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/ui/wk_web_view_configuration_provider.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
namespace web {
WKWebView* EnsureWebViewCreatedWithConfiguration(
WebState* web_state,
WKWebViewConfiguration* configuration) {
WebStateImpl* impl = static_cast<WebStateImpl*>(web_state);
BrowserState* browser_state = impl->GetBrowserState();
CRWWebController* web_controller = impl->GetWebController();
WKWebViewConfigurationProvider& provider =
WKWebViewConfigurationProvider::FromBrowserState(browser_state);
provider.ResetWithWebViewConfiguration(configuration);
// |web_controller| will get the |configuration| from the |provider| to create
// the webView to return.
return [web_controller ensureWebViewCreated];
}
} // namespace web
// Copyright 2019 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_view_only/wk_web_view_configuration_util.h"
#import <WebKit/WebKit.h>
#import "ios/web/test/web_test_with_web_controller.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace web {
// Tests for all web_view_only APIs in //ios/web/public/web_view_only
using WebViewOnlyAPITest = web::WebTestWithWebController;
// A test for web::EnsureWebViewCreatedWithConfiguration()
TEST_F(WebViewOnlyAPITest, EnsureWebViewCreatedWithConfiguration) {
WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
const CGFloat kMinimumFontSize = 99;
config.preferences.minimumFontSize = kMinimumFontSize;
[web_controller() removeWebView];
WKWebView* web_view =
web::EnsureWebViewCreatedWithConfiguration(web_state(), config);
ASSERT_TRUE(web_view);
EXPECT_EQ(kMinimumFontSize,
web_view.configuration.preferences.minimumFontSize);
[web_controller() removeWebView];
}
} // namespace
...@@ -320,6 +320,7 @@ ios_web_view_deps = [ ...@@ -320,6 +320,7 @@ ios_web_view_deps = [
"//ios/web/public/deprecated", "//ios/web/public/deprecated",
"//ios/web/public/security", "//ios/web/public/security",
"//ios/web/public/js_messaging", "//ios/web/public/js_messaging",
"//ios/web/public/web_view_only",
"//net", "//net",
"//net:extras", "//net:extras",
"//services/metrics/public/cpp:metrics_cpp", "//services/metrics/public/cpp:metrics_cpp",
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#import <WebKit/WebKit.h>
#include "base/bind.h" #include "base/bind.h"
#include "base/json/json_writer.h" #include "base/json/json_writer.h"
#include "base/mac/foundation_util.h" #include "base/mac/foundation_util.h"
...@@ -34,6 +36,7 @@ ...@@ -34,6 +36,7 @@
#import "ios/web/public/web_state.h" #import "ios/web/public/web_state.h"
#import "ios/web/public/web_state_delegate_bridge.h" #import "ios/web/public/web_state_delegate_bridge.h"
#import "ios/web/public/web_state_observer_bridge.h" #import "ios/web/public/web_state_observer_bridge.h"
#import "ios/web/public/web_view_only/wk_web_view_configuration_util.h"
#include "ios/web_view/cwv_web_view_buildflags.h" #include "ios/web_view/cwv_web_view_buildflags.h"
#import "ios/web_view/internal/autofill/cwv_autofill_controller_internal.h" #import "ios/web_view/internal/autofill/cwv_autofill_controller_internal.h"
#import "ios/web_view/internal/cwv_favicon_internal.h" #import "ios/web_view/internal/cwv_favicon_internal.h"
...@@ -215,12 +218,25 @@ static NSString* gUserAgentProduct = nil; ...@@ -215,12 +218,25 @@ static NSString* gUserAgentProduct = nil;
- (instancetype)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame
configuration:(CWVWebViewConfiguration*)configuration { configuration:(CWVWebViewConfiguration*)configuration {
return [self initWithFrame:frame
configuration:configuration
WKConfiguration:nil
createdWKWebView:nil];
}
- (instancetype)initWithFrame:(CGRect)frame
configuration:(CWVWebViewConfiguration*)configuration
WKConfiguration:(WKWebViewConfiguration*)wkConfiguration
createdWKWebView:(WKWebView**)createdWebView {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
if (self) { if (self) {
_configuration = configuration; _configuration = configuration;
[_configuration registerWebView:self]; [_configuration registerWebView:self];
_scrollView = [[CWVScrollView alloc] init]; _scrollView = [[CWVScrollView alloc] init];
[self resetWebStateWithSessionStorage:nil];
[self resetWebStateWithSessionStorage:nil
WKConfiguration:wkConfiguration
createdWKWebView:createdWebView];
} }
return self; return self;
} }
...@@ -586,7 +602,9 @@ static NSString* gUserAgentProduct = nil; ...@@ -586,7 +602,9 @@ static NSString* gUserAgentProduct = nil;
[super decodeRestorableStateWithCoder:coder]; [super decodeRestorableStateWithCoder:coder];
CRWSessionStorage* sessionStorage = CRWSessionStorage* sessionStorage =
[coder decodeObjectForKey:kSessionStorageKey]; [coder decodeObjectForKey:kSessionStorageKey];
[self resetWebStateWithSessionStorage:sessionStorage]; [self resetWebStateWithSessionStorage:sessionStorage
WKConfiguration:nil
createdWKWebView:nil];
} }
#pragma mark - Private methods #pragma mark - Private methods
...@@ -605,8 +623,15 @@ static NSString* gUserAgentProduct = nil; ...@@ -605,8 +623,15 @@ static NSString* gUserAgentProduct = nil;
// Creates a WebState instance and assigns it to |_webState|. // Creates a WebState instance and assigns it to |_webState|.
// It replaces the old |_webState| if any. // It replaces the old |_webState| if any.
// The WebState is restored from |sessionStorage| if provided. // The WebState is restored from |sessionStorage| if provided.
- (void)resetWebStateWithSessionStorage: //
(nullable CRWSessionStorage*)sessionStorage { // If |wkConfiguration| is provided, the underlying WKWebView is
// initialized with |wkConfiguration|, and assigned to
// |*createdWKWebView| if |createdWKWebView| is not nil.
// |*createdWKWebView| will be provided only if |wkConfiguration| is provided,
// otherwise it will always be reset to nil.
- (void)resetWebStateWithSessionStorage:(CRWSessionStorage*)sessionStorage
WKConfiguration:(WKWebViewConfiguration*)wkConfiguration
createdWKWebView:(WKWebView**)createdWebView {
if (_webState) { if (_webState) {
if (_webStateObserver) { if (_webStateObserver) {
_webState->RemoveObserver(_webStateObserver.get()); _webState->RemoveObserver(_webStateObserver.get());
...@@ -632,6 +657,19 @@ static NSString* gUserAgentProduct = nil; ...@@ -632,6 +657,19 @@ static NSString* gUserAgentProduct = nil;
_webState = web::WebState::Create(webStateCreateParams); _webState = web::WebState::Create(webStateCreateParams);
} }
// WARNING: NOTHING should be here between |web::WebState::Create()| and
// |web::EnsureWebViewCreatedWithConfiguration()|, as this is the requirement
// of |web::EnsureWebViewCreatedWithConfiguration()|
WKWebView* webView = nil;
if (wkConfiguration) {
webView = web::EnsureWebViewCreatedWithConfiguration(_webState.get(),
wkConfiguration);
}
if (createdWebView) {
*createdWebView = webView;
}
WebViewHolder::CreateForWebState(_webState.get()); WebViewHolder::CreateForWebState(_webState.get());
WebViewHolder::FromWebState(_webState.get())->set_web_view(self); WebViewHolder::FromWebState(_webState.get())->set_web_view(self);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define IOS_WEB_VIEW_PUBLIC_CWV_WEB_VIEW_H_ #define IOS_WEB_VIEW_PUBLIC_CWV_WEB_VIEW_H_
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
#import "cwv_export.h" #import "cwv_export.h"
...@@ -136,8 +137,23 @@ CWV_EXPORT ...@@ -136,8 +137,23 @@ CWV_EXPORT
clientID:(NSString*)clientID clientID:(NSString*)clientID
clientSecret:(NSString*)clientSecret; clientSecret:(NSString*)clientSecret;
- (instancetype)initWithFrame:(CGRect)frame
configuration:(CWVWebViewConfiguration*)configuration;
// If |wkConfiguration| is provided, the underlying WKWebView is
// initialized with |wkConfiguration|, and assigned to
// |*createdWKWebView| if |createdWKWebView| is not nil.
// |*createdWKWebView| will be provided only if |wkConfiguration| is provided,
// otherwise it will always be reset to nil.
//
// IMPORTANT: Use |*createdWKWebView| just as a return value of
// -[WKNavigationDelegate
// webView:createWebViewWithConfiguration:...], but for nothing
// else. e.g., You must not access its properties/methods.
- (instancetype)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame
configuration:(CWVWebViewConfiguration*)configuration configuration:(CWVWebViewConfiguration*)configuration
WKConfiguration:(nullable WKWebViewConfiguration*)wkConfiguration
createdWKWebView:(WKWebView* _Nullable* _Nullable)createdWebView
NS_DESIGNATED_INITIALIZER; NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
......
...@@ -13,6 +13,7 @@ source_set("inttests") { ...@@ -13,6 +13,7 @@ source_set("inttests") {
"scroll_view_kvo_inttest.mm", "scroll_view_kvo_inttest.mm",
"ui_delegate_inttest.mm", "ui_delegate_inttest.mm",
"web_view_autofill_inttest.mm", "web_view_autofill_inttest.mm",
"web_view_from_wk_web_view_configuration_inttest.mm",
"web_view_inttest.mm", "web_view_inttest.mm",
"web_view_inttest_base.h", "web_view_inttest_base.h",
"web_view_inttest_base.mm", "web_view_inttest_base.mm",
......
// Copyright 2019 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 <ChromeWebView/ChromeWebView.h>
#import <Foundation/Foundation.h>
#import "base/test/ios/wait_util.h"
#import "ios/web_view/test/observer.h"
#import "ios/web_view/test/web_view_inttest_base.h"
#import "ios/web_view/test/web_view_test_util.h"
#import "net/base/mac/url_conversions.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest_mac.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace ios_web_view {
// Tests if a CWVWebView can be created from a WKWebViewConfiguration outside
// //ios/web
class WebViewFromWKWebViewConfigurationTest : public WebViewInttestBase {
public:
// This method is called by the delegate method called when window.open() is
// called, and the |webView| argument is the newly opened CWVWebView by the
// window.open() call in a normal WKWebView. Saves the |webView| for further
// tests and inserts it into the View Hierarchy tree.
void SetWebView(CWVWebView* webView) {
[web_view_ removeFromSuperview];
web_view_ = webView;
UIViewController* view_controller =
[[[UIApplication sharedApplication] keyWindow] rootViewController];
[view_controller.view addSubview:web_view_];
}
// This method is called by the delegate method called when window.open() is
// called, and the |returned_wk_web_view| argument is the internal WKWebView
// of the newly opened CWVWebView by the window.open() call in a normal
// WKWebView. Saves the |returned_wk_web_view| for further tests.
void SetReturnedWKWebView(WKWebView* returned_wk_web_view) {
returned_wk_web_view_ = returned_wk_web_view;
}
protected:
WebViewFromWKWebViewConfigurationTest() {
// This |CWVWebView *web_view_| is inherited from the base class, but in
// this test case I don't hope to use it, because I need to test a newly
// opened CWVWebView by a window.open() call in a normal WKWebView, instead
// of this one directly generated by the base class from default
// configuration.
[web_view_ removeFromSuperview];
web_view_ = nil;
}
void GenerateTestPageUrls() {
window1_url_ = GetUrlForPageWithHtmlBody("<p>page1</p>");
window2_url_ = GetUrlForPageWithHtmlBody("<p>page2</p>");
}
WKWebView* returned_wk_web_view_ = nil;
GURL window1_url_;
GURL window2_url_;
};
} // namespace ios_web_view
@interface WKUIDelegateForTest : NSObject <WKUIDelegate>
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithTest:
(ios_web_view::WebViewFromWKWebViewConfigurationTest*)test
NS_DESIGNATED_INITIALIZER;
@end
@implementation WKUIDelegateForTest {
ios_web_view::WebViewFromWKWebViewConfigurationTest* _test;
}
- (instancetype)initWithTest:
(ios_web_view::WebViewFromWKWebViewConfigurationTest*)test {
self = [super init];
if (self) {
_test = test;
}
return self;
}
- (WKWebView*)webView:(WKWebView*)webView
createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration
forNavigationAction:(WKNavigationAction*)action
windowFeatures:(WKWindowFeatures*)windowFeatures {
WKWebView* created_web_view = nil;
_test->SetWebView([[CWVWebView alloc]
initWithFrame:UIScreen.mainScreen.bounds
configuration:[CWVWebViewConfiguration defaultConfiguration]
WKConfiguration:configuration
createdWKWebView:&created_web_view]);
_test->SetReturnedWKWebView(created_web_view);
return created_web_view;
}
@end
@interface NavigationFinishedObserver
: NSObject <WKNavigationDelegate, CWVNavigationDelegate>
@property(nonatomic) BOOL navigationFinished;
@end
@implementation NavigationFinishedObserver
- (void)webView:(WKWebView*)webView
didFinishNavigation:(WKNavigation*)navigation {
self.navigationFinished = YES;
}
- (void)webViewDidFinishNavigation:(CWVWebView*)webView {
self.navigationFinished = YES;
}
@end
namespace ios_web_view {
// Tests if a CWVWebView can be created from -[CWVWebView
// initWithFrame:configuration:WKConfiguration:createdWKWebView]
TEST_F(WebViewFromWKWebViewConfigurationTest, FromWKWebViewConfiguration) {
ASSERT_TRUE(test_server_->Start());
CGRect frame = UIScreen.mainScreen.bounds;
WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
WKWebView* wk_web_view = [[WKWebView alloc] initWithFrame:frame
configuration:config];
WKUIDelegateForTest* wk_ui_delegate_for_test =
[[WKUIDelegateForTest alloc] initWithTest:this];
wk_web_view.UIDelegate = wk_ui_delegate_for_test;
NavigationFinishedObserver* observer =
[[NavigationFinishedObserver alloc] init];
UIViewController* view_controller =
[[[UIApplication sharedApplication] keyWindow] rootViewController];
[view_controller.view addSubview:wk_web_view];
// Loads a page in wk_web_view and waits for its completion
GenerateTestPageUrls();
wk_web_view.navigationDelegate = observer;
[wk_web_view loadRequest:[[NSURLRequest alloc]
initWithURL:net::NSURLWithGURL(window1_url_)]];
using base::test::ios::kWaitForPageLoadTimeout;
ASSERT_TRUE(
base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
return observer.navigationFinished;
}));
wk_web_view.navigationDelegate = nil;
// Checks if the page in window1 (wk_web_view) is loaded, by a line of
// JavaScript
__block BOOL is_js_evaluated = NO;
[wk_web_view evaluateJavaScript:@"document.body.innerText"
completionHandler:^(NSString* result, NSError* error) {
ASSERT_FALSE(error);
EXPECT_NSEQ(@"page1", result);
is_js_evaluated = YES;
}];
using base::test::ios::kWaitForJSCompletionTimeout;
ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
kWaitForJSCompletionTimeout, ^{
return is_js_evaluated;
}));
// Opens a new CWVWebView from the wk_web_view
NSString* url_string = net::NSURLWithGURL(window2_url_).absoluteString;
NSString* script =
[NSString stringWithFormat:@"window.open('%@')", url_string];
is_js_evaluated = NO;
[wk_web_view evaluateJavaScript:script
completionHandler:^(NSString* result, NSError* error) {
is_js_evaluated = YES;
}];
ASSERT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
kWaitForJSCompletionTimeout, ^{
return is_js_evaluated;
}));
ASSERT_TRUE(returned_wk_web_view_);
ASSERT_TRUE(web_view_);
// Waits for the page in window2 (CWVWebView *web_view_) to be loaded
observer.navigationFinished = NO;
web_view_.navigationDelegate = observer;
ASSERT_TRUE(
base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
return observer.navigationFinished;
}));
web_view_.navigationDelegate = nil;
// Checks if the page in web_view_ is loaded successfully
NSString* inner_text =
test::EvaluateJavaScript(web_view_, @"document.body.innerText", nil);
EXPECT_NSEQ(@"page2", inner_text);
[wk_web_view removeFromSuperview];
}
} // namespace ios_web_view
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