Add support for loading extensions (issue #1947)

- Add CefRequestContext::LoadExtension, CefExtension, CefExtensionHandler and
  related methods/interfaces.
- Add chrome://extensions-support that lists supported Chrome APIs.
- Add CefBrowserHost::SetAutoResizeEnabled and CefDisplayHandler::OnAutoResize
  to support browser resize based on preferred web contents size.
- views: Add support for custom CefMenuButton popups.
- cefclient: Run with `--load-extension=set_page_color` command-line flag for
  an extension loading example. Add `--use-views` on Windows and Linux for an
  even better example.
This commit is contained in:
Marshall Greenblatt
2017-08-03 18:55:19 -04:00
parent 5b12134a45
commit 9cff99dc4e
178 changed files with 10360 additions and 650 deletions

View File

@@ -0,0 +1,239 @@
// Copyright (c) 2017 The Chromium Embedded Framework 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 "tests/shared/browser/extension_util.h"
#include "include/base/cef_bind.h"
#include "include/cef_parser.h"
#include "include/cef_path_util.h"
#include "include/wrapper/cef_closure_task.h"
#include "tests/shared/browser/file_util.h"
#include "tests/shared/browser/resource_util.h"
namespace client {
namespace extension_util {
namespace {
std::string GetResourcesPath() {
CefString resources_dir;
if (CefGetPath(PK_DIR_RESOURCES, resources_dir) && !resources_dir.empty()) {
return resources_dir.ToString() + file_util::kPathSep;
}
return std::string();
}
// Internal extension paths may be prefixed with PK_DIR_RESOURCES and always
// use forward slash as path separator.
std::string GetInternalPath(const std::string& extension_path) {
const std::string& resources_path = GetResourcesPath();
std::string internal_path;
if (!resources_path.empty() && extension_path.find(resources_path) == 0U) {
internal_path = extension_path.substr(resources_path.size());
} else {
internal_path = extension_path;
}
#if defined(OS_WIN)
// Normalize path separators.
std::replace(internal_path.begin(), internal_path.end(), '\\', '/');
#endif
return internal_path;
}
typedef base::Callback<void(CefRefPtr<CefDictionaryValue> /*manifest*/)>
ManifestCallback;
void RunManifestCallback(const ManifestCallback& callback,
CefRefPtr<CefDictionaryValue> manifest) {
if (!CefCurrentlyOn(TID_UI)) {
// Execute on the browser UI thread.
CefPostTask(TID_UI, base::Bind(RunManifestCallback, callback, manifest));
return;
}
callback.Run(manifest);
}
// Asynchronously reads the manifest and executes |callback| on the UI thread.
void GetInternalManifest(const std::string& extension_path,
const ManifestCallback& callback) {
if (!CefCurrentlyOn(TID_FILE)) {
// Execute on the browser FILE thread.
CefPostTask(TID_FILE,
base::Bind(GetInternalManifest, extension_path, callback));
return;
}
const std::string& manifest_path = GetInternalExtensionResourcePath(
file_util::JoinPath(extension_path, "manifest.json"));
std::string manifest_contents;
if (!LoadBinaryResource(manifest_path.c_str(), manifest_contents) ||
manifest_contents.empty()) {
LOG(ERROR) << "Failed to load manifest from " << manifest_path;
RunManifestCallback(callback, NULL);
return;
}
cef_json_parser_error_t error_code;
CefString error_msg;
CefRefPtr<CefValue> value = CefParseJSONAndReturnError(
manifest_contents, JSON_PARSER_RFC, error_code, error_msg);
if (!value || value->GetType() != VTYPE_DICTIONARY) {
if (error_msg.empty())
error_msg = "Incorrectly formatted dictionary contents.";
LOG(ERROR) << "Failed to parse manifest from " << manifest_path << "; "
<< error_msg.ToString();
RunManifestCallback(callback, NULL);
return;
}
RunManifestCallback(callback, value->GetDictionary());
}
void LoadExtensionWithManifest(CefRefPtr<CefRequestContext> request_context,
const std::string& extension_path,
CefRefPtr<CefExtensionHandler> handler,
CefRefPtr<CefDictionaryValue> manifest) {
CEF_REQUIRE_UI_THREAD();
// Load the extension internally. Resource requests will be handled via
// AddInternalExtensionToResourceManager.
request_context->LoadExtension(extension_path, manifest, handler);
}
} // namespace
bool IsInternalExtension(const std::string& extension_path) {
// List of internally handled extensions.
static const char* extensions[] = {"set_page_color"};
const std::string& internal_path = GetInternalPath(extension_path);
for (size_t i = 0; i < arraysize(extensions); ++i) {
// Exact match or first directory component.
const std::string& extension = extensions[i];
if (internal_path == extension ||
internal_path.find(extension + '/') == 0) {
return true;
}
}
return false;
}
std::string GetInternalExtensionResourcePath(
const std::string& extension_path) {
return "extensions/" + GetInternalPath(extension_path);
}
std::string GetExtensionResourcePath(const std::string& extension_path,
bool* internal) {
const bool is_internal = IsInternalExtension(extension_path);
if (internal)
*internal = is_internal;
if (is_internal)
return GetInternalExtensionResourcePath(extension_path);
return extension_path;
}
bool GetExtensionResourceContents(const std::string& extension_path,
std::string& contents) {
CEF_REQUIRE_FILE_THREAD();
if (IsInternalExtension(extension_path)) {
const std::string& contents_path =
GetInternalExtensionResourcePath(extension_path);
return LoadBinaryResource(contents_path.c_str(), contents);
}
return file_util::ReadFileToString(extension_path, &contents);
}
void LoadExtension(CefRefPtr<CefRequestContext> request_context,
const std::string& extension_path,
CefRefPtr<CefExtensionHandler> handler) {
if (!CefCurrentlyOn(TID_UI)) {
// Execute on the browser UI thread.
CefPostTask(TID_UI, base::Bind(LoadExtension, request_context,
extension_path, handler));
return;
}
if (IsInternalExtension(extension_path)) {
// Read the extension manifest and load asynchronously.
GetInternalManifest(extension_path,
base::Bind(LoadExtensionWithManifest, request_context,
extension_path, handler));
} else {
// Load the extension from disk.
request_context->LoadExtension(extension_path, NULL, handler);
}
}
void AddInternalExtensionToResourceManager(
CefRefPtr<CefExtension> extension,
CefRefPtr<CefResourceManager> resource_manager) {
DCHECK(IsInternalExtension(extension->GetPath()));
if (!CefCurrentlyOn(TID_IO)) {
// Execute on the browser IO thread.
CefPostTask(TID_IO, base::Bind(AddInternalExtensionToResourceManager,
extension, resource_manager));
return;
}
const std::string& origin = GetExtensionOrigin(extension->GetIdentifier());
const std::string& resource_path =
GetInternalExtensionResourcePath(extension->GetPath());
// Add provider for bundled resource files.
#if defined(OS_WIN)
// Read resources from the binary.
resource_manager->AddProvider(
CreateBinaryResourceProvider(origin, resource_path), 50, std::string());
#elif defined(OS_POSIX)
// Read resources from a directory on disk.
std::string resource_dir;
if (GetResourceDir(resource_dir)) {
resource_dir += "/" + resource_path;
resource_manager->AddDirectoryProvider(origin, resource_dir, 50,
std::string());
}
#endif
}
std::string GetExtensionOrigin(const std::string& extension_id) {
return "chrome-extension://" + extension_id + "/";
}
std::string GetExtensionURL(CefRefPtr<CefExtension> extension) {
CefRefPtr<CefDictionaryValue> browser_action =
extension->GetManifest()->GetDictionary("browser_action");
if (browser_action) {
const std::string& default_popup =
browser_action->GetString("default_popup");
if (!default_popup.empty())
return GetExtensionOrigin(extension->GetIdentifier()) + default_popup;
}
return std::string();
}
std::string GetExtensionIconPath(CefRefPtr<CefExtension> extension,
bool* internal) {
CefRefPtr<CefDictionaryValue> browser_action =
extension->GetManifest()->GetDictionary("browser_action");
if (browser_action) {
const std::string& default_icon = browser_action->GetString("default_icon");
if (!default_icon.empty()) {
return GetExtensionResourcePath(
file_util::JoinPath(extension->GetPath(), default_icon), internal);
}
}
return std::string();
}
} // namespace extension_util
} // namespace client

View File

@@ -0,0 +1,80 @@
// Copyright (c) 2017 The Chromium Embedded Framework 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 CEF_TESTS_CEFCLIENT_BROWSER_EXTENSION_UTIL_H_
#define CEF_TESTS_CEFCLIENT_BROWSER_EXTENSION_UTIL_H_
#pragma once
#include <string>
#include "include/cef_extension.h"
#include "include/cef_extension_handler.h"
#include "include/wrapper/cef_resource_manager.h"
namespace client {
namespace extension_util {
// Returns true if |extension_path| can be handled internally via
// LoadBinaryResource. This checks a hard-coded list of allowed extension path
// components.
bool IsInternalExtension(const std::string& extension_path);
// Returns the path relative to the resource directory after removing the
// PK_DIR_RESOURCES prefix. This will be the relative path expected by
// LoadBinaryResource (uses '/' as path separator on all platforms). Only call
// this method for internal extensions, either when IsInternalExtension returns
// true or when the extension is handled internally through some means other
// than LoadBinaryResource. Use GetExtensionResourcePath instead if you are
// unsure whether the extension is internal or external.
std::string GetInternalExtensionResourcePath(const std::string& extension_path);
// Returns the resource path for |extension_path|. For external extensions this
// will be the full file path on disk. For internal extensions this will be the
// relative path expected by LoadBinaryResource (uses '/' as path separator on
// all platforms). Internal extensions must be on the hard-coded list enforced
// by IsInternalExtension. If |internal| is non-NULL it will be set to true if
// the extension is handled internally.
std::string GetExtensionResourcePath(const std::string& extension_path,
bool* internal);
// Read the contents of |extension_path| into |contents|. For external
// extensions this will read the file from disk. For internal extensions this
// will call LoadBinaryResource. Internal extensions must be on the hard-coded
// list enforced by IsInternalExtension. Returns true on success. Must be
// called on the FILE thread.
bool GetExtensionResourceContents(const std::string& extension_path,
std::string& contents);
// Load |extension_path| in |request_context|. May be an internal or external
// extension. Internal extensions must be on the hard-coded list enforced by
// IsInternalExtension.
void LoadExtension(CefRefPtr<CefRequestContext> request_context,
const std::string& extension_path,
CefRefPtr<CefExtensionHandler> handler);
// Register an internal handler for extension resources. Internal extensions
// must be on the hard-coded list enforced by IsInternalExtension.
void AddInternalExtensionToResourceManager(
CefRefPtr<CefExtension> extension,
CefRefPtr<CefResourceManager> resource_manager);
// Returns the URL origin for |extension_id|.
std::string GetExtensionOrigin(const std::string& extension_id);
// Parse browser_action manifest values as defined at
// https://developer.chrome.com/extensions/browserAction
// Look for a browser_action.default_popup manifest value.
std::string GetExtensionURL(CefRefPtr<CefExtension> extension);
// Look for a browser_action.default_icon manifest value and return the resource
// path. If |internal| is non-NULL it will be set to true if the extension is
// handled internally.
std::string GetExtensionIconPath(CefRefPtr<CefExtension> extension,
bool* internal);
} // namespace extension_util
} // namespace client
#endif // CEF_TESTS_CEFCLIENT_BROWSER_EXTENSION_UTIL_H_

View File

@@ -0,0 +1,121 @@
// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright
// 2012 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 "tests/shared/browser/file_util.h"
#include "include/base/cef_build.h"
#include "include/base/cef_scoped_ptr.h"
#include "include/cef_task.h"
#include <algorithm>
#include <cstdio>
#include <memory>
namespace client {
namespace file_util {
namespace {
bool AllowFileIO() {
if (CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO)) {
NOTREACHED() << "file IO is not allowed on the current thread";
return false;
}
return true;
}
} // namespace
#if defined(OS_WIN)
const char kPathSep = '\\';
#else
const char kPathSep = '/';
#endif
bool ReadFileToString(const std::string& path,
std::string* contents,
size_t max_size) {
if (!AllowFileIO())
return false;
if (contents)
contents->clear();
FILE* file = fopen(path.c_str(), "rb");
if (!file)
return false;
const size_t kBufferSize = 1 << 16;
scoped_ptr<char[]> buf(new char[kBufferSize]);
size_t len;
size_t size = 0;
bool read_status = true;
// Many files supplied in |path| have incorrect size (proc files etc).
// Hence, the file is read sequentially as opposed to a one-shot read.
while ((len = fread(buf.get(), 1, kBufferSize, file)) > 0) {
if (contents)
contents->append(buf.get(), std::min(len, max_size - size));
if ((max_size - size) < len) {
read_status = false;
break;
}
size += len;
}
read_status = read_status && !ferror(file);
fclose(file);
return read_status;
}
int WriteFile(const std::string& path, const char* data, int size) {
if (!AllowFileIO())
return -1;
FILE* file = fopen(path.c_str(), "wb");
if (!file)
return -1;
int written = 0;
do {
size_t write = fwrite(data + written, 1, size - written, file);
if (write == 0)
break;
written += static_cast<int>(write);
} while (written < size);
fclose(file);
return written;
}
std::string JoinPath(const std::string& path1, const std::string& path2) {
if (path1.empty() && path2.empty())
return std::string();
if (path1.empty())
return path2;
if (path2.empty())
return path1;
std::string result = path1;
if (result[result.size() - 1] != kPathSep)
result += kPathSep;
if (path2[0] == kPathSep)
result += path2.substr(1);
else
result += path2;
return result;
}
std::string GetFileExtension(const std::string& path) {
size_t sep = path.find_last_of(".");
if (sep != std::string::npos)
return path.substr(sep + 1);
return std::string();
}
} // namespace file_util
} // namespace client

