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") { ...@@ -1872,6 +1872,8 @@ static_library("ui") {
"ash/holding_space/holding_space_keyed_service.h", "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.cc",
"ash/holding_space/holding_space_keyed_service_factory.h", "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.cc",
"ash/holding_space/holding_space_util.h", "ash/holding_space/holding_space_util.h",
"ash/image_downloader_impl.cc", "ash/image_downloader_impl.cc",
......
...@@ -43,7 +43,8 @@ HoldingSpaceKeyedService::HoldingSpaceKeyedService( ...@@ -43,7 +43,8 @@ HoldingSpaceKeyedService::HoldingSpaceKeyedService(
const AccountId& account_id) const AccountId& account_id)
: browser_context_(context), : browser_context_(context),
account_id_(account_id), 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 // If the service's associated profile is ready, we can proceed to restore the
// `holding_space_model_` from persistence. // `holding_space_model_` from persistence.
ProfileManager* const profile_manager = GetProfileManager(); ProfileManager* const profile_manager = GetProfileManager();
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_manager_observer.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_client_impl.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_thumbnail_loader.h"
#include "components/account_id/account_id.h" #include "components/account_id/account_id.h"
#include "components/download/public/common/download_item.h" #include "components/download/public/common/download_item.h"
#include "components/keyed_service/core/keyed_service.h" #include "components/keyed_service/core/keyed_service.h"
...@@ -105,6 +106,10 @@ class HoldingSpaceKeyedService : public KeyedService, ...@@ -105,6 +106,10 @@ class HoldingSpaceKeyedService : public KeyedService,
void SetDownloadManagerForTesting(content::DownloadManager* manager); void SetDownloadManagerForTesting(content::DownloadManager* manager);
HoldingSpaceThumbnailLoader* thumbnail_loader_for_testing() {
return &thumbnail_loader_;
}
private: private:
// KeyedService: // KeyedService:
void Shutdown() override; void Shutdown() override;
...@@ -146,6 +151,8 @@ class HoldingSpaceKeyedService : public KeyedService, ...@@ -146,6 +151,8 @@ class HoldingSpaceKeyedService : public KeyedService,
HoldingSpaceClientImpl holding_space_client_; HoldingSpaceClientImpl holding_space_client_;
HoldingSpaceModel holding_space_model_; HoldingSpaceModel holding_space_model_;
HoldingSpaceThumbnailLoader thumbnail_loader_;
ScopedObserver<HoldingSpaceModel, HoldingSpaceModelObserver> ScopedObserver<HoldingSpaceModel, HoldingSpaceModelObserver>
holding_space_model_observer_{this}; 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.
#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_
...@@ -2569,6 +2569,7 @@ if (!is_android) { ...@@ -2569,6 +2569,7 @@ if (!is_android) {
"../browser/ui/ash/chrome_new_window_client_browsertest.cc", "../browser/ui/ash/chrome_new_window_client_browsertest.cc",
"../browser/ui/ash/chrome_screenshot_grabber_browsertest.cc", "../browser/ui/ash/chrome_screenshot_grabber_browsertest.cc",
"../browser/ui/ash/clipboard_history_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_controller_browsertest.cc",
"../browser/ui/ash/keyboard/keyboard_end_to_end_browsertest.cc", "../browser/ui/ash/keyboard/keyboard_end_to_end_browsertest.cc",
"../browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc", "../browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc",
......
...@@ -55,10 +55,41 @@ function ImageLoader() { ...@@ -55,10 +55,41 @@ function ImageLoader() {
}.bind(this)); }.bind(this));
// Listen for incoming requests. // Listen for incoming requests.
chrome.runtime.onMessageExternal.addListener( chrome.runtime.onMessageExternal.addListener((msg, sender, sendResponse) => {
this.onIncomingRequest_.bind(this)); 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. * List of extensions allowed to perform image requests.
* *
...@@ -75,18 +106,11 @@ ImageLoader.ALLOWED_CLIENT_ORIGINS = [ ...@@ -75,18 +106,11 @@ ImageLoader.ALLOWED_CLIENT_ORIGINS = [
* Handler for incoming requests. * Handler for incoming requests.
* *
* @param {*} request_data A LoadImageRequest (received untyped). * @param {*} request_data A LoadImageRequest (received untyped).
* @param {!MessageSender} sender * @param {!string} senderOrigin
* @param {function(*): void} sendResponse * @param {function(*): void} sendResponse
*/ */
ImageLoader.prototype.onIncomingRequest_ = function( ImageLoader.prototype.onIncomingRequest_ = function(
request_data, sender, sendResponse) { request_data, senderOrigin, sendResponse) {
if (!sender.origin || !request_data) {
return;
}
if (ImageLoader.ALLOWED_CLIENT_ORIGINS.indexOf(sender.origin) === -1) {
return;
}
const request = /** @type {!LoadImageRequest} */ (request_data); const request = /** @type {!LoadImageRequest} */ (request_data);
// Sending a response may fail if the receiver already went offline. // Sending a response may fail if the receiver already went offline.
...@@ -108,14 +132,14 @@ ImageLoader.prototype.onIncomingRequest_ = function( ...@@ -108,14 +132,14 @@ ImageLoader.prototype.onIncomingRequest_ = function(
} else { } else {
request.orientation = new ImageOrientation(1, 0, 0, 1); 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 * Handles a request. Depending on type of the request, starts or stops
* an image task. * an image task.
* *
* @param {string} senderOrigin Sender's extension origin. * @param {string} senderOrigin Sender's origin.
* @param {!LoadImageRequest} request Pre-processed request. * @param {!LoadImageRequest} request Pre-processed request.
* @param {function(!LoadImageResponse)} callback Callback to be called to * @param {function(!LoadImageResponse)} callback Callback to be called to
* return response. * return response.
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
"fileSystem": ["requestFileSystem"] "fileSystem": ["requestFileSystem"]
}, },
"fileManagerPrivate", "fileManagerPrivate",
"nativeMessaging",
"storage" "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:", "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 @@ ...@@ -23,5 +24,6 @@
], ],
"persistent": false "persistent": false
}, },
"natively_connectable": ["com.google.holding_space_thumbnail_loader"],
"web_accessible_resources": ["image_loader_client.js"] "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