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