blob: 393c9bd741553d61ad2c29da902d476153024a21 [file] [log] [blame]
// Copyright 2018 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 <UIKit/UIKit.h>
#include "base/base64.h"
#include "base/macros.h"
#import "base/test/ios/wait_util.h"
#import "ios/web/public/test/web_js_test.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#import "ios/web/public/web_state/web_state.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#include "url/gurl.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::kWaitForJSCompletionTimeout;
namespace {
// Base64 data of a PNG image.
const char kImageBase64[] =
"iVBORw0KGgoAAAANSUhEUgAAAIQAAAB+AQMAAADfgyubAAAABlBMVEX+/"
"v4ODg5o52ONAAABfElEQVRIx+3UO27DMAwAUBoeNBQIe4DAvkLHFDDim/"
"QMHp2lVpAhY49Ubb2GgFxARZcMqVjKn1SUgl6g4WBAz6QByRQB7vGf48Hy4yWWgUV5IQagvsRC"
"LG0sRZAhljIInRMphCjSrLEgi5IysLy7SCrOQSFrlnqIpWHZhp1c45mloVh2LH0mOyFfLJ9hJ7"
"EUJyGnVHily48boiPhlTpm8hZLqEAUchwFpFR1LOqmNFIU6ab1iWwy6YWskHQnhXNcluNEI4Qc"
"K2SNtE9E0d6kOaWhWBpFByNaY8M5OhWVi2zDDgkzWQG5RAohFqmCVNYip7DoGykG/"
"SaTXkip0XeZOCFK42WUx/"
"lwQAF+"
"2yBPk2wBoSYLLN0kHipAMmGn05eKIPUohV2kYdFAl3Jq1tJDD6FTyKOZryicJ6G5ERUVDmoPMB"
"At1/"
"hgx3EwLD+"
"sJDIQpF0Ougzl7TmIW0aGB2h5UV9vXChv7TQF5tHjpvHzO5CQX72Gcncf3vf4M34AIPQSr4HM1"
"a4AAAAASUVORK5CYII=";
// Delay of the http response for image.
const int kImageDelayInMs = 1000;
// Request handler of the http server.
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
std::string image_binary;
EXPECT_TRUE(base::Base64Decode(kImageBase64, &image_binary));
if (request.GetURL().path() == "/image") {
auto result = std::make_unique<net::test_server::BasicHttpResponse>();
result->set_content_type("image/png");
result->set_content(image_binary);
result->AddCustomHeader("Access-Control-Allow-Origin", "*");
return std::move(result);
}
if (request.GetURL().path() == "/image_delayed") {
auto result = std::make_unique<net::test_server::DelayedHttpResponse>(
base::TimeDelta::FromMilliseconds(kImageDelayInMs));
result->set_content_type("image/png");
result->set_content(image_binary);
result->AddCustomHeader("Access-Control-Allow-Origin", "*");
return std::move(result);
}
return nullptr;
}
// The ID param for calling JS, and will be regained in the message sent back.
const int kCallJavaScriptId = 66666;
// The command prefix for receiving messages from JS.
const std::string kCommandPrefix = "imageFetch";
}
// Test fixture for image_fetch.js testing.
class ImageFetchJsTest : public web::WebJsTest<web::WebTestWithWebState> {
protected:
ImageFetchJsTest()
: web::WebJsTest<web::WebTestWithWebState>(@[ @"image_fetch" ]) {}
void SetUp() override {
WebTestWithWebState::SetUp();
server_.RegisterDefaultHandler(base::BindRepeating(HandleRequest));
ASSERT_TRUE(server_.Start());
web_state()->AddScriptCommandCallback(
base::BindRepeating(&ImageFetchJsTest::OnMessageFromJavaScript,
base::Unretained(this)),
kCommandPrefix);
}
void TearDown() override {
web_state()->RemoveScriptCommandCallback(kCommandPrefix);
WebTestWithWebState::TearDown();
}
bool OnMessageFromJavaScript(const base::DictionaryValue& message,
const GURL& page_url,
bool has_user_gesture,
bool form_in_main_frame,
web::WebFrame* sender_frame) {
message_received_ = true;
message_ = message.Clone();
return true;
}
net::EmbeddedTestServer server_;
base::Value message_;
bool message_received_ = false;
DISALLOW_COPY_AND_ASSIGN(ImageFetchJsTest);
};
// Tests that __gCrWeb.imageFetch.getImageData works when the image is
// same-domain.
TEST_F(ImageFetchJsTest, TestGetSameDomainImageData) {
const GURL image_url = server_.GetURL("/image");
const GURL page_url = server_.GetURL("/");
LoadHtmlAndInject([NSString stringWithFormat:@"<html><img src='%s'></html>",
image_url.spec().c_str()],
page_url);
ExecuteJavaScriptWithFormat(@"__gCrWeb.imageFetch.getImageData(%d, '%s');",
kCallJavaScriptId, image_url.spec().c_str());
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
return message_received_;
}));
ASSERT_TRUE(message_.is_dict());
const base::Value* id_key = message_.FindKey("id");
ASSERT_TRUE(id_key);
ASSERT_TRUE(id_key->is_double());
EXPECT_EQ(kCallJavaScriptId, static_cast<int>(id_key->GetDouble()));
// The fetched image data may not equal to original base64 data because of
// recompression in JavaScript. Therefore decode the returned image data and
// check if it can be rendered to an image.
const base::Value* data = message_.FindKey("data");
ASSERT_TRUE(data);
ASSERT_TRUE(data->is_string());
ASSERT_FALSE(data->GetString().empty());
std::string decoded_data;
ASSERT_TRUE(base::Base64Decode(data->GetString(), &decoded_data));
UIImage* image =
[UIImage imageWithData:[NSData dataWithBytes:decoded_data.c_str()
length:decoded_data.size()]];
EXPECT_TRUE(image);
const base::Value* from = message_.FindKey("from");
ASSERT_TRUE(from);
ASSERT_TRUE(from->is_string());
EXPECT_EQ("canvas", from->GetString());
}
// Tests that __gCrWeb.imageFetch.getImageData works when the image is
// cross-domain.
TEST_F(ImageFetchJsTest, TestGetCrossDomainImageData) {
const GURL image_url = server_.GetURL("/image");
// WebTestWithWebState::LoadHtml uses an HTTPS url for webpage as default. Use
// an HTTP url instead, because XMLHttpRequest with HTTP url sent from HTTPS
// website is forbidden due to the CORS policy.
const GURL page_url("http://chrooooome.com");
LoadHtmlAndInject([NSString stringWithFormat:@"<html><img src='%s'></html>",
image_url.spec().c_str()],
page_url);
ExecuteJavaScriptWithFormat(@"__gCrWeb.imageFetch.getImageData(%d, '%s');",
kCallJavaScriptId, image_url.spec().c_str());
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
return message_received_;
}));
ASSERT_TRUE(message_.is_dict());
const base::Value* id_key = message_.FindKey("id");
ASSERT_TRUE(id_key);
ASSERT_TRUE(id_key->is_double());
EXPECT_EQ(kCallJavaScriptId, static_cast<int>(id_key->GetDouble()));
const base::Value* data = message_.FindKey("data");
ASSERT_TRUE(data);
ASSERT_TRUE(data->is_string());
EXPECT_EQ(kImageBase64, data->GetString());
const base::Value* from = message_.FindKey("from");
ASSERT_TRUE(from);
ASSERT_TRUE(from->is_string());
EXPECT_EQ("xhr", from->GetString());
}
// Tests that __gCrWeb.imageFetch.getImageData fails for timeout when the image
// response is delayed. In this test the image must be cross-domain, otherwise
// image data will be fetched from <img> by <canvas> directly.
TEST_F(ImageFetchJsTest, TestGetDelayedImageData) {
const GURL image_url = server_.GetURL("/image_delayed");
const GURL page_url("http://chrooooome.com");
LoadHtmlAndInject([NSString stringWithFormat:@"<html><img src='%s'></html>",
image_url.spec().c_str()],
page_url);
ExecuteJavaScriptWithFormat(@"__gCrWeb.imageFetch.getImageData(%d, '%s');",
kCallJavaScriptId, image_url.spec().c_str());
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
return message_received_;
}));
ASSERT_TRUE(message_.is_dict());
const base::Value* id_key = message_.FindKey("id");
ASSERT_TRUE(id_key);
ASSERT_TRUE(id_key->is_double());
EXPECT_EQ(kCallJavaScriptId, static_cast<int>(id_key->GetDouble()));
EXPECT_FALSE(message_.FindKey("data"));
EXPECT_FALSE(message_.FindKey("from"));
}