Commit 653a8e42 authored by mrefaat's avatar mrefaat Committed by Commit Bot

Create CRWWKHTTPCookieStore & move cookies processing to the IO thread.

WKHTTPCookieStore doesn't have a public api to retrieve cookies for URL,
so we have to use getAllCookies for single URL requests. and for that we
have to filter returned cookies based on the URL.
CRWWHTTPCookieStore is a wrapper over WKHTTPCookieStore that workaround
its problems, it adds caching and ensure that the WebsiteDataStore exist
with each getCookies cakk

Move the filtering step to IO Process instead of UI. Also create a
cache to save cookies so getAllCookies isn't called if it's not needed.

Bug: 941643
Change-Id: Ia2a7048646b01650874567c4942e2d107405324f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1538777
Commit-Queue: Mohammad Refaat <mrefaat@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#653595}
parent 5345de28
......@@ -333,6 +333,7 @@ source_set("ios_web_net_unittests") {
sources = [
"net/cert_host_pair_unittest.cc",
"net/cert_policy_unittest.cc",
"net/cookies/crw_wk_http_cookie_store_unittest.mm",
"net/cookies/system_cookie_store_util_unittest.mm",
"net/cookies/wk_cookie_util_unittest.mm",
"net/cookies/wk_http_system_cookie_store_unittest.mm",
......
......@@ -14,6 +14,8 @@ source_set("cookies") {
]
sources = [
"crw_wk_http_cookie_store.h",
"crw_wk_http_cookie_store.mm",
"system_cookie_store_util.mm",
"wk_cookie_util.h",
"wk_cookie_util.mm",
......
// Copyright (c) 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_NET_COOKIES_CRW_WK_HTTP_COOKIE_STORE_H_
#define IOS_WEB_NET_COOKIES_CRW_WK_HTTP_COOKIE_STORE_H_
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
// A WKHTTPCookieStore wrapper which caches the output of getAllCookies call to
// use on subsequent calls, while observing the core WKHTTPCookieStore to
// invalidate the cached copy once the store is updated.
// This class implements a fix for when WKHTTPCookieStore's getAllCookies method
// callback is not called bug, see crbug.com/885218 for details.
// All the methods of CRWWKHTTPCookieStore follow the same rules of the
// wrapped WKHTTPCookieStore.
@interface CRWWKHTTPCookieStore : NSObject
// CRWWKHTTPCookieStore will not retain the WKHTTPCookieStore instance, and it
// will be deleted with the owning WKWebSiteDataStore.
@property(nonatomic, weak) WKHTTPCookieStore* HTTPCookieStore;
// Fetches all stored cookies. If the store didn't change between calls, this
// method will return the cached result of the last call.
// TODO(crbug.com/946171): Remove caching when WKHTTPCookieStore performance bug
// is fixed.
- (void)getAllCookies:(void (^)(NSArray<NSHTTPCookie*>*))completionHandler;
// Sets |cookie| to the store, and invokes |completionHandler| after cookie is
// set.
- (void)setCookie:(NSHTTPCookie*)cookie
completionHandler:(void (^)(void))completionHandler;
// Deletes |cookie| from the store, and invokes |completionHandler| after cookie
// is deleted.
- (void)deleteCookie:(NSHTTPCookie*)cookie
completionHandler:(void (^)(void))completionHandler;
@end
#endif // IOS_WEB_NET_COOKIES_CRW_WK_HTTP_COOKIE_STORE_H_
// Copyright (c) 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/net/cookies/crw_wk_http_cookie_store.h"
#include "base/logging.h"
#include "ios/web/public/web_thread.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Prioritizes queued WKHTTPCookieStore completion handlers to run as soon as
// possible. This function is needed because some of WKHTTPCookieStore methods
// completion handlers are not called until there is a WKWebView on the view
// hierarchy.
void PrioritizeWKHTTPCookieStoreCallbacks() {
// TODO(crbug.com/885218): Currently this hack is needed to fix
// crbug.com/885218. Remove when the behavior of
// [WKHTTPCookieStore getAllCookies:] changes.
NSSet* data_types = [NSSet setWithObject:WKWebsiteDataTypeCookies];
[[WKWebsiteDataStore defaultDataStore]
fetchDataRecordsOfTypes:data_types
completionHandler:^(NSArray<WKWebsiteDataRecord*>* records){
}];
}
} // namespace
@interface CRWWKHTTPCookieStore () <WKHTTPCookieStoreObserver>
// The last getAllCookies output. Will always be set from the UI
// thread.
@property(nonatomic) NSArray<NSHTTPCookie*>* cachedCookies;
@end
@implementation CRWWKHTTPCookieStore
- (void)dealloc {
[_HTTPCookieStore removeObserver:self];
}
- (void)getAllCookies:(void (^)(NSArray<NSHTTPCookie*>*))completionHandler {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
NSArray<NSHTTPCookie*>* result = _HTTPCookieStore ? _cachedCookies : @[];
if (result) {
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(result);
});
} else {
__weak __typeof(self) weakSelf = self;
[_HTTPCookieStore getAllCookies:^(NSArray<NSHTTPCookie*>* cookies) {
weakSelf.cachedCookies = cookies;
completionHandler(cookies);
}];
PrioritizeWKHTTPCookieStoreCallbacks();
}
}
- (void)setCookie:(NSHTTPCookie*)cookie
completionHandler:(nullable void (^)(void))completionHandler {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
_cachedCookies = nil;
[_HTTPCookieStore setCookie:cookie completionHandler:completionHandler];
}
- (void)deleteCookie:(NSHTTPCookie*)cookie
completionHandler:(nullable void (^)(void))completionHandler {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
_cachedCookies = nil;
[_HTTPCookieStore deleteCookie:cookie completionHandler:completionHandler];
}
- (void)setHTTPCookieStore:(WKHTTPCookieStore*)newCookieStore {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
_cachedCookies = nil;
if (newCookieStore == _HTTPCookieStore)
return;
[_HTTPCookieStore removeObserver:self];
_HTTPCookieStore = newCookieStore;
[_HTTPCookieStore addObserver:self];
}
#pragma mark WKHTTPCookieStoreObserver method
- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore*)cookieStore {
DCHECK(_HTTPCookieStore == cookieStore);
_cachedCookies = nil;
}
@end
// 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/net/cookies/crw_wk_http_cookie_store.h"
#import <WebKit/WebKit.h>
#include "base/run_loop.h"
#import "base/test/ios/wait_util.h"
#include "ios/net/cookies/cookie_store_ios_test_util.h"
#include "ios/web/public/test/test_web_thread_bundle.h"
#include "ios/web/public/test/web_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.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::kWaitForCookiesTimeout;
class CRWWKHTTPCookieStoreTest : public PlatformTest {
public:
CRWWKHTTPCookieStoreTest()
: crw_cookie_store_([[CRWWKHTTPCookieStore alloc] init]) {
mock_http_cookie_store_ = OCMPartialMock(CreateDataStore().httpCookieStore);
crw_cookie_store_.HTTPCookieStore = mock_http_cookie_store_;
NSURL* test_cookie_url = [NSURL URLWithString:@"http://foo.google.com/bar"];
test_cookie_1_ = [NSHTTPCookie cookieWithProperties:@{
NSHTTPCookiePath : test_cookie_url.path,
NSHTTPCookieName : @"test1",
NSHTTPCookieValue : @"value1",
NSHTTPCookieDomain : test_cookie_url.host,
}];
test_cookie_2_ = [NSHTTPCookie cookieWithProperties:@{
NSHTTPCookiePath : test_cookie_url.path,
NSHTTPCookieName : @"test2",
NSHTTPCookieValue : @"value2",
NSHTTPCookieDomain : test_cookie_url.host,
}];
}
// Returns a new WKWebSiteDataStore.
WKWebsiteDataStore* CreateDataStore() {
WKWebsiteDataStore* data_store =
[WKWebsiteDataStore nonPersistentDataStore];
// This is needed to force WKWebSiteDataStore to be created, otherwise the
// cookie isn't set in some cases.
NSSet* data_types = [NSSet setWithObject:WKWebsiteDataTypeCookies];
[data_store
fetchDataRecordsOfTypes:data_types
completionHandler:^(NSArray<WKWebsiteDataRecord*>* records){
}];
return data_store;
}
// Adds |cookie| to the CRWWKHTTPCookieStore.
bool SetCookie(NSHTTPCookie* cookie) WARN_UNUSED_RESULT {
__block bool cookie_set = false;
[crw_cookie_store_ setCookie:cookie
completionHandler:^{
cookie_set = true;
}];
return WaitUntilConditionOrTimeout(kWaitForCookiesTimeout, ^bool {
return cookie_set;
});
}
// Deletes |cookie| from the CRWWKHTTPCookieStore.
bool DeleteCookie(NSHTTPCookie* cookie) WARN_UNUSED_RESULT {
__block bool cookie_deleted = false;
[crw_cookie_store_ deleteCookie:cookie
completionHandler:^{
cookie_deleted = true;
}];
return WaitUntilConditionOrTimeout(kWaitForCookiesTimeout, ^bool {
return cookie_deleted;
});
}
// Gets all cookies from CRWWKHTTPCookieStore and ensures that getAllCookies
// callback was called.
NSArray<NSHTTPCookie*>* GetCookies() WARN_UNUSED_RESULT {
__block NSArray<NSHTTPCookie*>* result_cookies = nil;
__block bool callback_called = false;
[crw_cookie_store_ getAllCookies:^(NSArray<NSHTTPCookie*>* cookies) {
callback_called = true;
result_cookies = cookies;
}];
bool success = WaitUntilConditionOrTimeout(kWaitForCookiesTimeout, ^bool {
return callback_called;
});
EXPECT_TRUE(success);
return result_cookies;
}
protected:
web::TestWebThreadBundle thread_bundle_;
CRWWKHTTPCookieStore* crw_cookie_store_;
id mock_http_cookie_store_ = nil;
NSHTTPCookie* test_cookie_1_ = nil;
NSHTTPCookie* test_cookie_2_ = nil;
};
// Tests that getting cookies are cached correctly for consecutive calls.
TEST_F(CRWWKHTTPCookieStoreTest, GetCookiesCachedCorrectly) {
EXPECT_TRUE(SetCookie(test_cookie_1_));
OCMExpect([mock_http_cookie_store_ getAllCookies:[OCMArg any]])
.andForwardToRealObject();
NSArray<NSHTTPCookie*>* result_1 = GetCookies();
EXPECT_EQ(1U, result_1.count);
// Internal getAllCookies shouldn't be called again.
[[mock_http_cookie_store_ reject] getAllCookies:[OCMArg any]];
NSArray<NSHTTPCookie*>* result_2 = GetCookies();
// Check that the same exact object is returned.
EXPECT_EQ(result_1, result_2);
EXPECT_NSEQ(result_1, result_2);
EXPECT_OCMOCK_VERIFY(mock_http_cookie_store_);
}
// Tests that |setCookie:| works correctly and invalidates the cache.
TEST_F(CRWWKHTTPCookieStoreTest, SetCookie) {
// Verify that internal cookie store setCookie method was called.
OCMExpect([mock_http_cookie_store_ setCookie:test_cookie_1_
completionHandler:[OCMArg any]])
.andForwardToRealObject();
EXPECT_TRUE(SetCookie(test_cookie_1_));
// internal getAllCookies should be called.
OCMExpect([mock_http_cookie_store_ getAllCookies:[OCMArg any]])
.andForwardToRealObject();
NSArray<NSHTTPCookie*>* result_1 = GetCookies();
// Verify that there is a cookie in the cookie store.
EXPECT_EQ(1U, result_1.count);
// Verify that internal cookie store setCookie method was called.
OCMExpect([mock_http_cookie_store_ setCookie:test_cookie_2_
completionHandler:[OCMArg any]])
.andForwardToRealObject();
EXPECT_TRUE(SetCookie(test_cookie_2_));
// Verify that cache was invalidated and internal getAllCookies is called.
OCMExpect([mock_http_cookie_store_ getAllCookies:[OCMArg any]])
.andForwardToRealObject();
NSArray<NSHTTPCookie*>* result_2 = GetCookies();
// Check that the cookies returned was changed and cache was invalidated.
EXPECT_NSNE(result_1, result_2);
EXPECT_OCMOCK_VERIFY(mock_http_cookie_store_);
}
// Tests that |deleteCookie:| works correctly and invalidates the cache.
TEST_F(CRWWKHTTPCookieStoreTest, DeleteCookie) {
EXPECT_TRUE(SetCookie(test_cookie_1_));
EXPECT_TRUE(SetCookie(test_cookie_2_));
NSArray<NSHTTPCookie*>* result_1 = GetCookies();
EXPECT_EQ(2U, result_1.count);
// Verify that internal cookie store deleteCookie method is called.
OCMExpect([mock_http_cookie_store_ deleteCookie:test_cookie_2_
completionHandler:[OCMArg any]])
.andForwardToRealObject();
EXPECT_TRUE(DeleteCookie(test_cookie_2_));
// Verify that cache was invalidated and internal getAllCookies is called.
OCMExpect([mock_http_cookie_store_ getAllCookies:[OCMArg any]])
.andForwardToRealObject();
NSArray<NSHTTPCookie*>* result_2 = GetCookies();
EXPECT_EQ(1U, result_2.count);
EXPECT_OCMOCK_VERIFY(mock_http_cookie_store_);
}
// Tests that chacing work correctly after changing the internal cookieStore.
TEST_F(CRWWKHTTPCookieStoreTest, ChangeCookieStore) {
EXPECT_TRUE(SetCookie(test_cookie_1_));
// Verify that cache was invalidated and internal getAllCookies is called.
OCMExpect([mock_http_cookie_store_ getAllCookies:[OCMArg any]])
.andForwardToRealObject();
NSArray<NSHTTPCookie*>* result_1 = GetCookies();
EXPECT_EQ(1U, result_1.count);
EXPECT_OCMOCK_VERIFY(mock_http_cookie_store_);
// Change the internal cookie store.
[mock_http_cookie_store_ stopMocking];
mock_http_cookie_store_ = OCMPartialMock(CreateDataStore().httpCookieStore);
crw_cookie_store_.HTTPCookieStore = mock_http_cookie_store_;
// Verify that internal getAllCookies is called.
OCMExpect([mock_http_cookie_store_ getAllCookies:[OCMArg any]])
.andForwardToRealObject();
NSArray<NSHTTPCookie*>* result_3 = GetCookies();
// There should be no cookies in the new cookie store.
EXPECT_EQ(0U, result_3.count);
EXPECT_TRUE(SetCookie(test_cookie_2_));
// Verify that cache was invalidated and internal getAllCookies is called.
OCMExpect([mock_http_cookie_store_ getAllCookies:[OCMArg any]])
.andForwardToRealObject();
NSArray<NSHTTPCookie*>* result_4 = GetCookies();
EXPECT_EQ(1U, result_4.count);
EXPECT_OCMOCK_VERIFY(mock_http_cookie_store_);
}
// Tests that if the internal cookie store is nil, getAllCookie will still run
// its callback.
TEST_F(CRWWKHTTPCookieStoreTest, NilCookieStore) {
[mock_http_cookie_store_ stopMocking];
crw_cookie_store_.HTTPCookieStore = nil;
// GetCookies should return empty array when there is no cookie store.
NSArray<NSHTTPCookie*>* result = GetCookies();
EXPECT_EQ(0U, result.count);
}
......@@ -12,6 +12,7 @@
#import "ios/web/net/cookies/wk_cookie_util.h"
#import "ios/web/net/cookies/wk_http_system_cookie_store.h"
#include "ios/web/public/browser_state.h"
#import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
......@@ -24,12 +25,10 @@ std::unique_ptr<net::SystemCookieStore> CreateSystemCookieStore(
if (base::FeatureList::IsEnabled(web::features::kWKHTTPSystemCookieStore)) {
// Using WKHTTPCookieStore guarantee that cookies are always in sync and
// allows SystemCookieStore to handle cookies for OffTheRecord browser.
WKHTTPCookieStore* wk_cookie_store =
web::WKCookieStoreForBrowserState(browser_state);
return std::make_unique<web::WKHTTPSystemCookieStore>(wk_cookie_store);
WKWebViewConfigurationProvider& config_provider =
WKWebViewConfigurationProvider::FromBrowserState(browser_state);
return std::make_unique<web::WKHTTPSystemCookieStore>(&config_provider);
}
// TODO(crbug.com/759229): Return a different CookieStore for OffTheRecord
// browser state.
return std::make_unique<net::NSHTTPSystemCookieStore>();
}
......
......@@ -9,15 +9,19 @@
#import <WebKit/WebKit.h>
#import "ios/net/cookies/system_cookie_store.h"
#import "ios/web/net/cookies/crw_wk_http_cookie_store.h"
#import "ios/web/web_state/ui/wk_web_view_configuration_provider_observer.h"
namespace web {
// This class is an implementation of SystemCookieStore, WKHTTPSystemCookieStore
// uses WKHTTPCookieStore as the underlying system cookie store.
class API_AVAILABLE(ios(11.0)) WKHTTPSystemCookieStore
: public net::SystemCookieStore {
: public net::SystemCookieStore,
public WKWebViewConfigurationProviderObserver {
public:
explicit WKHTTPSystemCookieStore(WKHTTPCookieStore* cookie_store);
explicit WKHTTPSystemCookieStore(
WKWebViewConfigurationProvider* config_provider);
~WKHTTPSystemCookieStore() override;
......@@ -43,16 +47,40 @@ class API_AVAILABLE(ios(11.0)) WKHTTPSystemCookieStore
NSHTTPCookieAcceptPolicy GetCookieAcceptPolicy() override;
private:
// Run |callback| on |cookies| after sorting them as per RFC6265.
static void RunSystemCookieCallbackForCookies(
// Gets cookies on UI Thread then processes the result and runs callback on IO
// thread. If |include_url| is not empty, only include cookies that match it.
void GetCookiesAsyncInternal(
const GURL& include_url,
net::SystemCookieStore::SystemCookieCallbackForCookies callback);
// WKWebViewConfigurationProviderObserver:
// Updates the internal WKHTTPCookieStore and its observer.
void DidCreateNewConfiguration(
WKWebViewConfigurationProvider* config_provider,
WKWebViewConfiguration* new_config) override;
// WKWebViewConfigurationProviderObserver:
// Stops observing |config_provider|.
void ConfigurationProviderDestroyed(
WKWebViewConfigurationProvider* config_provider) override;
// Filters |cookies| to match |include_url|, sorts based on RFC6265 using
// |weak_time_manager| and then runs |callback|.
// If |include_url| is empty then cookies are processed without filtering.
static void ProcessGetCookiesResultInIOThread(
net::SystemCookieStore::SystemCookieCallbackForCookies callback,
base::WeakPtr<net::CookieCreationTimeManager> weak_time_manager,
const GURL& include_url,
NSArray<NSHTTPCookie*>* cookies);
// cookie_store_ must be deleted in the UI thread, So by making it weak
// WKHTTPSystemCookieStore will not retain the WKHTTPCookieStore instance, and
// it will be deleted with the owning WKWebSiteDataStore.
__weak WKHTTPCookieStore* cookie_store_;
// Using CRWWKHTTPCookieStore instead of using WKHTTPCookieStore directly to
// work around several bugs on WKHTTPCookieStore.
CRWWKHTTPCookieStore* crw_cookie_store_ = nil;
// The WKWebViewConfigurationProvider object that is observed by this cookie
// store. If this cookie store doesn't observe any
// WKWebViewConfigurationProvider |config_provider_| will be nullptr.
WKWebViewConfigurationProvider* config_provider_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(WKHTTPSystemCookieStore);
};
......
......@@ -11,7 +11,10 @@
#import "base/test/ios/wait_util.h"
#include "ios/net/cookies/system_cookie_store_unittest_template.h"
#include "ios/web/public/test/fakes/test_browser_state.h"
#include "ios/web/public/test/scoped_testing_web_client.h"
#include "ios/web/public/test/test_web_thread_bundle.h"
#import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
#include "testing/gtest/include/gtest/gtest.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
......@@ -24,10 +27,13 @@ namespace net {
// WKHTTPSystemCookieStore.
class WKHTTPSystemCookieStoreTestDelegate {
public:
WKHTTPSystemCookieStoreTestDelegate()
: shared_store_(
[WKWebsiteDataStore nonPersistentDataStore].httpCookieStore),
store_(std::make_unique<web::WKHTTPSystemCookieStore>(shared_store_)) {}
WKHTTPSystemCookieStoreTestDelegate() {
web::WKWebViewConfigurationProvider& config_provider =
web::WKWebViewConfigurationProvider::FromBrowserState(&browser_state_);
shared_store_ = config_provider.GetWebViewConfiguration()
.websiteDataStore.httpCookieStore;
store_ = std::make_unique<web::WKHTTPSystemCookieStore>(&config_provider);
}
bool IsCookieSet(NSHTTPCookie* system_cookie, NSURL* url) {
// Verify that cookie is set in system storage.
......@@ -90,7 +96,8 @@ class WKHTTPSystemCookieStoreTestDelegate {
private:
web::TestWebThreadBundle web_thread_;
WKHTTPCookieStore* shared_store_;
web::TestBrowserState browser_state_;
WKHTTPCookieStore* shared_store_ = nil;
std::unique_ptr<web::WKHTTPSystemCookieStore> store_;
};
......
......@@ -12,6 +12,7 @@ source_set("fakes") {
"//ios/web/navigation:core",
"//ios/web/public:public",
"//ios/web/web_state/ui:crw_web_view_navigation_proxy",
"//ios/web/web_state/ui:wk_web_view_configuration_provider",
"//testing/gmock",
"//third_party/ocmock:ocmock",
]
......@@ -31,6 +32,8 @@ source_set("fakes") {
"crw_fake_wk_navigation_response.mm",
"fake_navigation_manager_delegate.h",
"fake_navigation_manager_delegate.mm",
"fake_wk_configuration_provider_observer.h",
"fake_wk_configuration_provider_observer.mm",
"mock_interstitial_delegate.h",
"mock_interstitial_delegate.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.
#ifndef IOS_WEB_TEST_FAKES_FAKE_WK_CONFIGURATION_PROVIDER_OBSERVER_H_
#define IOS_WEB_TEST_FAKES_FAKE_WK_CONFIGURATION_PROVIDER_OBSERVER_H_
#import "ios/web/web_state/ui/wk_web_view_configuration_provider_observer.h"
#import <WebKit/WebKit.h>
namespace web {
// Fake implementation of WKWebViewConfigurationProviderObserver.
class FakeWKConfigurationProviderObserver
: public WKWebViewConfigurationProviderObserver {
public:
explicit FakeWKConfigurationProviderObserver(
WKWebViewConfigurationProvider* config_provider);
// Returns the WKWebViewConfiguration object that was passed to
// DidCreateNewConfiguration method.
WKWebViewConfiguration* GetLastCreatedWKConfiguration();
bool IsProviderDestroyed() const;
void ResetLastCreatedWKConfig();
private:
// Sets the |last_created_wk_config| with |new_config|.
void DidCreateNewConfiguration(
WKWebViewConfigurationProvider* config_provider,
WKWebViewConfiguration* new_config) override;
void ConfigurationProviderDestroyed(
WKWebViewConfigurationProvider* config_provider) override;
// The last created configuration that was passed to
// DidCreateNewConfiguration.
WKWebViewConfiguration* last_created_wk_config_ = nil;
// True after ConfigurationProviderDestroyed is called.
bool is_provider_destroyed_ = false;
};
} // namespace web
#endif // IOS_WEB_TEST_FAKES_FAKE_WK_CONFIGURATION_PROVIDER_OBSERVER_H_
// 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/test/fakes/fake_wk_configuration_provider_observer.h"
#import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace web {
FakeWKConfigurationProviderObserver::FakeWKConfigurationProviderObserver(
WKWebViewConfigurationProvider* config_provider) {
config_provider->AddObserver(this);
}
WKWebViewConfiguration*
FakeWKConfigurationProviderObserver::GetLastCreatedWKConfiguration() {
return last_created_wk_config_;
}
void FakeWKConfigurationProviderObserver::ResetLastCreatedWKConfig() {
last_created_wk_config_ = nil;
}
void FakeWKConfigurationProviderObserver::DidCreateNewConfiguration(
WKWebViewConfigurationProvider* config_provider,
WKWebViewConfiguration* new_config) {
last_created_wk_config_ = new_config;
}
bool FakeWKConfigurationProviderObserver::IsProviderDestroyed() const {
return is_provider_destroyed_;
}
void FakeWKConfigurationProviderObserver::ConfigurationProviderDestroyed(
WKWebViewConfigurationProvider* config_provider) {
config_provider->RemoveObserver(this);
is_provider_destroyed_ = true;
}
} // namespace web
......@@ -141,6 +141,7 @@ source_set("wk_web_view_configuration_provider") {
sources = [
"wk_web_view_configuration_provider.h",
"wk_web_view_configuration_provider.mm",
"wk_web_view_configuration_provider_observer.h",
]
libs = [ "WebKit.framework" ]
......
......@@ -6,6 +6,7 @@
#define IOS_WEB_WEB_STATE_UI_WK_WEB_VIEW_CONFIGURATION_PROVIDER_H_
#include "base/macros.h"
#include "base/observer_list.h"
#include "base/supports_user_data.h"
@class CRWWebUISchemeHandler;
......@@ -15,6 +16,7 @@
namespace web {
class BrowserState;
class WKWebViewConfigurationProviderObserver;
// A provider class associated with a single web::BrowserState object. Manages
// the lifetime and performs setup of WKWebViewConfiguration and
......@@ -47,15 +49,24 @@ class WKWebViewConfigurationProvider : public base::SupportsUserData::Data {
// be enforced in debug builds).
void Purge();
// Adds |observer| to monitor changes to the ConfigurationProvider.
void AddObserver(WKWebViewConfigurationProviderObserver* observer);
// Stop |observer| from monitoring changes to the ConfigurationProvider.
void RemoveObserver(WKWebViewConfigurationProviderObserver* observer);
private:
explicit WKWebViewConfigurationProvider(BrowserState* browser_state);
WKWebViewConfigurationProvider() = delete;
CRWWebUISchemeHandler* scheme_handler_ = nil;
WKWebViewConfiguration* configuration_;
CRWWKScriptMessageRouter* router_;
BrowserState* browser_state_;
// A list of observers notified when WKWebViewConfiguration changes.
base::ObserverList<WKWebViewConfigurationProviderObserver, true>::Unchecked
observers_;
DISALLOW_COPY_AND_ASSIGN(WKWebViewConfigurationProvider);
};
......
......@@ -16,6 +16,7 @@
#include "ios/web/public/web_client.h"
#import "ios/web/web_state/js/page_script_util.h"
#import "ios/web/web_state/ui/crw_wk_script_message_router.h"
#import "ios/web/web_state/ui/wk_web_view_configuration_provider_observer.h"
#import "ios/web/webui/crw_web_ui_scheme_handler.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
......@@ -80,6 +81,8 @@ WKWebViewConfigurationProvider::WKWebViewConfigurationProvider(
: browser_state_(browser_state) {}
WKWebViewConfigurationProvider::~WKWebViewConfigurationProvider() {
for (auto& observer : observers_)
observer.ConfigurationProviderDestroyed(this);
}
WKWebViewConfiguration*
......@@ -126,6 +129,18 @@ WKWebViewConfigurationProvider::GetWebViewConfiguration() {
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){
}];
}
// This is a shallow copy to prevent callers from changing the internals of
......@@ -151,4 +166,14 @@ void WKWebViewConfigurationProvider::Purge() {
router_ = nil;
}
void WKWebViewConfigurationProvider::AddObserver(
WKWebViewConfigurationProviderObserver* observer) {
observers_.AddObserver(observer);
}
void WKWebViewConfigurationProvider::RemoveObserver(
WKWebViewConfigurationProviderObserver* observer) {
observers_.RemoveObserver(observer);
}
} // 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.
#ifndef IOS_WEB_WEB_STATE_UI_WK_WEB_VIEW_CONFIGURATION_PROVIDER_OBSERVER_H_
#define IOS_WEB_WEB_STATE_UI_WK_WEB_VIEW_CONFIGURATION_PROVIDER_OBSERVER_H_
#include "base/macros.h"
@class WKWebViewConfiguration;
namespace web {
class WKWebViewConfigurationProvider;
class WKWebViewConfigurationProviderObserver {
public:
// Called when the observed WKWebViewConfigurationProvider creates a new
// WKWebViewConfiguration.
virtual void DidCreateNewConfiguration(
WKWebViewConfigurationProvider* config_provider,
WKWebViewConfiguration* new_config) {}
// Called when the observed WKWebViewConfigurationProvider is being destroyed.
virtual void ConfigurationProviderDestroyed(
WKWebViewConfigurationProvider* config_provider) {}
virtual ~WKWebViewConfigurationProviderObserver() = default;
protected:
WKWebViewConfigurationProviderObserver() = default;
private:
DISALLOW_COPY_AND_ASSIGN(WKWebViewConfigurationProviderObserver);
};
} // namespace web
#endif // IOS_WEB_WEB_STATE_UI_WK_WEB_VIEW_CONFIGURATION_PROVIDER_OBSERVER_H_
......@@ -10,6 +10,7 @@
#include "ios/web/public/test/fakes/test_browser_state.h"
#include "ios/web/public/test/scoped_testing_web_client.h"
#import "ios/web/public/web_client.h"
#import "ios/web/test/fakes/fake_wk_configuration_provider_observer.h"
#import "ios/web/web_state/js/page_script_util.h"
#import "ios/web/web_state/ui/crw_wk_script_message_router.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -159,5 +160,27 @@ TEST_F(WKWebViewConfigurationProviderTest, UserScript) {
[[scripts[2] source] rangeOfString:late_all_frames_script].length);
}
// Tests that observers methods are correctly triggered when observing the
// WKWebViewConfigurationProvider
TEST_F(WKWebViewConfigurationProviderTest, Observers) {
std::unique_ptr<TestBrowserState> browser_state =
std::make_unique<TestBrowserState>();
WKWebViewConfigurationProvider* provider = &GetProvider(browser_state.get());
FakeWKConfigurationProviderObserver observer(provider);
EXPECT_FALSE(observer.GetLastCreatedWKConfiguration());
WKWebViewConfiguration* config = provider->GetWebViewConfiguration();
EXPECT_NSEQ(config.preferences,
observer.GetLastCreatedWKConfiguration().preferences);
observer.ResetLastCreatedWKConfig();
config = provider->GetWebViewConfiguration();
EXPECT_FALSE(observer.GetLastCreatedWKConfiguration());
// Test that ConfigurationProviderDestroyed is called.
ASSERT_FALSE(observer.IsProviderDestroyed());
browser_state.reset();
EXPECT_TRUE(observer.IsProviderDestroyed());
}
} // namespace
} // namespace web
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