Commit 378627db authored by Yi Su's avatar Yi Su Committed by Commit Bot

Add image_fetcher::IOSImageDataFetcherWrapper to ImageFetchTabHelper

ImageFetchTabHelper will be able to get image data by both JavaScript and
image_fetcher::IOSImageDataFetcherWrapper. The image_fetcher ivar of BVC
can be removed once this is completed.

Bug: 163201
Cq-Include-Trybots: luci.chromium.try:ios-simulator-full-configs;master.tryserver.chromium.mac:ios-simulator-cronet
Change-Id: Ie5bb9279c5fb19498d3cde4c37cdf8944baa9e98
Reviewed-on: https://chromium-review.googlesource.com/1181571Reviewed-by: default avatarMatt Menke <mmenke@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Commit-Queue: Yi Su <mrsuyi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#586032}
parent b3c874a3
...@@ -21,5 +21,6 @@ include_rules = [ ...@@ -21,5 +21,6 @@ include_rules = [
specific_include_rules = { specific_include_rules = {
".*test\.mm": [ ".*test\.mm": [
"+services/network/public/cpp", "+services/network/public/cpp",
"+services/network/test",
], ],
} }
...@@ -41,6 +41,7 @@ source_set("web") { ...@@ -41,6 +41,7 @@ source_set("web") {
":tab_helper_delegates", ":tab_helper_delegates",
"//base", "//base",
"//components/error_page/common", "//components/error_page/common",
"//components/image_fetcher/ios",
"//components/resources:components_resources_grit", "//components/resources:components_resources_grit",
"//components/strings", "//components/strings",
"//ios/chrome/app/strings:ios_strings_grit", "//ios/chrome/app/strings:ios_strings_grit",
...@@ -119,6 +120,7 @@ source_set("unit_tests") { ...@@ -119,6 +120,7 @@ source_set("unit_tests") {
"//ios/web/public/test/fakes", "//ios/web/public/test/fakes",
"//ios/web/public/test/http_server", "//ios/web/public/test/http_server",
"//net:test_support", "//net:test_support",
"//services/network:test_support",
"//testing/gmock", "//testing/gmock",
"//testing/gtest", "//testing/gtest",
"//third_party/ocmock", "//third_party/ocmock",
......
...@@ -12,31 +12,28 @@ ...@@ -12,31 +12,28 @@
#include "ios/web/public/web_state/web_state_observer.h" #include "ios/web/public/web_state/web_state_observer.h"
#import "ios/web/public/web_state/web_state_user_data.h" #import "ios/web/public/web_state/web_state_user_data.h"
// Gets the image data in binary string by calling injected JavaScript. // Gets the image data by JavaScript or
// Never keep a reference to this class, instead get it by // image_fetcher::IOSImageDataFetcherWrapper. Always use this class by
// ImageFetchTabHelper::FromWebState everytime. // ImageFetchTabHelper::FromWebState on UI thread. All callbacks will also be
// invoked on UI thread.
class ImageFetchTabHelper : public web::WebStateObserver, class ImageFetchTabHelper : public web::WebStateObserver,
public web::WebStateUserData<ImageFetchTabHelper> { public web::WebStateUserData<ImageFetchTabHelper> {
public: public:
~ImageFetchTabHelper() override; ~ImageFetchTabHelper() override;
// Callback for GetImageData. |data| will be in binary format, or nullptr if // Callback for GetImageData. |data| will be in binary format, or nil if
// GetImageData failed. // GetImageData failed.
typedef base::OnceCallback<void(const std::string* data)> ImageDataCallback; typedef void (^ImageDataCallback)(NSData* data);
// Gets image data as binary string by trying 2 methods in following order: // Gets image data in binary format by following steps:
// 1. Draw <img> to <canvas> and export its data; // 1. Call injected JavaScript to get the image data from web page;
// 2. Download the image by XMLHttpRequest and hopefully get responsed from // 2. If JavaScript fails or does not send a message back in 300ms, try
// cache. // downloading the image by image_fetcher::IOSImageDataFetcherWrapper.
// This method should only be called from UI thread, and |url| should be equal
// to the resolved "src" attribute of <img>, otherwise the method 1 would
// fail. |callback| will be called on UI thread. If the JavaScript does not
// response after |timeout|, the |callback| will be invoked with nullptr.
void GetImageData(const GURL& url, void GetImageData(const GURL& url,
base::TimeDelta timeout, const web::Referrer& referrer,
ImageDataCallback&& callback); ImageDataCallback callback);
private: protected:
friend class web::WebStateUserData<ImageFetchTabHelper>; friend class web::WebStateUserData<ImageFetchTabHelper>;
explicit ImageFetchTabHelper(web::WebState* web_state); explicit ImageFetchTabHelper(web::WebState* web_state);
...@@ -46,17 +43,38 @@ class ImageFetchTabHelper : public web::WebStateObserver, ...@@ -46,17 +43,38 @@ class ImageFetchTabHelper : public web::WebStateObserver,
web::NavigationContext* navigation_context) override; web::NavigationContext* navigation_context) override;
void WebStateDestroyed(web::WebState* web_state) override; void WebStateDestroyed(web::WebState* web_state) override;
// Callback for GetImageDataByJs. |data| will be in binary format, or nullptr
// if GetImageDataByJs failed.
typedef base::OnceCallback<void(const std::string* data)> JsCallback;
// Gets image data in binary format by trying 2 JavaScript methods in order:
// 1. Draw <img> to <canvas> and export its data;
// 2. Download the image by XMLHttpRequest and hopefully get responded from
// cache.
// |url| should be equal to the resolved "src" attribute of <img>, otherwise
// the method 1 would fail. If the JavaScript does not respond after
// |timeout|, the |callback| will be invoked with nullptr.
void GetImageDataByJs(const GURL& url,
base::TimeDelta timeout,
JsCallback&& callback);
// Handler for messages sent back from injected JavaScript. // Handler for messages sent back from injected JavaScript.
bool OnImageDataReceived(const base::DictionaryValue& message); bool OnJsMessage(const base::DictionaryValue& message);
// Handler for timeout on GetImageDataByJs.
void OnJsTimeout(int call_id);
// Handler for timeout on GetImageData. // Handler for calling GetImageDataByJs inside GetImageData.
void OnImageDataTimeout(int call_id); void JsCallbackOfGetImageData(const GURL& url,
const web::Referrer& referrer,
ImageDataCallback callback,
const std::string* data);
// WebState this tab helper is attached to. // WebState this tab helper is attached to.
web::WebState* web_state_ = nullptr; web::WebState* web_state_ = nullptr;
// Store callbacks for GetImageData, with url as key. // Store callbacks for GetImageData, with url as key.
std::unordered_map<int, ImageDataCallback> callbacks_; std::unordered_map<int, JsCallback> js_callbacks_;
// |GetImageData| uses this counter as ID to match calls with callbacks. Each // |GetImageData| uses this counter as ID to match calls with callbacks. Each
// call on |GetImageData| will increment |call_id_| by 1 and pass it as ID // call on |GetImageData| will increment |call_id_| by 1 and pass it as ID
......
...@@ -8,8 +8,12 @@ ...@@ -8,8 +8,12 @@
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/values.h" #include "base/values.h"
#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
#include "ios/web/public/browser_state.h"
#include "ios/web/public/referrer_util.h"
#import "ios/web/public/web_state/navigation_context.h" #import "ios/web/public/web_state/navigation_context.h"
#include "ios/web/public/web_thread.h" #include "ios/web/public/web_thread.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
...@@ -20,6 +24,35 @@ DEFINE_WEB_STATE_USER_DATA_KEY(ImageFetchTabHelper); ...@@ -20,6 +24,35 @@ DEFINE_WEB_STATE_USER_DATA_KEY(ImageFetchTabHelper);
namespace { namespace {
// Command prefix for injected JavaScript. // Command prefix for injected JavaScript.
const char kCommandPrefix[] = "imageFetch"; const char kCommandPrefix[] = "imageFetch";
// Key for image_fetcher
const char kImageFetcherKeyName[] = "0";
// Wrapper class for image_fetcher::IOSImageDataFetcherWrapper. ImageFetcher is
// attached to web::BrowserState instead of web::WebState, because if a user
// closes the tab immediately after Copy/Save image, the web::WebState will be
// destroyed thus fail the download.
class ImageFetcher : public image_fetcher::IOSImageDataFetcherWrapper,
public base::SupportsUserData::Data {
public:
~ImageFetcher() override = default;
ImageFetcher(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: image_fetcher::IOSImageDataFetcherWrapper(url_loader_factory) {}
static ImageFetcher* FromBrowserState(web::BrowserState* browser_state) {
if (!browser_state->GetUserData(&kImageFetcherKeyName)) {
browser_state->SetUserData(
&kImageFetcherKeyName,
std::make_unique<ImageFetcher>(
browser_state->GetSharedURLLoaderFactory()));
}
return static_cast<ImageFetcher*>(
browser_state->GetUserData(&kImageFetcherKeyName));
}
DISALLOW_COPY_AND_ASSIGN(ImageFetcher);
};
} }
ImageFetchTabHelper::ImageFetchTabHelper(web::WebState* web_state) ImageFetchTabHelper::ImageFetchTabHelper(web::WebState* web_state)
...@@ -32,7 +65,7 @@ ImageFetchTabHelper::ImageFetchTabHelper(web::WebState* web_state) ...@@ -32,7 +65,7 @@ ImageFetchTabHelper::ImageFetchTabHelper(web::WebState* web_state)
[](base::WeakPtr<ImageFetchTabHelper> ptr, [](base::WeakPtr<ImageFetchTabHelper> ptr,
const base::DictionaryValue& message, const GURL& page_url, const base::DictionaryValue& message, const GURL& page_url,
bool has_user_gesture, bool form_in_main_frame) { bool has_user_gesture, bool form_in_main_frame) {
return ptr ? ptr->OnImageDataReceived(message) : true; return ptr ? ptr->OnJsMessage(message) : true;
}, },
weak_ptr_factory_.GetWeakPtr()), weak_ptr_factory_.GetWeakPtr()),
kCommandPrefix); kCommandPrefix);
...@@ -46,29 +79,59 @@ void ImageFetchTabHelper::DidStartNavigation( ...@@ -46,29 +79,59 @@ void ImageFetchTabHelper::DidStartNavigation(
if (navigation_context->IsSameDocument()) { if (navigation_context->IsSameDocument()) {
return; return;
} }
for (auto&& pair : callbacks_) for (auto&& pair : js_callbacks_)
std::move(pair.second).Run(nullptr); std::move(pair.second).Run(nullptr);
callbacks_.clear(); js_callbacks_.clear();
} }
void ImageFetchTabHelper::WebStateDestroyed(web::WebState* web_state) { void ImageFetchTabHelper::WebStateDestroyed(web::WebState* web_state) {
web_state->RemoveScriptCommandCallback(kCommandPrefix); web_state->RemoveScriptCommandCallback(kCommandPrefix);
for (auto&& pair : callbacks_) for (auto&& pair : js_callbacks_)
std::move(pair.second).Run(nullptr); std::move(pair.second).Run(nullptr);
web_state->RemoveObserver(this); web_state->RemoveObserver(this);
web_state_ = nullptr; web_state_ = nullptr;
} }
void ImageFetchTabHelper::GetImageData(const GURL& url, void ImageFetchTabHelper::GetImageData(const GURL& url,
base::TimeDelta timeout, const web::Referrer& referrer,
ImageDataCallback&& callback) { ImageDataCallback callback) {
// |this| is captured into the callback of GetImageDataByJs, which will always
// be invoked before the |this| is destroyed, so it's safe.
GetImageDataByJs(
url, base::TimeDelta::FromMilliseconds(300),
base::BindOnce(&ImageFetchTabHelper::JsCallbackOfGetImageData,
base::Unretained(this), url, referrer, callback));
}
void ImageFetchTabHelper::JsCallbackOfGetImageData(
const GURL& url,
const web::Referrer& referrer,
ImageDataCallback callback,
const std::string* data) {
if (data) {
callback([NSData dataWithBytes:data->c_str() length:data->size()]);
return;
}
ImageFetcher::FromBrowserState(web_state_->GetBrowserState())
->FetchImageDataWebpDecoded(
url,
^(NSData* data, const image_fetcher::RequestMetadata& metadata) {
callback(data);
},
web::ReferrerHeaderValueForNavigation(url, referrer),
web::PolicyForNavigation(url, referrer), /*send_cookies=*/true);
}
void ImageFetchTabHelper::GetImageDataByJs(const GURL& url,
base::TimeDelta timeout,
JsCallback&& callback) {
++call_id_; ++call_id_;
DCHECK_EQ(callbacks_.count(call_id_), 0UL); DCHECK_EQ(js_callbacks_.count(call_id_), 0UL);
callbacks_.insert({call_id_, std::move(callback)}); js_callbacks_.insert({call_id_, std::move(callback)});
web::WebThread::PostDelayedTask( web::WebThread::PostDelayedTask(
web::WebThread::UI, FROM_HERE, web::WebThread::UI, FROM_HERE,
base::BindRepeating(&ImageFetchTabHelper::OnImageDataTimeout, base::BindRepeating(&ImageFetchTabHelper::OnJsTimeout,
weak_ptr_factory_.GetWeakPtr(), call_id_), weak_ptr_factory_.GetWeakPtr(), call_id_),
timeout); timeout);
...@@ -89,18 +152,17 @@ void ImageFetchTabHelper::GetImageData(const GURL& url, ...@@ -89,18 +152,17 @@ void ImageFetchTabHelper::GetImageData(const GURL& url,
// For failure: // For failure:
// {'command': 'image.getImageData', // {'command': 'image.getImageData',
// 'id': id_sent_to_gCrWeb_image_getImageData} // 'id': id_sent_to_gCrWeb_image_getImageData}
bool ImageFetchTabHelper::OnImageDataReceived( bool ImageFetchTabHelper::OnJsMessage(const base::DictionaryValue& message) {
const base::DictionaryValue& message) {
const base::Value* id_key = message.FindKey("id"); const base::Value* id_key = message.FindKey("id");
if (!id_key || !id_key->is_double()) { if (!id_key || !id_key->is_double()) {
return false; return false;
} }
int id_value = static_cast<int>(id_key->GetDouble()); int id_value = static_cast<int>(id_key->GetDouble());
if (!callbacks_.count(id_value)) { if (!js_callbacks_.count(id_value)) {
return true; return true;
} }
ImageDataCallback callback = std::move(callbacks_[id_value]); JsCallback callback = std::move(js_callbacks_[id_value]);
callbacks_.erase(id_value); js_callbacks_.erase(id_value);
const base::Value* data = message.FindKey("data"); const base::Value* data = message.FindKey("data");
std::string decoded_data; std::string decoded_data;
...@@ -113,10 +175,10 @@ bool ImageFetchTabHelper::OnImageDataReceived( ...@@ -113,10 +175,10 @@ bool ImageFetchTabHelper::OnImageDataReceived(
return true; return true;
} }
void ImageFetchTabHelper::OnImageDataTimeout(int call_id) { void ImageFetchTabHelper::OnJsTimeout(int call_id) {
if (callbacks_.count(call_id)) { if (js_callbacks_.count(call_id)) {
ImageDataCallback callback = std::move(callbacks_[call_id]); JsCallback callback = std::move(js_callbacks_[call_id]);
callbacks_.erase(call_id); js_callbacks_.erase(call_id);
std::move(callback).Run(nullptr); std::move(callback).Run(nullptr);
} }
} }
...@@ -73,7 +73,8 @@ class BrowserState : public base::SupportsUserData { ...@@ -73,7 +73,8 @@ class BrowserState : public base::SupportsUserData {
network::mojom::ProxyResolvingSocketFactoryRequest request); network::mojom::ProxyResolvingSocketFactoryRequest request);
// Like URLLoaderFactory, but wrapped inside SharedURLLoaderFactory // Like URLLoaderFactory, but wrapped inside SharedURLLoaderFactory
scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory(); virtual scoped_refptr<network::SharedURLLoaderFactory>
GetSharedURLLoaderFactory();
// Safely cast a base::SupportsUserData to a BrowserState. Returns nullptr // Safely cast a base::SupportsUserData to a BrowserState. Returns nullptr
// if |supports_user_data| is not a BrowserState. // if |supports_user_data| is not a BrowserState.
......
...@@ -67,4 +67,16 @@ void TestBrowserState::SetOffTheRecord(bool flag) { ...@@ -67,4 +67,16 @@ void TestBrowserState::SetOffTheRecord(bool flag) {
is_off_the_record_ = flag; is_off_the_record_ = flag;
} }
scoped_refptr<network::SharedURLLoaderFactory>
TestBrowserState::GetSharedURLLoaderFactory() {
return test_shared_url_loader_factory_
? test_shared_url_loader_factory_
: BrowserState::GetSharedURLLoaderFactory();
}
void TestBrowserState::SetSharedURLLoaderFactory(
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory) {
test_shared_url_loader_factory_ = std::move(shared_url_loader_factory);
}
} // namespace web } // namespace web
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "ios/web/public/browser_state.h" #include "ios/web/public/browser_state.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace web { namespace web {
class TestBrowserState : public BrowserState { class TestBrowserState : public BrowserState {
...@@ -18,13 +19,24 @@ class TestBrowserState : public BrowserState { ...@@ -18,13 +19,24 @@ class TestBrowserState : public BrowserState {
bool IsOffTheRecord() const override; bool IsOffTheRecord() const override;
base::FilePath GetStatePath() const override; base::FilePath GetStatePath() const override;
net::URLRequestContextGetter* GetRequestContext() override; net::URLRequestContextGetter* GetRequestContext() override;
scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory()
override;
// Sets a SharedURLLoaderFactory for test.
void SetSharedURLLoaderFactory(
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory);
// Makes |IsOffTheRecord| return the given flag value. // Makes |IsOffTheRecord| return the given flag value.
void SetOffTheRecord(bool flag); void SetOffTheRecord(bool flag);
private: private:
scoped_refptr<net::URLRequestContextGetter> request_context_; scoped_refptr<net::URLRequestContextGetter> request_context_;
bool is_off_the_record_; bool is_off_the_record_;
// A SharedURLLoaderFactory for test.
scoped_refptr<network::SharedURLLoaderFactory>
test_shared_url_loader_factory_;
}; };
} // namespace web } // namespace web
......
...@@ -40,6 +40,10 @@ class WebTest : public PlatformTest { ...@@ -40,6 +40,10 @@ class WebTest : public PlatformTest {
// fixture will fail if a render process crashes. // fixture will fail if a render process crashes.
void SetIgnoreRenderProcessCrashesDuringTesting(bool allow); void SetIgnoreRenderProcessCrashesDuringTesting(bool allow);
// Sets a SharedURLLoaderFactory for |browser_state_|.
void SetSharedURLLoaderFactory(
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory);
private: private:
// The WebClient used in tests. // The WebClient used in tests.
ScopedTestingWebClient web_client_; ScopedTestingWebClient web_client_;
......
...@@ -51,4 +51,10 @@ void WebTest::SetIgnoreRenderProcessCrashesDuringTesting(bool allow) { ...@@ -51,4 +51,10 @@ void WebTest::SetIgnoreRenderProcessCrashesDuringTesting(bool allow) {
} }
} }
void WebTest::SetSharedURLLoaderFactory(
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory) {
browser_state_.SetSharedURLLoaderFactory(
std::move(shared_url_loader_factory));
}
} // namespace web } // namespace web
...@@ -23,8 +23,11 @@ class WebState; ...@@ -23,8 +23,11 @@ class WebState;
class WebTestWithWebState : public WebTest, class WebTestWithWebState : public WebTest,
public base::MessageLoop::TaskObserver { public base::MessageLoop::TaskObserver {
protected: protected:
WebTestWithWebState(); explicit WebTestWithWebState(
explicit WebTestWithWebState(std::unique_ptr<web::WebClient> web_client); TestWebThreadBundle::Options = TestWebThreadBundle::Options::DEFAULT);
WebTestWithWebState(
std::unique_ptr<web::WebClient> web_client,
TestWebThreadBundle::Options = TestWebThreadBundle::Options::DEFAULT);
~WebTestWithWebState() override; ~WebTestWithWebState() override;
// WebTest overrides. // WebTest overrides.
......
...@@ -36,11 +36,13 @@ CRWWebController* GetWebController(web::WebState* web_state) { ...@@ -36,11 +36,13 @@ CRWWebController* GetWebController(web::WebState* web_state) {
namespace web { namespace web {
WebTestWithWebState::WebTestWithWebState() {} WebTestWithWebState::WebTestWithWebState(TestWebThreadBundle::Options options)
: WebTest(options) {}
WebTestWithWebState::WebTestWithWebState( WebTestWithWebState::WebTestWithWebState(
std::unique_ptr<web::WebClient> web_client) std::unique_ptr<web::WebClient> web_client,
: WebTest(std::move(web_client)) {} TestWebThreadBundle::Options options)
: WebTest(std::move(web_client), options) {}
WebTestWithWebState::~WebTestWithWebState() {} WebTestWithWebState::~WebTestWithWebState() {}
......
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