1// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ios/chrome/browser/web/image_fetch_tab_helper.h"
6
7#include "base/strings/sys_string_conversions.h"
8#import "base/test/ios/wait_util.h"
9#include "base/test/metrics/histogram_tester.h"
10#import "ios/web/public/test/web_test_with_web_state.h"
11#include "net/http/http_util.h"
12#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
13#include "services/network/public/mojom/url_response_head.mojom.h"
14#include "services/network/test/test_url_loader_factory.h"
15#include "testing/gtest/include/gtest/gtest.h"
16#include "testing/gtest_mac.h"
17
18#if !defined(__has_feature) || !__has_feature(objc_arc)
19#error "This file requires ARC support."
20#endif
21
22using base::test::ios::WaitUntilConditionOrTimeout;
23
24namespace {
25// Timeout for calling on ImageFetchTabHelper::GetImageData.
26const NSTimeInterval kWaitForGetImageDataTimeout = 1.0;
27
28const char kImageUrl[] = "http://www.chrooooooooooome.com/";
29const char kImageData[] = "abc";
30}
31
32// Test fixture for ImageFetchTabHelper class.
33class ImageFetchTabHelperTest : public web::WebTestWithWebState {
34 protected:
35  ImageFetchTabHelperTest()
36      : web::WebTestWithWebState(
37            web::WebTaskEnvironment::Options::IO_MAINLOOP) {}
38
39  void SetUp() override {
40    WebTestWithWebState::SetUp();
41    ASSERT_TRUE(LoadHtml("<html></html>"));
42    ImageFetchTabHelper::CreateForWebState(web_state());
43    SetUpTestSharedURLLoaderFactory();
44  }
45
46  // Sets up the network::TestURLLoaderFactory to handle download request.
47  void SetUpTestSharedURLLoaderFactory() {
48    SetSharedURLLoaderFactory(
49        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
50            &test_url_loader_factory_));
51
52    network::mojom::URLResponseHeadPtr head =
53        network::mojom::URLResponseHead::New();
54    std::string raw_header =
55        "HTTP/1.1 200 OK\n"
56        "Content-type: image/png\n\n";
57    head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
58        net::HttpUtil::AssembleRawHeaders(raw_header));
59    head->mime_type = "image/png";
60    network::URLLoaderCompletionStatus status;
61    status.decoded_body_length = strlen(kImageData);
62    test_url_loader_factory_.AddResponse(GURL(kImageUrl), std::move(head),
63                                         kImageData, status);
64  }
65
66  ImageFetchTabHelper* image_fetch_tab_helper() {
67    return ImageFetchTabHelper::FromWebState(web_state());
68  }
69
70  // Returns the expected image data in NSData*.
71  NSData* GetExpectedData() const {
72    return [base::SysUTF8ToNSString(kImageData)
73        dataUsingEncoding:NSUTF8StringEncoding];
74  }
75
76  network::TestURLLoaderFactory test_url_loader_factory_;
77  base::HistogramTester histogram_tester_;
78
79  DISALLOW_COPY_AND_ASSIGN(ImageFetchTabHelperTest);
80};
81
82// Tests that ImageFetchTabHelper::GetImageData can get image data from Js.
83TEST_F(ImageFetchTabHelperTest, GetImageDataWithJsSucceedFromCanvas) {
84  // Inject fake |__gCrWeb.imageFetch.getImageData| that returns |kImageData|
85  // in base64 format.
86  id script_result = ExecuteJavaScript([NSString
87      stringWithFormat:
88          @"__gCrWeb.imageFetch = {}; __gCrWeb.imageFetch.getImageData = "
89           "function(id, url) { __gCrWeb.message.invokeOnHost({'command': "
90           "'imageFetch.getImageData', 'id': id, 'data': btoa('%s'), "
91           "'from':'canvas'}); }; true;",
92          kImageData]);
93  ASSERT_NSEQ(@YES, script_result);
94
95  __block bool callback_invoked = false;
96  image_fetch_tab_helper()->GetImageData(GURL(kImageUrl), web::Referrer(),
97                                         ^(NSData* data) {
98                                           ASSERT_TRUE(data);
99                                           EXPECT_NSEQ(GetExpectedData(), data);
100                                           callback_invoked = true;
101                                         });
102
103  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForGetImageDataTimeout, ^{
104    base::RunLoop().RunUntilIdle();
105    return callback_invoked;
106  }));
107  histogram_tester_.ExpectUniqueSample(
108      kUmaGetImageDataByJsResult,
109      ContextMenuGetImageDataByJsResult::kCanvasSucceed, 1);
110}
111
112// Tests that ImageFetchTabHelper::GetImageData can get image data from Js.
113TEST_F(ImageFetchTabHelperTest, GetImageDataWithJsSucceedFromXmlHttpRequest) {
114  // Inject fake |__gCrWeb.imageFetch.getImageData| that returns |kImageData|
115  // in base64 format.
116  id script_result = ExecuteJavaScript([NSString
117      stringWithFormat:
118          @"__gCrWeb.imageFetch = {}; __gCrWeb.imageFetch.getImageData = "
119           "function(id, url) { __gCrWeb.message.invokeOnHost({'command': "
120           "'imageFetch.getImageData', 'id': id, 'data': btoa('%s'), "
121           "'from':'xhr'}); }; true;",
122          kImageData]);
123  ASSERT_NSEQ(@YES, script_result);
124
125  __block bool callback_invoked = false;
126  image_fetch_tab_helper()->GetImageData(GURL(kImageUrl), web::Referrer(),
127                                         ^(NSData* data) {
128                                           ASSERT_TRUE(data);
129                                           EXPECT_NSEQ(GetExpectedData(), data);
130                                           callback_invoked = true;
131                                         });
132
133  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForGetImageDataTimeout, ^{
134    base::RunLoop().RunUntilIdle();
135    return callback_invoked;
136  }));
137  histogram_tester_.ExpectUniqueSample(
138      kUmaGetImageDataByJsResult,
139      ContextMenuGetImageDataByJsResult::kXMLHttpRequestSucceed, 1);
140}
141
142// Tests that ImageFetchTabHelper::GetImageData gets image data from server when
143// Js fails.
144TEST_F(ImageFetchTabHelperTest, GetImageDataWithJsFail) {
145  id script_result = ExecuteJavaScript(
146      @"__gCrWeb.imageFetch = {}; __gCrWeb.imageFetch.getImageData = "
147       "function(id, url) { __gCrWeb.message.invokeOnHost({'command': "
148       "'imageFetch.getImageData', 'id': id}); }; true;");
149  ASSERT_NSEQ(@YES, script_result);
150
151  __block bool callback_invoked = false;
152  image_fetch_tab_helper()->GetImageData(GURL(kImageUrl), web::Referrer(),
153                                         ^(NSData* data) {
154                                           ASSERT_TRUE(data);
155                                           EXPECT_NSEQ(GetExpectedData(), data);
156                                           callback_invoked = true;
157                                         });
158
159  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForGetImageDataTimeout, ^{
160    base::RunLoop().RunUntilIdle();
161    return callback_invoked;
162  }));
163  histogram_tester_.ExpectUniqueSample(
164      kUmaGetImageDataByJsResult, ContextMenuGetImageDataByJsResult::kFail, 1);
165}
166
167// Tests that ImageFetchTabHelper::GetImageData gets image data from server when
168// Js does not send a message back.
169TEST_F(ImageFetchTabHelperTest, GetImageDataWithJsTimeout) {
170  // Inject fake |__gCrWeb.imageFetch.getImageData| that does not do anything.
171  id script_result = ExecuteJavaScript(
172      @"__gCrWeb.imageFetch = {}; __gCrWeb.imageFetch.getImageData = "
173      @"function(id, url) {}; true;");
174  ASSERT_NSEQ(@YES, script_result);
175
176  __block bool callback_invoked = false;
177  image_fetch_tab_helper()->GetImageData(GURL(kImageUrl), web::Referrer(),
178                                         ^(NSData* data) {
179                                           ASSERT_TRUE(data);
180                                           EXPECT_NSEQ(GetExpectedData(), data);
181                                           callback_invoked = true;
182                                         });
183
184  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForGetImageDataTimeout, ^{
185    base::RunLoop().RunUntilIdle();
186    return callback_invoked;
187  }));
188  histogram_tester_.ExpectUniqueSample(
189      kUmaGetImageDataByJsResult, ContextMenuGetImageDataByJsResult::kTimeout,
190      1);
191}
192
193// Tests that ImageFetchTabHelper::GetImageData gets image data from server when
194// WebState is destroyed.
195TEST_F(ImageFetchTabHelperTest, GetImageDataWithWebStateDestroy) {
196  // Inject fake |__gCrWeb.imageFetch.getImageData| that does not do anything.
197  id script_result = ExecuteJavaScript(
198      @"__gCrWeb.imageFetch = {}; __gCrWeb.imageFetch.getImageData = "
199      @"function(id, url) {}; true;");
200  ASSERT_NSEQ(@YES, script_result);
201
202  __block bool callback_invoked = false;
203  image_fetch_tab_helper()->GetImageData(GURL(kImageUrl), web::Referrer(),
204                                         ^(NSData* data) {
205                                           ASSERT_TRUE(data);
206                                           EXPECT_NSEQ(GetExpectedData(), data);
207                                           callback_invoked = true;
208                                         });
209
210  DestroyWebState();
211
212  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForGetImageDataTimeout, ^{
213    base::RunLoop().RunUntilIdle();
214    return callback_invoked;
215  }));
216  histogram_tester_.ExpectTotalCount(kUmaGetImageDataByJsResult, 0);
217}
218
219// Tests that ImageFetchTabHelper::GetImageData gets image data from server when
220// WebState navigates to a new web page.
221TEST_F(ImageFetchTabHelperTest, GetImageDataWithWebStateNavigate) {
222  // Inject fake |__gCrWeb.imageFetch.getImageData| that does not do anything.
223  id script_result = ExecuteJavaScript(
224      @"__gCrWeb.imageFetch = {}; __gCrWeb.imageFetch.getImageData = "
225      @"function(id, url) {}; true;");
226  ASSERT_NSEQ(@YES, script_result);
227
228  __block bool callback_invoked = false;
229  image_fetch_tab_helper()->GetImageData(GURL(kImageUrl), web::Referrer(),
230                                         ^(NSData* data) {
231                                           ASSERT_TRUE(data);
232                                           EXPECT_NSEQ(GetExpectedData(), data);
233                                           callback_invoked = true;
234                                         });
235
236  LoadHtml(@"<html>new</html>"), GURL("http://new.webpage.com/");
237
238  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForGetImageDataTimeout, ^{
239    base::RunLoop().RunUntilIdle();
240    return callback_invoked;
241  }));
242  histogram_tester_.ExpectTotalCount(kUmaGetImageDataByJsResult, 0);
243}
244