blob: 6f71f1effae86cfae42019747307e124f29b8f78 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/common/api/extension_action/action_info.h"
#include <memory>
#include "base/files/file_path.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/icons/extension_icon_variants.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handler_helpers.h"
#include "extensions/common/manifest_handlers/icon_variants_handler.h"
namespace extensions {
namespace errors = manifest_errors;
namespace keys = manifest_keys;
namespace {
constexpr char kEnabled[] = "enabled";
constexpr char kDisabled[] = "disabled";
// The manifest data container for the ActionInfos for BrowserActions and
// PageActions.
struct ActionInfoData : public Extension::ManifestData {
explicit ActionInfoData(std::unique_ptr<ActionInfo> action_info);
~ActionInfoData() override;
// The action associated with the BrowserAction.
std::unique_ptr<ActionInfo> action_info;
};
ActionInfoData::ActionInfoData(std::unique_ptr<ActionInfo> info)
: action_info(std::move(info)) {}
ActionInfoData::~ActionInfoData() = default;
using extensions::diagnostics::icon_variants::Feature;
using extensions::diagnostics::icon_variants::Id;
using extensions::diagnostics::icon_variants::Severity;
// Returns the icon variants parsed from the `extension` manifest.
// Populates `error` if there are no icon variants.
ExtensionIconVariants GetIconVariants(const base::Value* value) {
ExtensionIconVariants icon_variants;
// Convert the input key into a list containing everything.
if (!value->is_list()) {
icon_variants.AddDiagnostic(Feature::kIconVariants,
Id::kIconVariantsKeyMustBeAList);
return icon_variants;
}
icon_variants.Parse(&value->GetList());
// Verify `icon_variants`, e.g. that at least one `icon_variant` is valid.
if (icon_variants.IsEmpty()) {
icon_variants.AddDiagnostic(Feature::kIconVariants,
Id::kIconVariantsInvalid);
}
return icon_variants;
}
} // namespace
ActionInfo::ActionInfo(Type type) : type(type), synthesized(false) {
switch (type) {
case ActionInfo::Type::kPage:
default_state = ActionInfo::DefaultState::kDisabled;
break;
case ActionInfo::Type::kBrowser:
case ActionInfo::Type::kAction:
default_state = ActionInfo::DefaultState::kEnabled;
break;
}
}
ActionInfo::ActionInfo(ActionInfo&& other) = default;
ActionInfo::ActionInfo(const ActionInfo& other) = default;
ActionInfo::~ActionInfo() = default;
// static
std::unique_ptr<ActionInfo> ActionInfo::Load(
const Extension* extension,
Type type,
const base::Value::Dict& dict,
std::vector<InstallWarning>* install_warnings,
std::u16string* error) {
auto result = std::make_unique<ActionInfo>(type);
// Read the action `default_icon` (optional).
// The `default_icon` value can be either dictionary {icon size -> icon path}
// or non empty string value.
if (const base::Value* default_icon = dict.Find(keys::kActionDefaultIcon)) {
std::string default_icon_str;
if (default_icon->is_string())
default_icon_str = default_icon->GetString();
if (default_icon->is_dict()) {
std::vector<std::string> warnings;
if (!manifest_handler_helpers::LoadIconsFromDictionary(
default_icon->GetDict(), &result->default_icon, error,
&warnings)) {
return nullptr;
}
if (!warnings.empty()) {
install_warnings->reserve(install_warnings->size() + warnings.size());
std::string manifest_key = GetManifestKeyForActionType(type);
for (const auto& warning : warnings) {
install_warnings->emplace_back(warning, manifest_key,
keys::kActionDefaultIcon);
}
}
} else if (default_icon->is_string() &&
manifest_handler_helpers::NormalizeAndValidatePath(
&default_icon_str)) {
if (manifest_handler_helpers::IsIconMimeTypeValid(
base::FilePath::FromUTF8Unsafe(default_icon_str))) {
// Choose the most optimistic (highest) icon density regardless of the
// actual icon resolution, whatever that happens to be. Code elsewhere
// knows how to scale down to 19.
result->default_icon.Add(extension_misc::EXTENSION_ICON_GIGANTOR,
default_icon_str);
} else {
// Issue a warning and ignore this file. This is a warning and not a
// hard-error to preserve both backwards compatibility and potential
// future-compatibility if mime types change.
install_warnings->emplace_back(
manifest_errors::kInvalidActionDefaultIconMimeType,
GetManifestKeyForActionType(type), keys::kActionDefaultIcon);
}
} else {
*error = errors::kInvalidActionDefaultIcon;
return nullptr;
}
}
// Read the action title from `default_title` if present, `name` if not
// (both optional).
if (const base::Value* default_title = dict.Find(keys::kActionDefaultTitle)) {
if (!default_title->is_string()) {
*error = errors::kInvalidActionDefaultTitle;
return nullptr;
}
result->default_title = default_title->GetString();
}
// Read the action's default popup (optional).
if (const base::Value* default_popup = dict.Find(keys::kActionDefaultPopup)) {
const std::string* url_str = default_popup->GetIfString();
if (!url_str) {
*error = errors::kInvalidActionDefaultPopup;
return nullptr;
}
if (!url_str->empty()) {
GURL popup_url = extension->GetResourceURL(*url_str);
if (!popup_url.is_valid()) {
*error = errors::kInvalidActionDefaultPopup;
return nullptr;
}
result->default_popup_url = popup_url;
} else {
// An empty string is treated as having no popup.
DCHECK(result->default_popup_url.is_empty());
}
}
if (const base::Value* default_state = dict.Find(keys::kActionDefaultState)) {
// The `default_state` key is only valid for TYPE_ACTION; throw an error for
// others.
if (type != ActionInfo::Type::kAction) {
*error = errors::kDefaultStateShouldNotBeSet;
return nullptr;
}
if (!default_state->is_string() ||
!(default_state->GetString() == kEnabled ||
default_state->GetString() == kDisabled)) {
*error = errors::kInvalidActionDefaultState;
return nullptr;
}
result->default_state = default_state->GetString() == kEnabled
? ActionInfo::DefaultState::kEnabled
: ActionInfo::DefaultState::kDisabled;
}
bool supports_icon_variants =
IconVariantsInfo::SupportsIconVariants(*extension);
const base::Value* icon_variants_value = dict.Find(keys::kIconVariants);
if (icon_variants_value) {
if (!supports_icon_variants) {
auto diagnostic = extensions::diagnostics::icon_variants::GetDiagnostic(
Feature::kIconVariants, Id::kIconVariantsNotEnabled);
install_warnings->emplace_back(diagnostic.message);
} else {
ExtensionIconVariants icon_variants =
GetIconVariants(icon_variants_value);
// Add any install warnings, handle errors, and then clear out
// diagnostics.
auto& diagnostics = icon_variants.get_diagnostics();
for (const auto& diagnostic : diagnostics) {
switch (diagnostic.severity) {
case Severity::kWarning:
// Add an install warning.
install_warnings->emplace_back(diagnostic.message);
break;
case Severity::kError:
// If any error exists, do not load the extension.
*error = base::UTF8ToUTF16(diagnostic.message);
return nullptr;
default:
NOTREACHED();
}
install_warnings->emplace_back(diagnostic.message);
}
diagnostics.clear();
result->icon_variants = std::move(icon_variants);
}
}
return result;
}
// static
const ActionInfo* ActionInfo::GetExtensionActionInfo(
const Extension* extension) {
const ActionInfoData* data =
static_cast<ActionInfoData*>(extension->GetManifestData(keys::kAction));
return data ? data->action_info.get() : nullptr;
}
// static
void ActionInfo::SetExtensionActionInfo(Extension* extension,
std::unique_ptr<ActionInfo> info) {
// Note: we store all actions (actions, browser actions, and page actions)
// under the same key for simplicity because they are mutually exclusive,
// and most callers shouldn't care about the type.
extension->SetManifestData(keys::kAction,
std::make_unique<ActionInfoData>(std::move(info)));
}
// static
const char* ActionInfo::GetManifestKeyForActionType(ActionInfo::Type type) {
const char* action_key = nullptr;
switch (type) {
case ActionInfo::Type::kBrowser:
action_key = manifest_keys::kBrowserAction;
break;
case ActionInfo::Type::kPage:
action_key = manifest_keys::kPageAction;
break;
case ActionInfo::Type::kAction:
action_key = manifest_keys::kAction;
break;
}
return action_key;
}
} // namespace extensions