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 = [
specific_include_rules = {
".*test\.mm": [
"+services/network/public/cpp",
"+services/network/test",
],
}
......@@ -41,6 +41,7 @@ source_set("web") {
":tab_helper_delegates",
"//base",
"//components/error_page/common",
"//components/image_fetcher/ios",
"//components/resources:components_resources_grit",
"//components/strings",
"//ios/chrome/app/strings:ios_strings_grit",
......@@ -119,6 +120,7 @@ source_set("unit_tests") {
"//ios/web/public/test/fakes",
"//ios/web/public/test/http_server",
"//net:test_support",
"//services/network:test_support",
"//testing/gmock",
"//testing/gtest",
"//third_party/ocmock",
......
......@@ -12,31 +12,28 @@
#include "ios/web/public/web_state/web_state_observer.h"
#import "ios/web/public/web_state/web_state_user_data.h"
// Gets the image data in binary string by calling injected JavaScript.
// Never keep a reference to this class, instead get it by
// ImageFetchTabHelper::FromWebState everytime.
// Gets the image data by JavaScript or
// image_fetcher::IOSImageDataFetcherWrapper. Always use this class by
// ImageFetchTabHelper::FromWebState on UI thread. All callbacks will also be
// invoked on UI thread.
class ImageFetchTabHelper : public web::WebStateObserver,
public web::WebStateUserData<ImageFetchTabHelper> {
public:
~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.
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:
// 1. Draw <img> to <canvas> and export its data;
// 2. Download the image by XMLHttpRequest and hopefully get responsed from
// cache.
// 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.
// Gets image data in binary format by following steps:
// 1. Call injected JavaScript to get the image data from web page;
// 2. If JavaScript fails or does not send a message back in 300ms, try
// downloading the image by image_fetcher::IOSImageDataFetcherWrapper.
void GetImageData(const GURL& url,
base::TimeDelta timeout,
ImageDataCallback&& callback);
const web::Referrer& referrer,
ImageDataCallback callback);
private:
protected:
friend class web::WebStateUserData<ImageFetchTabHelper>;
explicit ImageFetchTabHelper(web::WebState* web_state);
......@@ -46,17 +43,38 @@ class ImageFetchTabHelper : public web::WebStateObserver,
web::NavigationContext* navigation_context) 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.
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.
void OnImageDataTimeout(int call_id);
// Handler for calling GetImageDataByJs inside GetImageData.
void JsCallbackOfGetImageData(const GURL& url,
const web::Referrer& referrer,
ImageDataCallback callback,
const std::string* data);
// WebState this tab helper is attached to.
web::WebState* web_state_ = nullptr;
// 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
// call on |GetImageData| will increment |call_id_| by 1 and pass it as ID
......
......@@ -8,8 +8,12 @@
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.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"
#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)
#error "This file requires ARC support."
......@@ -20,6 +24,35 @@ DEFINE_WEB_STATE_USER_DATA_KEY(ImageFetchTabHelper);
namespace {
// Command prefix for injected JavaScript.
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)
......@@ -32,7 +65,7 @@ ImageFetchTabHelper::ImageFetchTabHelper(web::WebState* web_state)
[](base::WeakPtr<ImageFetchTabHelper> ptr,
const base::DictionaryValue& message, const GURL& page_url,
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()),
kCommandPrefix);
......@@ -46,29 +79,59 @@ void ImageFetchTabHelper::DidStartNavigation(
if (navigation_context->IsSameDocument()) {
return;
}
for (auto&& pair : callbacks_)
for (auto&& pair : js_callbacks_)
std::move(pair.second).Run(nullptr);
callbacks_.clear();
js_callbacks_.clear();
}
void ImageFetchTabHelper::WebStateDestroyed(web::WebState* web_state) {
web_state->RemoveScriptCommandCallback(kCommandPrefix);
for (auto&& pair : callbacks_)
for (auto&& pair : js_callbacks_)
std::move(pair.second).Run(nullptr);
web_state->RemoveObserver(this);
web_state_ = nullptr;
}
void ImageFetchTabHelper::GetImageData(const GURL& url,
base::TimeDelta timeout,
ImageDataCallback&& callback) {
const web::Referrer& referrer,
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_;
DCHECK_EQ(callbacks_.count(call_id_), 0UL);
callbacks_.insert({call_id_, std::move(callback)});
DCHECK_EQ(js_callbacks_.count(call_id_), 0UL);
js_callbacks_.insert({call_id_, std::move(callback)});
web::WebThread::PostDelayedTask(
web::WebThread::UI, FROM_HERE,
base::BindRepeating(&ImageFetchTabHelper::OnImageDataTimeout,
base::BindRepeating(&ImageFetchTabHelper::OnJsTimeout,
weak_ptr_factory_.GetWeakPtr(), call_id_),
timeout);
......@@ -89,18 +152,17 @@ void ImageFetchTabHelper::GetImageData(const GURL& url,
// For failure:
// {'command': 'image.getImageData',
// 'id': id_sent_to_gCrWeb_image_getImageData}
bool ImageFetchTabHelper::OnImageDataReceived(
const base::DictionaryValue& message) {
bool ImageFetchTabHelper::OnJsMessage(const base::DictionaryValue& message) {
const base::Value* id_key = message.FindKey("id");
if (!id_key || !id_key->is_double()) {
return false;
}
int id_value = static_cast<int>(id_key->GetDouble());
if (!callbacks_.count(id_value)) {
if (!js_callbacks_.count(id_value)) {
return true;
}
ImageDataCallback callback = std::move(callbacks_[id_value]);
callbacks_.erase(id_value);
JsCallback callback = std::move(js_callbacks_[id_value]);
js_callbacks_.erase(id_value);
const base::Value* data = message.FindKey("data");
std::string decoded_data;
......@@ -113,10 +175,10 @@ bool ImageFetchTabHelper::OnImageDataReceived(
return true;
}
void ImageFetchTabHelper::OnImageDataTimeout(int call_id) {
if (callbacks_.count(call_id)) {
ImageDataCallback callback = std::move(callbacks_[call_id]);
callbacks_.erase(call_id);
void ImageFetchTabHelper::OnJsTimeout(int call_id) {
if (js_callbacks_.count(call_id)) {
JsCallback callback = std::move(js_callbacks_[call_id]);
js_callbacks_.erase(call_id);
std::move(callback).Run(nullptr);
}
}
......@@ -73,7 +73,8 @@ class BrowserState : public base::SupportsUserData {
network::mojom::ProxyResolvingSocketFactoryRequest request);
// 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
// if |supports_user_data| is not a BrowserState.
......
......@@ -67,4 +67,16 @@ void TestBrowserState::SetOffTheRecord(bool 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
......@@ -7,6 +7,7 @@
#include "base/memory/ref_counted.h"
#include "ios/web/public/browser_state.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace web {
class TestBrowserState : public BrowserState {
......@@ -18,13 +19,24 @@ class TestBrowserState : public BrowserState {
bool IsOffTheRecord() const override;
base::FilePath GetStatePath() const 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.
void SetOffTheRecord(bool flag);
private:
scoped_refptr<net::URLRequestContextGetter> request_context_;
bool is_off_the_record_;
// A SharedURLLoaderFactory for test.
scoped_refptr<network::SharedURLLoaderFactory>
test_shared_url_loader_factory_;
};
} // namespace web
......
......@@ -40,6 +40,10 @@ class WebTest : public PlatformTest {
// fixture will fail if a render process crashes.
void SetIgnoreRenderProcessCrashesDuringTesting(bool allow);
// Sets a SharedURLLoaderFactory for |browser_state_|.
void SetSharedURLLoaderFactory(
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory);
private:
// The WebClient used in tests.
ScopedTestingWebClient web_client_;
......
......@@ -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
......@@ -23,8 +23,11 @@ class WebState;
class WebTestWithWebState : public WebTest,
public base::MessageLoop::TaskObserver {
protected:
WebTestWithWebState();
explicit WebTestWithWebState(std::unique_ptr<web::WebClient> web_client);
explicit WebTestWithWebState(
TestWebThreadBundle::Options = TestWebThreadBundle::Options::DEFAULT);
WebTestWithWebState(
std::unique_ptr<web::WebClient> web_client,
TestWebThreadBundle::Options = TestWebThreadBundle::Options::DEFAULT);
~WebTestWithWebState() override;
// WebTest overrides.
......
......@@ -36,11 +36,13 @@ CRWWebController* GetWebController(web::WebState* web_state) {
namespace web {
WebTestWithWebState::WebTestWithWebState() {}
WebTestWithWebState::WebTestWithWebState(TestWebThreadBundle::Options options)
: WebTest(options) {}
WebTestWithWebState::WebTestWithWebState(
std::unique_ptr<web::WebClient> web_client)
: WebTest(std::move(web_client)) {}
std::unique_ptr<web::WebClient> web_client,
TestWebThreadBundle::Options options)
: WebTest(std::move(web_client), options) {}
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