View File

@@ -0,0 +1,45 @@
// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright
// 2012 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 CEF_TESTS_SHARED_BROWSER_FILE_UTIL_H_
#define CEF_TESTS_SHARED_BROWSER_FILE_UTIL_H_
#pragma once
#include <limits>
#include <string>
namespace client {
namespace file_util {
// Platform-specific path separator.
extern const char kPathSep;
// Reads the file at |path| into |contents| and returns true on success and
// false on error. In case of I/O error, |contents| holds the data that could
// be read from the file before the error occurred. When the file size exceeds
// max_size|, the function returns false with |contents| holding the file
// truncated to |max_size|. |contents| may be NULL, in which case this function
// is useful for its side effect of priming the disk cache (could be used for
// unit tests). Calling this function on the browser process UI or IO threads is
// not allowed.
bool ReadFileToString(const std::string& path,
std::string* contents,
size_t max_size = std::numeric_limits<size_t>::max());
// Writes the given buffer into the file, overwriting any data that was
// previously there. Returns the number of bytes written, or -1 on error.
// Calling this function on the browser process UI or IO threads is not allowed.
int WriteFile(const std::string& path, const char* data, int size);
// Combines |path1| and |path2| with the correct platform-specific path
// separator.
std::string JoinPath(const std::string& path1, const std::string& path2);
// Extracts the file extension from |path|.
std::string GetFileExtension(const std::string& path);
} // namespace file_util
} // namespace client
#endif // CEF_TESTS_SHARED_BROWSER_FILE_UTIL_H_

