Commit 9dd81c74 authored by Toni Barzic's avatar Toni Barzic Committed by Commit Bot

Holding space thumbnail loader

Implements HoldingSpaceThumbnailLoader - a helper class that uses
image loader extension to generate thumbnail bitmaps for file system
file paths. The class establishes a native connection with the image
loader extension, and sends it a message with the requested image
parameter. The message format should match the image loader request.

Upon response from the image loader, the HoldingSpaceThumbnailLoader
closes the connection to the extension, converts the data received
from the extension into a bitmap, and returns the bitmap to the caller.

On image loader side, implements onConnectNative listener to handle
image requests.

BUG=1113772

Change-Id: Icff6ead726b3be8945ab58b7ff177259707ebc45
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2362973
Commit-Queue: Toni Baržić <tbarzic@chromium.org>
Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Reviewed-by: default avatarMaksim Ivanov <emaxx@chromium.org>
Reviewed-by: default avatarDavid Black <dmblack@google.com>
Cr-Commit-Position: refs/heads/master@{#802717}
parent 84be2902
......@@ -1872,6 +1872,8 @@ static_library("ui") {
"ash/holding_space/holding_space_keyed_service.h",
"ash/holding_space/holding_space_keyed_service_factory.cc",
"ash/holding_space/holding_space_keyed_service_factory.h",
"ash/holding_space/holding_space_thumbnail_loader.cc",
"ash/holding_space/holding_space_thumbnail_loader.h",
"ash/holding_space/holding_space_util.cc",
"ash/holding_space/holding_space_util.h",
"ash/image_downloader_impl.cc",
......
......@@ -43,7 +43,8 @@ HoldingSpaceKeyedService::HoldingSpaceKeyedService(
const AccountId& account_id)
: browser_context_(context),
account_id_(account_id),
holding_space_client_(Profile::FromBrowserContext(context)) {
holding_space_client_(Profile::FromBrowserContext(context)),
thumbnail_loader_(Profile::FromBrowserContext(context)) {
// If the service's associated profile is ready, we can proceed to restore the
// `holding_space_model_` from persistence.
ProfileManager* const profile_manager = GetProfileManager();
......
......@@ -15,6 +15,7 @@
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_manager_observer.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_client_impl.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader.h"
#include "components/account_id/account_id.h"
#include "components/download/public/common/download_item.h"
#include "components/keyed_service/core/keyed_service.h"
......@@ -105,6 +106,10 @@ class HoldingSpaceKeyedService : public KeyedService,
void SetDownloadManagerForTesting(content::DownloadManager* manager);
HoldingSpaceThumbnailLoader* thumbnail_loader_for_testing() {
return &thumbnail_loader_;
}
private:
// KeyedService:
void Shutdown() override;
......@@ -146,6 +151,8 @@ class HoldingSpaceKeyedService : public KeyedService,
HoldingSpaceClientImpl holding_space_client_;
HoldingSpaceModel holding_space_model_;
HoldingSpaceThumbnailLoader thumbnail_loader_;
ScopedObserver<HoldingSpaceModel, HoldingSpaceModelObserver>
holding_space_model_observer_{this};
......
// Copyright 2020 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.
#include "chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader.h"
#include "ash/public/cpp/image_downloader.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/optional.h"
#include "base/single_thread_task_runner.h"
#include "base/util/values/values_util.h"
#include "base/values.h"
#include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
#include "chrome/browser/bitmap_fetcher/bitmap_fetcher_delegate.h"
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
#include "chrome/browser/extensions/api/messaging/native_message_port.h"
#include "chrome/browser/profiles/profile.h"
#include "extensions/browser/api/messaging/channel_endpoint.h"
#include "extensions/browser/api/messaging/message_service.h"
#include "extensions/browser/api/messaging/native_message_host.h"
#include "extensions/common/api/messaging/messaging_endpoint.h"
#include "extensions/common/api/messaging/port_id.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "storage/browser/file_system/file_system_context.h"
#include "ui/gfx/image/image_skia.h"
#include "url/gurl.h"
namespace ash {
namespace {
// The native host name that will identify the holding space thumbnail loader to
// the image loader extension.
constexpr char kNativeMessageHostName[] =
"com.google.holding_space_thumbnail_loader";
using ThumbnailDataCallback = base::OnceCallback<void(const std::string& data)>;
// Handles a parsed message sent from image loader extension in response to a
// thumbnail request.
void HandleParsedThumbnailResponse(
const std::string& request_id,
ThumbnailDataCallback callback,
data_decoder::DataDecoder::ValueOrError result) {
if (!result.value) {
VLOG(2) << "Failed to parse request response " << *result.error;
std::move(callback).Run("");
return;
}
if (!result.value->is_dict()) {
VLOG(2) << "Invalid response format";
std::move(callback).Run("");
return;
}
const std::string* received_request_id =
result.value->FindStringKey("taskId");
const std::string* data = result.value->FindStringKey("data");
if (!data || !received_request_id || *received_request_id != request_id) {
std::move(callback).Run("");
return;
}
std::move(callback).Run(*data);
}
// Native message host for communication to the image loader extension.
// It handles a single image request - when the connection to the extension is
// established, it send a message containing an image request to the image
// loader. It closes the connection once it receives a response from the image
// loader.
class ThumbnailLoaderNativeMessageHost : public extensions::NativeMessageHost {
public:
ThumbnailLoaderNativeMessageHost(const std::string& request_id,
const std::string& message,
ThumbnailDataCallback callback)
: request_id_(request_id),
message_(message),
callback_(std::move(callback)) {}
~ThumbnailLoaderNativeMessageHost() override {
if (callback_)
std::move(callback_).Run("");
}
void OnMessage(const std::string& message) override {
if (response_received_)
return;
response_received_ = true;
// Detach the callback from the message host in case the extension closes
// connection by the time the response is parsed.
data_decoder::DataDecoder::ParseJsonIsolated(
message, base::BindOnce(&HandleParsedThumbnailResponse, request_id_,
std::move(callback_)));
client_->CloseChannel("");
client_ = nullptr;
}
void Start(Client* client) override {
client_ = client;
client_->PostMessageFromNativeHost(message_);
}
scoped_refptr<base::SingleThreadTaskRunner> task_runner() const override {
return task_runner_;
}
private:
const std::string request_id_;
const std::string message_;
ThumbnailDataCallback callback_;
Client* client_ = nullptr;
bool response_received_ = false;
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_ =
base::ThreadTaskRunnerHandle::Get();
};
} // namespace
// Converts a data URL to bitmap.
class HoldingSpaceThumbnailLoader::ThumbnailDecoder
: public BitmapFetcherDelegate {
public:
explicit ThumbnailDecoder(Profile* profile) : profile_(profile) {}
ThumbnailDecoder(const ThumbnailDecoder&) = delete;
ThumbnailDecoder& operator=(const ThumbnailDecoder&) = delete;
~ThumbnailDecoder() override = default;
// BitmapFetcherDelegate:
void OnFetchComplete(const GURL& url, const SkBitmap* bitmap) override {
std::move(callback_).Run(bitmap);
}
void Start(const std::string& data,
HoldingSpaceThumbnailLoader::ImageCallback callback) {
DCHECK(!callback_);
DCHECK(!bitmap_fetcher_);
// The data sent from the image loader extension should be in form of a data
// URL.
GURL data_url(data);
if (!data_url.is_valid() || !data_url.SchemeIs(url::kDataScheme)) {
std::move(callback).Run(nullptr);
return;
}
callback_ = std::move(callback);
// Note that the image downloader will not use network traffic for data
// URLs.
bitmap_fetcher_ = std::make_unique<BitmapFetcher>(
data_url, this, MISSING_TRAFFIC_ANNOTATION);
bitmap_fetcher_->Init(
/*referrer=*/std::string(), net::ReferrerPolicy::NEVER_CLEAR,
network::mojom::CredentialsMode::kOmit);
bitmap_fetcher_->Start(profile_->GetURLLoaderFactory().get());
}
private:
Profile* const profile_;
HoldingSpaceThumbnailLoader::ImageCallback callback_;
std::unique_ptr<BitmapFetcher> bitmap_fetcher_;
};
HoldingSpaceThumbnailLoader::HoldingSpaceThumbnailLoader(Profile* profile)
: profile_(profile) {}
HoldingSpaceThumbnailLoader::~HoldingSpaceThumbnailLoader() = default;
HoldingSpaceThumbnailLoader::ThumbnailRequest::ThumbnailRequest(
const base::FilePath& item_path,
const gfx::Size& size)
: item_path(item_path), size(size) {}
HoldingSpaceThumbnailLoader::ThumbnailRequest::~ThumbnailRequest() = default;
void HoldingSpaceThumbnailLoader::Load(const ThumbnailRequest& request,
ImageCallback callback) {
// Get the item's last modified time - this will be used for cache lookup in
// the image loader extension.
file_manager::util::GetMetadataForPath(
file_manager::util::GetFileSystemContextForExtensionId(
profile_, file_manager::kImageLoaderExtensionId),
request.item_path,
storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
base::BindOnce(&HoldingSpaceThumbnailLoader::LoadForFileWithMetadata,
weak_factory_.GetWeakPtr(), request, std::move(callback)));
}
void HoldingSpaceThumbnailLoader::LoadForFileWithMetadata(
const ThumbnailRequest& request,
ImageCallback callback,
base::File::Error result,
const base::File::Info& file_info) {
if (result != base::File::FILE_OK) {
std::move(callback).Run(nullptr);
return;
}
if (file_info.is_directory) {
std::move(callback).Run(nullptr);
return;
}
GURL thumbnail_url;
if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
profile_, request.item_path, file_manager::kImageLoaderExtensionId,
&thumbnail_url)) {
std::move(callback).Run(nullptr);
return;
}
base::UnguessableToken request_id = base::UnguessableToken::Create();
requests_[request_id] = std::move(callback);
// Generate an image loader request. The request type is defined in
// ui/file_manager/image_loader/load_image_request.js.
base::Value request_value(base::Value::Type::DICTIONARY);
request_value.SetKey("taskId", base::Value(request_id.ToString()));
request_value.SetKey("url", base::Value(thumbnail_url.spec()));
request_value.SetKey("timestamp", util::TimeToValue(file_info.last_modified));
request_value.SetBoolKey("cache", true);
request_value.SetBoolKey("crop", true);
request_value.SetKey("priority", base::Value(1));
request_value.SetKey("width", base::Value(request.size.width()));
request_value.SetKey("height", base::Value(request.size.height()));
std::string request_message;
base::JSONWriter::Write(request_value, &request_message);
// Open a channel to the image loader extension using a message host that send
// the image loader request.
auto native_message_host = std::make_unique<ThumbnailLoaderNativeMessageHost>(
request_id.ToString(), request_message,
base::BindOnce(&HoldingSpaceThumbnailLoader::OnThumbnailLoaded,
weak_factory_.GetWeakPtr(), request_id));
const extensions::PortId port_id(base::UnguessableToken::Create(),
1 /* port_number */, true /* is_opener */);
extensions::MessageService* const message_service =
extensions::MessageService::Get(profile_);
auto native_message_port = std::make_unique<extensions::NativeMessagePort>(
message_service->GetChannelDelegate(), port_id,
std::move(native_message_host));
message_service->OpenChannelToExtension(
extensions::ChannelEndpoint(profile_), port_id,
extensions::MessagingEndpoint::ForNativeApp(kNativeMessageHostName),
std::move(native_message_port), file_manager::kImageLoaderExtensionId,
GURL(), std::string() /* channel_name */);
}
void HoldingSpaceThumbnailLoader::OnThumbnailLoaded(
const base::UnguessableToken& request_id,
const std::string& data) {
if (!requests_.count(request_id))
return;
if (data.empty()) {
RespondToRequest(request_id, nullptr);
return;
}
auto thumbnail_decoder = std::make_unique<ThumbnailDecoder>(profile_);
ThumbnailDecoder* thumbnail_decoder_ptr = thumbnail_decoder.get();
thumbnail_decoders_.emplace(request_id, std::move(thumbnail_decoder));
thumbnail_decoder_ptr->Start(
data, base::BindOnce(&HoldingSpaceThumbnailLoader::RespondToRequest,
weak_factory_.GetWeakPtr(), request_id));
}
void HoldingSpaceThumbnailLoader::RespondToRequest(
const base::UnguessableToken& request_id,
const SkBitmap* bitmap) {
thumbnail_decoders_.erase(request_id);
auto request_it = requests_.find(request_id);
if (request_it == requests_.end())
return;
ImageCallback callback = std::move(request_it->second);
requests_.erase(request_it);
std::move(callback).Run(bitmap);
}
} // namespace ash
// Copyright 2020 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.
#ifndef CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_THUMBNAIL_LOADER_H_
#define CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_THUMBNAIL_LOADER_H_
#include <map>
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/files/file.h"
#include "base/memory/weak_ptr.h"
#include "base/unguessable_token.h"
#include "ui/gfx/geometry/size.h"
class Profile;
class SkBitmap;
namespace base {
class FilePath;
}
namespace ash {
// Loader for file-backed holding space items' thumbnails. It opens a native
// connection to the image loader extension (also used to generate thumbnails
// for the file manager), and sends it an image request for a file path.
// It decodes data returned by the extension into a bitmap.
class HoldingSpaceThumbnailLoader {
public:
explicit HoldingSpaceThumbnailLoader(Profile* profile);
HoldingSpaceThumbnailLoader(const HoldingSpaceThumbnailLoader&) = delete;
HoldingSpaceThumbnailLoader& operator=(const HoldingSpaceThumbnailLoader&) =
delete;
~HoldingSpaceThumbnailLoader();
// Thumbnail request data that will be forwarded to the image loader.
struct ThumbnailRequest {
ThumbnailRequest(const base::FilePath& item_path, const gfx::Size& size);
~ThumbnailRequest();
// The absolute item file path.
const base::FilePath item_path;
// The desired bitmap size.
const gfx::Size size;
};
using ImageCallback = base::OnceCallback<void(const SkBitmap* bitmap)>;
// Starts a request for a thumbnail. `callback` called with the generated
// bitmap. On error, the bitmap will be null.
void Load(const ThumbnailRequest& request, ImageCallback callback);
private:
class ThumbnailDecoder;
// Starts thumbnail request after the file metadata has been retrieved. The
// metadata is used to verify that the path exists, points to a file, and to
// get the file's last modification time.
void LoadForFileWithMetadata(const ThumbnailRequest& request,
ImageCallback callback,
base::File::Error result,
const base::File::Info& file_info);
// Callback to the image loader request.
// `request_id` identifies the thumbnail request.
// `data` - Image data returned by the image loader. Expected to be in a data
// URL form. It will attempt to decode the received data.
void OnThumbnailLoaded(const base::UnguessableToken& request_id,
const std::string& data);
// Finalizes the thumbnail request identified by `request_id`. It invokes the
// request callback with `bitmap`.
void RespondToRequest(const base::UnguessableToken& request_id,
const SkBitmap* bitmap);
Profile* const profile_;
// Maps pending thumbnail requests to their registered callbacks.
std::map<base::UnguessableToken, ImageCallback> requests_;
// Maps pending thumbnail requests to image decoders used to transform data
// received from the image loder into a bitmap.
std::map<base::UnguessableToken, std::unique_ptr<ThumbnailDecoder>>
thumbnail_decoders_;
base::WeakPtrFactory<HoldingSpaceThumbnailLoader> weak_factory_{this};
};
} // namespace ash
#endif // CHROME_BROWSER_UI_ASH_HOLDING_SPACE_HOLDING_SPACE_THUMBNAIL_LOADER_H_
// Copyright 2020 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.
#include "chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader.h"
#include <memory>
#include "ash/public/cpp/ash_features.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/test/bind_test_util.h"
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
#include "chrome/browser/chromeos/file_manager/open_util.h"
#include "chrome/browser/chromeos/file_manager/volume_manager.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_context.h"
#include "ui/gfx/image/image_unittest_util.h"
namespace {
// References to paths set up in the test mount point during the browser test
// setup.
enum class TestPath { kNonExistent, kEmptyDir, kJpg, kBrokenJpg, kPng };
// Copies |bitmap| into |copy| and runs |callback|.
void CopyBitmapAndRunClosure(base::OnceClosure callback,
SkBitmap* copy,
const SkBitmap* bitmap) {
if (bitmap)
*copy = *bitmap;
else
ADD_FAILURE() << "Got null bitmap";
std::move(callback).Run();
}
// Utility class that registers an external file system mount point, and grants
// file manager app access permission for the mount point.
class ScopedExternalMountPoint {
public:
ScopedExternalMountPoint(Profile* profile, const std::string& name)
: name_(name) {
if (!temp_dir_.CreateUniqueTempDir())
return;
storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
name_, storage::kFileSystemTypeNativeLocal,
storage::FileSystemMountOption(), temp_dir_.GetPath());
file_manager::util::GetFileSystemContextForExtensionId(
profile, file_manager::kImageLoaderExtensionId)
->external_backend()
->GrantFileAccessToExtension(file_manager::kImageLoaderExtensionId,
base::FilePath(name_));
}
~ScopedExternalMountPoint() {
storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(name_);
}
bool IsValid() const { return temp_dir_.IsValid(); }
const base::FilePath& GetRootPath() const { return temp_dir_.GetPath(); }
const std::string& name() const { return name_; }
private:
base::ScopedTempDir temp_dir_;
std::string name_;
};
} // namespace
class HoldingSpaceThumbnailLoaderTest : public InProcessBrowserTest {
public:
HoldingSpaceThumbnailLoaderTest() {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kTemporaryHoldingSpace);
}
HoldingSpaceThumbnailLoaderTest(const HoldingSpaceThumbnailLoaderTest&) =
delete;
HoldingSpaceThumbnailLoaderTest& operator=(
const HoldingSpaceThumbnailLoaderTest&) = delete;
~HoldingSpaceThumbnailLoaderTest() override = default;
// InProcessBrowserTest:
void SetUpInProcessBrowserTestFixture() override {
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
test_mount_point_ = std::make_unique<ScopedExternalMountPoint>(
browser()->profile(), "test_downloads");
ASSERT_TRUE(test_mount_point_->IsValid());
SetUpTestDirStructure();
}
void TearDownOnMainThread() override {
test_mount_point_.reset();
InProcessBrowserTest::TearDownOnMainThread();
}
ash::HoldingSpaceThumbnailLoader* GetThumbnailLoader() {
return ash::HoldingSpaceKeyedServiceFactory::GetInstance()
->GetService(browser()->profile())
->thumbnail_loader_for_testing();
}
base::FilePath GetTestDataFilePath(const std::string& file_name) {
// Get the path to file manager's test data directory.
base::FilePath source_dir;
CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_dir));
auto test_data_dir = source_dir.AppendASCII("chrome")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("chromeos")
.AppendASCII("file_manager");
// Return full test data path to the given |file_name|.
return test_data_dir.AppendASCII(file_name);
}
base::FilePath GetTestPath(TestPath test_path) {
switch (test_path) {
case TestPath::kNonExistent:
return mount_point()->GetRootPath().AppendASCII("fake.png");
case TestPath::kEmptyDir:
return mount_point()->GetRootPath().AppendASCII("empty_dir");
case TestPath::kJpg:
return mount_point()->GetRootPath().AppendASCII("image3.jpg");
case TestPath::kBrokenJpg:
return mount_point()->GetRootPath().AppendASCII("broken.jpg");
case TestPath::kPng:
return mount_point()->GetRootPath().AppendASCII("image.png");
}
}
const ScopedExternalMountPoint* mount_point() const {
return test_mount_point_.get();
}
private:
void SetUpTestDirStructure() {
ASSERT_TRUE(base::CreateDirectory(GetTestPath(TestPath::kEmptyDir)));
ASSERT_TRUE(base::CopyFile(GetTestDataFilePath("image.png"),
GetTestPath(TestPath::kPng)));
ASSERT_TRUE(base::CopyFile(GetTestDataFilePath("image3.jpg"),
GetTestPath(TestPath::kJpg)));
ASSERT_TRUE(base::CopyFile(GetTestDataFilePath("broken.jpg"),
GetTestPath(TestPath::kBrokenJpg)));
}
std::unique_ptr<ScopedExternalMountPoint> test_mount_point_;
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(HoldingSpaceThumbnailLoaderTest, LoadNonExistentItem) {
ash::HoldingSpaceThumbnailLoader* loader = GetThumbnailLoader();
ASSERT_TRUE(loader);
base::RunLoop run_loop;
ash::HoldingSpaceThumbnailLoader::ThumbnailRequest request(
GetTestPath(TestPath::kNonExistent), gfx::Size(48, 48));
loader->Load(request,
base::BindLambdaForTesting([&run_loop](const SkBitmap* bitmap) {
EXPECT_FALSE(bitmap);
run_loop.Quit();
}));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(HoldingSpaceThumbnailLoaderTest, LoadFolder) {
ash::HoldingSpaceThumbnailLoader* loader = GetThumbnailLoader();
ASSERT_TRUE(loader);
base::RunLoop run_loop;
ash::HoldingSpaceThumbnailLoader::ThumbnailRequest request(
GetTestPath(TestPath::kEmptyDir), gfx::Size(48, 48));
loader->Load(request,
base::BindLambdaForTesting([&run_loop](const SkBitmap* bitmap) {
EXPECT_FALSE(bitmap);
run_loop.Quit();
}));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(HoldingSpaceThumbnailLoaderTest, LoadJpg) {
ash::HoldingSpaceThumbnailLoader* loader = GetThumbnailLoader();
ASSERT_TRUE(loader);
SkBitmap bitmap;
base::RunLoop run_loop;
ash::HoldingSpaceThumbnailLoader::ThumbnailRequest request(
GetTestPath(TestPath::kJpg), gfx::Size(48, 48));
loader->Load(request, base::BindOnce(&CopyBitmapAndRunClosure,
run_loop.QuitClosure(), &bitmap));
run_loop.Run();
EXPECT_FALSE(bitmap.isNull());
EXPECT_EQ(48, bitmap.width());
EXPECT_EQ(48, bitmap.height());
}
IN_PROC_BROWSER_TEST_F(HoldingSpaceThumbnailLoaderTest, LoadBrokenJpg) {
ash::HoldingSpaceThumbnailLoader* loader = GetThumbnailLoader();
ASSERT_TRUE(loader);
base::RunLoop run_loop;
ash::HoldingSpaceThumbnailLoader::ThumbnailRequest request(
GetTestPath(TestPath::kBrokenJpg), gfx::Size(48, 48));
loader->Load(request,
base::BindLambdaForTesting([&run_loop](const SkBitmap* bitmap) {
EXPECT_FALSE(bitmap);
run_loop.Quit();
}));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(HoldingSpaceThumbnailLoaderTest, LoadPng) {
ash::HoldingSpaceThumbnailLoader* loader = GetThumbnailLoader();
ASSERT_TRUE(loader);
SkBitmap bitmap;
base::RunLoop run_loop;
ash::HoldingSpaceThumbnailLoader::ThumbnailRequest request(
GetTestPath(TestPath::kPng), gfx::Size(48, 48));
loader->Load(request, base::BindOnce(&CopyBitmapAndRunClosure,
run_loop.QuitClosure(), &bitmap));
run_loop.Run();
EXPECT_FALSE(bitmap.isNull());
EXPECT_EQ(48, bitmap.width());
EXPECT_EQ(48, bitmap.height());
}
IN_PROC_BROWSER_TEST_F(HoldingSpaceThumbnailLoaderTest, RepeatedLoads) {
ash::HoldingSpaceThumbnailLoader* loader = GetThumbnailLoader();
ASSERT_TRUE(loader);
ash::HoldingSpaceThumbnailLoader::ThumbnailRequest request(
GetTestPath(TestPath::kPng), gfx::Size(48, 48));
SkBitmap bitmap1;
base::RunLoop run_loop1;
loader->Load(request, base::BindOnce(&CopyBitmapAndRunClosure,
run_loop1.QuitClosure(), &bitmap1));
run_loop1.Run();
ASSERT_FALSE(bitmap1.isNull());
SkBitmap bitmap2;
base::RunLoop run_loop2;
loader->Load(request, base::BindOnce(&CopyBitmapAndRunClosure,
run_loop2.QuitClosure(), &bitmap2));
run_loop2.Run();
ASSERT_FALSE(bitmap2.isNull());
EXPECT_TRUE(gfx::test::AreBitmapsEqual(bitmap1, bitmap2));
// Change the backing image, and verify the loaded bitmap changes, too.
{
base::ScopedAllowBlockingForTesting allow_io;
ASSERT_TRUE(base::CopyFile(GetTestPath(TestPath::kJpg),
GetTestPath(TestPath::kPng)));
}
SkBitmap bitmap3;
base::RunLoop run_loop3;
loader->Load(request, base::BindOnce(&CopyBitmapAndRunClosure,
run_loop3.QuitClosure(), &bitmap3));
run_loop3.Run();
ASSERT_FALSE(bitmap3.isNull());
EXPECT_FALSE(gfx::test::AreBitmapsEqual(bitmap1, bitmap3));
}
IN_PROC_BROWSER_TEST_F(HoldingSpaceThumbnailLoaderTest, ConcurentLoads) {
ash::HoldingSpaceThumbnailLoader* loader = GetThumbnailLoader();
ASSERT_TRUE(loader);
SkBitmap bitmap1;
ash::HoldingSpaceThumbnailLoader::ThumbnailRequest request1(
GetTestPath(TestPath::kPng), gfx::Size(48, 48));
base::RunLoop run_loop1;
loader->Load(request1, base::BindOnce(&CopyBitmapAndRunClosure,
run_loop1.QuitClosure(), &bitmap1));
SkBitmap bitmap2;
ash::HoldingSpaceThumbnailLoader::ThumbnailRequest request2(
GetTestPath(TestPath::kPng), gfx::Size(96, 96));
base::RunLoop run_loop2;
loader->Load(request2, base::BindOnce(&CopyBitmapAndRunClosure,
run_loop2.QuitClosure(), &bitmap2));
SkBitmap bitmap3;
base::RunLoop run_loop3;
ash::HoldingSpaceThumbnailLoader::ThumbnailRequest request3(
GetTestPath(TestPath::kJpg), gfx::Size(48, 48));
loader->Load(request3, base::BindOnce(&CopyBitmapAndRunClosure,
run_loop3.QuitClosure(), &bitmap3));
run_loop1.Run();
EXPECT_FALSE(bitmap1.isNull());
EXPECT_EQ(48, bitmap1.width());
EXPECT_EQ(48, bitmap1.height());
run_loop2.Run();
EXPECT_FALSE(bitmap2.isNull());
EXPECT_EQ(96, bitmap2.width());
EXPECT_EQ(96, bitmap2.height());
run_loop3.Run();
EXPECT_FALSE(bitmap3.isNull());
EXPECT_EQ(48, bitmap3.width());
EXPECT_EQ(48, bitmap3.height());
EXPECT_FALSE(gfx::test::AreBitmapsEqual(bitmap1, bitmap2));
EXPECT_FALSE(gfx::test::AreBitmapsEqual(bitmap1, bitmap3));
}
......@@ -2569,6 +2569,7 @@ if (!is_android) {
"../browser/ui/ash/chrome_new_window_client_browsertest.cc",
"../browser/ui/ash/chrome_screenshot_grabber_browsertest.cc",
"../browser/ui/ash/clipboard_history_browsertest.cc",
"../browser/ui/ash/holding_space/holding_space_thumbnail_loader_browsertest.cc",
"../browser/ui/ash/keyboard/keyboard_controller_browsertest.cc",
"../browser/ui/ash/keyboard/keyboard_end_to_end_browsertest.cc",
"../browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc",
......
......@@ -55,10 +55,41 @@ function ImageLoader() {
}.bind(this));
// Listen for incoming requests.
chrome.runtime.onMessageExternal.addListener(
this.onIncomingRequest_.bind(this));
chrome.runtime.onMessageExternal.addListener((msg, sender, sendResponse) => {
if (!sender.origin || !msg) {
return;
}
if (ImageLoader.ALLOWED_CLIENT_ORIGINS.indexOf(sender.origin) === -1) {
return;
}
this.onIncomingRequest_(msg, sender.origin, sendResponse);
});
chrome.runtime['onConnectNative'].addListener((port) => {
if (port.sender.nativeApplication !=
'com.google.holding_space_thumbnail_loader') {
port.disconnect();
return;
}
port.onMessage.addListener((msg) => {
// Each connection is expected to handle a single request only.
const started = this.onIncomingRequest_(
msg, port.sender.nativeApplication, response => {
port.postMessage(response);
port.disconnect();
});
if (!started) {
port.disconnect();
}
});
});
}
/**
* List of extensions allowed to perform image requests.
*
......@@ -75,18 +106,11 @@ ImageLoader.ALLOWED_CLIENT_ORIGINS = [
* Handler for incoming requests.
*
* @param {*} request_data A LoadImageRequest (received untyped).
* @param {!MessageSender} sender
* @param {!string} senderOrigin
* @param {function(*): void} sendResponse
*/
ImageLoader.prototype.onIncomingRequest_ = function(
request_data, sender, sendResponse) {
if (!sender.origin || !request_data) {
return;
}
if (ImageLoader.ALLOWED_CLIENT_ORIGINS.indexOf(sender.origin) === -1) {
return;
}
request_data, senderOrigin, sendResponse) {
const request = /** @type {!LoadImageRequest} */ (request_data);
// Sending a response may fail if the receiver already went offline.
......@@ -108,14 +132,14 @@ ImageLoader.prototype.onIncomingRequest_ = function(
} else {
request.orientation = new ImageOrientation(1, 0, 0, 1);
}
return this.onMessage_(sender.origin, request, failSafeSendResponse);
return this.onMessage_(senderOrigin, request, failSafeSendResponse);
};
/**
* Handles a request. Depending on type of the request, starts or stops
* an image task.
*
* @param {string} senderOrigin Sender's extension origin.
* @param {string} senderOrigin Sender's origin.
* @param {!LoadImageRequest} request Pre-processed request.
* @param {function(!LoadImageResponse)} callback Callback to be called to
* return response.
......
......@@ -12,6 +12,7 @@
"fileSystem": ["requestFileSystem"]
},
"fileManagerPrivate",
"nativeMessaging",
"storage"
],
"content_security_policy": "default-src 'none'; script-src 'self' 'wasm-eval' blob: filesystem: chrome://resources chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj; style-src 'self' blob: filesystem:; frame-src 'self' blob: filesystem:; img-src 'self' blob: filesystem: data:; media-src 'self' blob: filesystem:; connect-src 'self' blob: filesystem:",
......@@ -23,5 +24,6 @@
],
"persistent": false
},
"natively_connectable": ["com.google.holding_space_thumbnail_loader"],
"web_accessible_resources": ["image_loader_client.js"]
}
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