Commit 33edc49e authored by Bo Majewski's avatar Bo Majewski Committed by Commit Bot

Files App: Adds ability to generate PNG thumbnails for local PDF files

Exposes PDF thumbnailer to misc API. Uses it to fetch PNG bytes for
a local PDF file. Alters MakeThumbnailDataUrlOnThreadPool to accept
base::StringPiece. This allows the existing code path for drivefs
thumbnails and the new code path to re-use it.

Bug: 903742
Change-Id: I561d734a4d168c0ed96118decddab992d67889e2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2291914
Commit-Queue: Bo Majewski <majewski@chromium.org>
Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#803765}
parent a5d0bb21
...@@ -5,9 +5,8 @@ ...@@ -5,9 +5,8 @@
#include "chrome/browser/chromeos/extensions/file_manager/private_api_misc.h" #include "chrome/browser/chromeos/extensions/file_manager/private_api_misc.h"
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <set> #include <set>
#include <utility> #include <utility>
#include <vector> #include <vector>
...@@ -16,8 +15,11 @@ ...@@ -16,8 +15,11 @@
#include "base/base64.h" #include "base/base64.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/containers/span.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/i18n/encoding_detection.h" #include "base/i18n/encoding_detection.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/strings/strcat.h" #include "base/strings/strcat.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
...@@ -44,6 +46,7 @@ ...@@ -44,6 +46,7 @@
#include "chrome/browser/file_util_service.h" #include "chrome/browser/file_util_service.h"
#include "chrome/browser/lifetime/application_lifetime.h" #include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/net/system_network_context_manager.h" #include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/printing/printing_service.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profiles_state.h" #include "chrome/browser/profiles/profiles_state.h"
...@@ -68,6 +71,8 @@ ...@@ -68,6 +71,8 @@
#include "components/signin/public/identity_manager/identity_manager.h" #include "components/signin/public/identity_manager/identity_manager.h"
#include "components/user_manager/user_manager.h" #include "components/user_manager/user_manager.h"
#include "components/zoom/page_zoom.h" #include "components/zoom/page_zoom.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/common/page_zoom.h" #include "content/public/common/page_zoom.h"
#include "extensions/browser/api/file_handlers/mime_util.h" #include "extensions/browser/api/file_handlers/mime_util.h"
...@@ -78,7 +83,9 @@ ...@@ -78,7 +83,9 @@
#include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/shared_url_loader_factory.h"
#include "storage/common/file_system/file_system_types.h" #include "storage/common/file_system/file_system_types.h"
#include "storage/common/file_system/file_system_util.h" #include "storage/common/file_system/file_system_util.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/webui/web_ui_util.h" #include "ui/base/webui/web_ui_util.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace extensions { namespace extensions {
...@@ -199,13 +206,32 @@ bool IsAllowedSource(storage::FileSystemType type, ...@@ -199,13 +206,32 @@ bool IsAllowedSource(storage::FileSystemType type,
// Encodes PNG data as a dataURL. // Encodes PNG data as a dataURL.
std::string MakeThumbnailDataUrlOnThreadPool( std::string MakeThumbnailDataUrlOnThreadPool(
const std::vector<uint8_t>& png_data) { base::span<const uint8_t> png_data) {
std::string encoded; base::AssertLongCPUWorkAllowed();
base::Base64Encode( return base::StrCat({"data:image/png;base64,", base::Base64Encode(png_data)});
base::StringPiece(reinterpret_cast<const char*>(png_data.data()), }
png_data.size()),
&encoded); // The maximum size of the PDF size for which thumbnails are generated.
return base::StrCat({"data:image/png;base64,", encoded}); constexpr static uint32_t kMaxPdfSize = 1024u * 1024u;
// A function that performs IO operations to read and render PDF thumbnail
// Must be run by a blocking task runner.
std::string ReadLocalPdf(const base::FilePath& pdf_file_path) {
int64_t file_size;
if (!base::GetFileSize(pdf_file_path, &file_size)) {
DLOG(ERROR) << "Failed to get file size of " << pdf_file_path;
return std::string();
}
if (file_size > kMaxPdfSize) {
DLOG(ERROR) << "File " << pdf_file_path << " is too large " << file_size;
return std::string();
}
std::string contents;
if (!base::ReadFileToString(pdf_file_path, &contents)) {
DLOG(ERROR) << "Failed to load " << pdf_file_path;
return std::string();
}
return contents;
} }
} // namespace } // namespace
...@@ -1167,7 +1193,69 @@ FileManagerPrivateInternalGetThumbnailFunction::GetLocalThumbnail( ...@@ -1167,7 +1193,69 @@ FileManagerPrivateInternalGetThumbnailFunction::GetLocalThumbnail(
base::FilePath::CompareIgnoreCase(path.Extension(), ".pdf") != 0) { base::FilePath::CompareIgnoreCase(path.Extension(), ".pdf") != 0) {
return RespondNow(Error("Can only handle PDF files")); return RespondNow(Error("Can only handle PDF files"));
} }
return RespondNow(Error("Not implemented")); base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()}, base::BindOnce(&ReadLocalPdf, path),
base::BindOnce(
&FileManagerPrivateInternalGetThumbnailFunction::FetchPdfThumbnail,
this, crop_to_square));
return RespondLater();
}
void FileManagerPrivateInternalGetThumbnailFunction::FetchPdfThumbnail(
bool crop_to_square,
const std::string& pdf_contents) {
if (pdf_contents.empty()) {
Respond(Error("Failed to read file content"));
return;
}
if (!pdf_thumbnailer_.is_bound()) {
GetPrintingService()->BindPdfThumbnailer(
pdf_thumbnailer_.BindNewPipeAndPassReceiver());
pdf_thumbnailer_.set_disconnect_handler(
base::BindOnce(&FileManagerPrivateInternalGetThumbnailFunction::
PdfThumbnailDisconected,
base::Unretained(this)));
}
auto pdf_region =
base::ReadOnlySharedMemoryRegion::Create(pdf_contents.size());
memcpy(pdf_region.mapping.memory(), pdf_contents.data(), pdf_contents.size());
gfx::Size thumb_size =
crop_to_square
? gfx::Size(FileManagerPrivateInternalGetThumbnailFunction::kSize,
FileManagerPrivateInternalGetThumbnailFunction::kSize)
: gfx::Size(FileManagerPrivateInternalGetThumbnailFunction::kWidth,
FileManagerPrivateInternalGetThumbnailFunction::kHeight);
auto params = printing::mojom::ThumbParams::New(
thumb_size,
gfx::Size(FileManagerPrivateInternalGetThumbnailFunction::kDpi,
FileManagerPrivateInternalGetThumbnailFunction::kDpi),
/*stretch_to_bounds=*/false, /*keep_aspect_ratio=*/true);
pdf_thumbnailer_->GetThumbnail(
std::move(params), std::move(pdf_region.region),
base::BindOnce(
&FileManagerPrivateInternalGetThumbnailFunction::GotPdfThumbnail,
this));
}
void FileManagerPrivateInternalGetThumbnailFunction::PdfThumbnailDisconected() {
DLOG(WARNING) << "PDF thumbnail disconnected";
Respond(Error("PDF service disconnected"));
}
void FileManagerPrivateInternalGetThumbnailFunction::GotPdfThumbnail(
const SkBitmap& bitmap) {
if (bitmap.isNull()) {
Respond(Error("Failed to render the thumbnail"));
return;
}
sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
sk_sp<SkData> png_data(image->encodeToData(SkEncodedImageFormat::kPNG, 100));
if (!png_data) {
Respond(Error("Thumbnail encoding error"));
return;
}
SendEncodedThumbnail(MakeThumbnailDataUrlOnThreadPool(base::make_span(
reinterpret_cast<const uint8_t*>(png_data->data()), png_data->size())));
} }
ExtensionFunction::ResponseAction ExtensionFunction::ResponseAction
...@@ -1199,21 +1287,25 @@ FileManagerPrivateInternalGetThumbnailFunction::GetDrivefsThumbnail( ...@@ -1199,21 +1287,25 @@ FileManagerPrivateInternalGetThumbnailFunction::GetDrivefsThumbnail(
drivefs_interface->GetThumbnail( drivefs_interface->GetThumbnail(
path, crop_to_square, path, crop_to_square,
mojo::WrapCallbackWithDefaultInvokeIfNotRun( mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce( base::BindOnce(&FileManagerPrivateInternalGetThumbnailFunction::
&FileManagerPrivateInternalGetThumbnailFunction::GotThumbnail, GotDriveThumbnail,
this), this),
base::Optional<std::vector<uint8_t>>())); base::Optional<std::vector<uint8_t>>()));
return RespondLater(); return RespondLater();
} }
void FileManagerPrivateInternalGetThumbnailFunction::GotThumbnail( void FileManagerPrivateInternalGetThumbnailFunction::GotDriveThumbnail(
const base::Optional<std::vector<uint8_t>>& data) { const base::Optional<std::vector<uint8_t>>& data) {
if (!data) { if (!data) {
Respond(OneArgument(std::make_unique<base::Value>(""))); Respond(OneArgument(std::make_unique<base::Value>("")));
return; return;
} }
base::ThreadPool::PostTaskAndReplyWithResult( base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&MakeThumbnailDataUrlOnThreadPool, *data), FROM_HERE,
base::BindOnce(
&MakeThumbnailDataUrlOnThreadPool,
base::make_span(reinterpret_cast<const uint8_t*>(data->data()),
data->size())),
base::BindOnce( base::BindOnce(
&FileManagerPrivateInternalGetThumbnailFunction::SendEncodedThumbnail, &FileManagerPrivateInternalGetThumbnailFunction::SendEncodedThumbnail,
this)); this));
......
...@@ -18,9 +18,13 @@ ...@@ -18,9 +18,13 @@
#include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h" #include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h"
#include "chrome/browser/extensions/chrome_extension_function_details.h" #include "chrome/browser/extensions/chrome_extension_function_details.h"
#include "chrome/common/extensions/api/file_manager_private.h" #include "chrome/common/extensions/api/file_manager_private.h"
#include "chrome/services/printing/public/mojom/pdf_thumbnailer.mojom.h"
#include "google_apis/drive/drive_api_error_codes.h" #include "google_apis/drive/drive_api_error_codes.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "storage/browser/file_system/file_system_url.h" #include "storage/browser/file_system/file_system_url.h"
class SkBitmap;
namespace chromeos { namespace chromeos {
class RecentFile; class RecentFile;
} // namespace chromeos } // namespace chromeos
...@@ -490,16 +494,46 @@ class FileManagerPrivateInternalGetThumbnailFunction ...@@ -490,16 +494,46 @@ class FileManagerPrivateInternalGetThumbnailFunction
const storage::FileSystemURL& url, const storage::FileSystemURL& url,
bool crop_to_square); bool crop_to_square);
// A function that performs IO operations to read and render PDF thumbnail // For a given |pdf_contents| starts fetching the first page PDF thumbnail by
// Must be run on the IO Thread. // calling PdfThumbnailer from the printing service. The first parameter,
void GetLocalThumbnailOnIOThread(const base::FilePath& path, // |crop_to_square| is supplied by the JavaScript caller.
bool crop_to_square); void FetchPdfThumbnail(bool crop_to_square, const std::string& pdf_contents);
// Callback invoked by the thumbnailing service when a PDF thumbnail has been
// generated. The solitary parameter |bitmap| is supplied by the callback.
// If |bitmap| is null, an error occurred. Otherwise, |bitmap| contains the
// generated thumbnail.
void GotPdfThumbnail(const SkBitmap& bitmap);
// Handles a mojo channel disconnect event.
void PdfThumbnailDisconected();
// A callback invoked when thumbnail data has been generated. // A callback invoked when thumbnail data has been generated.
void GotThumbnail(const base::Optional<std::vector<uint8_t>>& data); void GotDriveThumbnail(const base::Optional<std::vector<uint8_t>>& data);
// Responds with a base64 encoded PNG thumbnail data. // Responds with a base64 encoded PNG thumbnail data.
void SendEncodedThumbnail(std::string thumbnail_data_url); void SendEncodedThumbnail(std::string thumbnail_data_url);
// Holds the channel to Printing PDF thumbnailing service. Bound only
// when needed.
mojo::Remote<printing::mojom::PdfThumbnailer> pdf_thumbnailer_;
// The dots per inch (dpi) resolution at which the PDF is rendered to a
// thumbnail. The value of 30 is selected so that a US Letter size page does
// not overflow a kSize x kSize thumbnail.
constexpr static int kDpi = 30;
// The default size if we are asked to generate a square thumbnail. The
// value is set to match chromeos/components/drivefs/mojom/drivefs.mojom
constexpr static int kSize = 360;
// The default width if we are asked to generate a non-square thumbnail. The
// value is set to match chromeos/components/drivefs/mojom/drivefs.mojom
constexpr static int kWidth = 500;
// The default height if we are asked to generate a non-square thumbnail. The
// value is set to match chromeos/components/drivefs/mojom/drivefs.mojom
constexpr static int kHeight = 500;
}; };
} // namespace extensions } // namespace extensions
......
...@@ -586,6 +586,15 @@ FileType.isRaw = (entry, opt_mimeType) => { ...@@ -586,6 +586,15 @@ FileType.isRaw = (entry, opt_mimeType) => {
return FileType.getMediaType(entry, opt_mimeType) === 'raw'; return FileType.getMediaType(entry, opt_mimeType) === 'raw';
}; };
/**
* @param {Entry} entry Reference to the file
* @param {string=} opt_mimeType Optional mime type for this file.
* @return {boolean} Whether or not this is a PDF file.
*/
FileType.isPDF = (entry, opt_mimeType) => {
return FileType.getType(entry, opt_mimeType).subtype === 'PDF';
};
/** /**
* Files with more pixels won't have preview. * Files with more pixels won't have preview.
* @param {!Array<string>} types * @param {!Array<string>} types
......
...@@ -105,7 +105,8 @@ class ThumbnailLoader { ...@@ -105,7 +105,8 @@ class ThumbnailLoader {
case ThumbnailLoader.LoadTarget.FILE_ENTRY: case ThumbnailLoader.LoadTarget.FILE_ENTRY:
if (FileType.isImage(entry, mimeType) || if (FileType.isImage(entry, mimeType) ||
FileType.isVideo(entry, mimeType) || FileType.isVideo(entry, mimeType) ||
FileType.isRaw(entry, mimeType)) { FileType.isRaw(entry, mimeType) ||
FileType.isPDF(entry, mimeType)) {
this.thumbnailUrl_ = entry.toURL(); this.thumbnailUrl_ = entry.toURL();
this.transform_ = this.transform_ =
opt_metadata.media && opt_metadata.media.imageTransform; opt_metadata.media && opt_metadata.media.imageTransform;
......
...@@ -255,6 +255,37 @@ ImageRequestTask.prototype.saveToCache_ = function(width, height, data) { ...@@ -255,6 +255,37 @@ ImageRequestTask.prototype.saveToCache_ = function(width, height, data) {
this.cache_.saveImage(cacheKey, timestamp, width, height, this.ifd_, data); this.cache_.saveImage(cacheKey, timestamp, width, height, this.ifd_, data);
}; };
/**
* Gets a file thumb from the browser hosted environment. If the thumbnail
* is returned it is assigned to this.image_ instance variable, which ultimately
* results in onsuccess function associated with the image being called.
* @param {string} url The URL of the file entry for which we get a thumbnail.
* @param {function()} onFailure a callback invoked if errors occur.
*/
ImageRequestTask.prototype.getExternalThumbnail = function(url, onFailure) {
window.webkitResolveLocalFileSystemURL(
url,
entry => {
chrome.fileManagerPrivate.getThumbnail(
/** @type {FileEntry} */ (entry), !!this.request_.crop,
thumbnail => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
onFailure();
} else if (thumbnail) {
this.image_.src = thumbnail;
this.contentType_ = 'image/png';
} else {
onFailure();
}
});
},
error => {
console.error(error);
onFailure();
});
};
/** /**
* Downloads an image directly or for remote resources using the XmlHttpRequest. * Downloads an image directly or for remote resources using the XmlHttpRequest.
* *
...@@ -281,23 +312,11 @@ ImageRequestTask.prototype.downloadOriginal_ = function(onSuccess, onFailure) { ...@@ -281,23 +312,11 @@ ImageRequestTask.prototype.downloadOriginal_ = function(onSuccess, onFailure) {
} }
const drivefsUrlMatches = this.request_.url.match(/^drivefs:(.*)/); const drivefsUrlMatches = this.request_.url.match(/^drivefs:(.*)/);
if (drivefsUrlMatches) { if (drivefsUrlMatches) {
window.webkitResolveLocalFileSystemURL( this.getExternalThumbnail(drivefsUrlMatches[1], onFailure);
drivefsUrlMatches[1], return;
entry => { }
chrome.fileManagerPrivate.getThumbnail( if (this.request_.url.endsWith('.pdf')) {
/** @type {FileEntry} */ (entry), !!this.request_.crop, this.getExternalThumbnail(this.request_.url, onFailure);
thumbnail => {
if (!thumbnail) {
onFailure();
return;
}
this.image_.src = thumbnail;
this.contentType_ = 'image/png';
});
},
error => {
onFailure();
});
return; return;
} }
......
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