View File

@@ -1,32 +0,0 @@
// Copyright (c) 2013 The Chromium Embedded Framework 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 "tests/shared/browser/resource_util.h"
#include <algorithm>
#include <string>
#include "include/base/cef_logging.h"
namespace client {
CefRefPtr<CefImage> LoadImageIcon(const char* icon_name) {
CefRefPtr<CefImage> icon = CefImage::CreateImage();
std::string image, resource;
resource = std::string(icon_name) + ".1x.png";
if (LoadBinaryResource(resource.c_str(), image))
icon->AddPNG(1.0f, image.c_str(), image.size());
resource = std::string(icon_name) + ".2x.png";
if (LoadBinaryResource(resource.c_str(), image))
icon->AddPNG(2.0f, image.c_str(), image.size());
// Icons must be 16 in DIP.
DCHECK_EQ(16U, std::max(icon->GetWidth(), icon->GetHeight()));
return icon;
}
} // namespace client

View File

@@ -30,15 +30,10 @@ CefRefPtr<CefStreamReader> GetBinaryResourceReader(const char* resource_name);
#if defined(OS_WIN)
// Create a new provider for loading binary resources.
CefResourceManager::Provider* CreateBinaryResourceProvider(
const std::string& url_path);
const std::string& url_path,
const std::string& resource_path_prefix);
#endif
// Load an image icon at different scale factors. The image representations are
// expected to be 16 DIP in size. For example, if |icon_name| is "image" then
// the expected file names are "image.1x.png" for the 1x scale image (16 pixels)
// and "image.2x.png" for the 2x scale image (32 pixels).
CefRefPtr<CefImage> LoadImageIcon(const char* icon_name);
} // namespace client
#endif // CEF_TESTS_SHARED_BROWSER_RESOURCE_UTIL_H_

View File

@@ -33,9 +33,14 @@ bool LoadBinaryResource(int binaryId, DWORD& dwSize, LPBYTE& pBytes) {
// Provider of binary resources.
class BinaryResourceProvider : public CefResourceManager::Provider {
public:
explicit BinaryResourceProvider(const std::string& url_path)
: url_path_(url_path) {
BinaryResourceProvider(const std::string& url_path,
const std::string& resource_path_prefix)
: url_path_(url_path), resource_path_prefix_(resource_path_prefix) {
DCHECK(!url_path.empty());
if (!resource_path_prefix_.empty() &&
resource_path_prefix_[resource_path_prefix_.length() - 1] != '/') {
resource_path_prefix_ += "/";
}
}
bool OnRequest(scoped_refptr<CefResourceManager::Request> request) OVERRIDE {
@@ -49,8 +54,11 @@ class BinaryResourceProvider : public CefResourceManager::Provider {
CefRefPtr<CefResourceHandler> handler;
const std::string& relative_path = url.substr(url_path_.length());
std::string relative_path = url.substr(url_path_.length());
if (!relative_path.empty()) {
if (!resource_path_prefix_.empty())
relative_path = resource_path_prefix_ + relative_path;
CefRefPtr<CefStreamReader> stream =
GetBinaryResourceReader(relative_path.data());
if (stream.get()) {
@@ -65,6 +73,7 @@ class BinaryResourceProvider : public CefResourceManager::Provider {
private:
std::string url_path_;
std::string resource_path_prefix_;
DISALLOW_COPY_AND_ASSIGN(BinaryResourceProvider);
};
@@ -109,8 +118,9 @@ CefRefPtr<CefStreamReader> GetBinaryResourceReader(const char* resource_name) {
}
CefResourceManager::Provider* CreateBinaryResourceProvider(
const std::string& url_path) {
return new BinaryResourceProvider(url_path);
const std::string& url_path,
const std::string& resource_path_prefix) {
return new BinaryResourceProvider(url_path, resource_path_prefix);
}
} // namespace client

View File

@@ -38,6 +38,7 @@ const char kHideTopMenu[] = "hide-top-menu";
const char kWidevineCdmPath[] = "widevine-cdm-path";
const char kSslClientCertificate[] = "ssl-client-certificate";
const char kCRLSetsPath[] = "crl-sets-path";
const char kLoadExtension[] = "load-extension";
} // namespace switches
} // namespace client

View File

@@ -32,6 +32,7 @@ extern const char kHideTopMenu[];
extern const char kWidevineCdmPath[];
extern const char kSslClientCertificate[];
extern const char kCRLSetsPath[];
extern const char kLoadExtension[];
} // namespace switches
} // namespace client