|  | // Copyright 2014 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | // Definition of helper functions for the ContextMenus API. | 
|  |  | 
|  | #ifndef CHROME_BROWSER_EXTENSIONS_CONTEXT_MENU_HELPERS_H_ | 
|  | #define CHROME_BROWSER_EXTENSIONS_CONTEXT_MENU_HELPERS_H_ | 
|  |  | 
|  | #include "base/notreached.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/types/optional_util.h" | 
|  | #include "chrome/browser/extensions/menu_manager.h" | 
|  | #include "chrome/common/extensions/api/context_menus.h" | 
|  | #include "content/public/browser/browser_context.h" | 
|  | #include "extensions/common/error_utils.h" | 
|  | #include "extensions/common/extension_id.h" | 
|  | #include "extensions/common/manifest_handlers/background_info.h" | 
|  | #include "extensions/common/utils/extension_utils.h" | 
|  |  | 
|  | namespace content { | 
|  | struct ContextMenuParams; | 
|  | }  // namespace content | 
|  |  | 
|  | namespace extensions { | 
|  | class ContextMenuMatcher; | 
|  | }  // namespace extensions | 
|  |  | 
|  | namespace extensions::context_menu_helpers { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | template <typename PropertyWithEnumT> | 
|  | std::unique_ptr<MenuItem::Id> GetParentId(const PropertyWithEnumT& property, | 
|  | bool is_off_the_record, | 
|  | const MenuItem::ExtensionKey& key) { | 
|  | if (!property.parent_id) | 
|  | return nullptr; | 
|  |  | 
|  | std::unique_ptr<MenuItem::Id> parent_id( | 
|  | new MenuItem::Id(is_off_the_record, key)); | 
|  | if (property.parent_id->as_integer) { | 
|  | parent_id->uid = *property.parent_id->as_integer; | 
|  | } else if (property.parent_id->as_string) { | 
|  | parent_id->string_uid = *property.parent_id->as_string; | 
|  | } else { | 
|  | NOTREACHED(); | 
|  | } | 
|  | return parent_id; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | extern const char kActionNotAllowedError[]; | 
|  | extern const char kCannotFindItemError[]; | 
|  | extern const char kCheckedError[]; | 
|  | extern const char kDuplicateIDError[]; | 
|  | extern const char kGeneratedIdKey[]; | 
|  | extern const char kLauncherNotAllowedError[]; | 
|  | extern const char kOnclickDisallowedError[]; | 
|  | extern const char kParentsMustBeNormalError[]; | 
|  | extern const char kTitleNeededError[]; | 
|  | extern const char kTooManyMenuItems[]; | 
|  |  | 
|  | std::string GetIDString(const MenuItem::Id& id); | 
|  |  | 
|  | MenuItem* GetParent(MenuItem::Id parent_id, | 
|  | const MenuManager* menu_manager, | 
|  | std::string* error); | 
|  |  | 
|  | MenuItem::ContextList GetContexts( | 
|  | const std::vector<api::context_menus::ContextType>& in_contexts); | 
|  |  | 
|  | MenuItem::Type GetType(api::context_menus::ItemType type, | 
|  | MenuItem::Type default_type); | 
|  |  | 
|  | // Determines if a context menu item should be shown for a given click context. | 
|  | // This checks if the properties of a right-click (the `params`) match the | 
|  | // requirements of an extension's context menu item, which are defined by its | 
|  | // allowed `contexts` and `target_url_patterns`. | 
|  | // | 
|  | // params The properties of the context menu click, such as the link | 
|  | //     URL, selected text, and media type. | 
|  | // contexts The set of contexts the menu item is registered for (e.g., | 
|  | //     `MenuItem::IMAGE`, `MenuItem::LINK`). | 
|  | // target_url_patterns The set of URL patterns to match against for | 
|  | //     applicable contexts like links and media. | 
|  | // Returns whether the menu item is a match for the given context and should be | 
|  | // shown. | 
|  | bool ExtensionContextAndPatternMatch(const content::ContextMenuParams& params, | 
|  | const MenuItem::ContextList& contexts, | 
|  | const URLPatternSet& target_url_patterns); | 
|  |  | 
|  | // Determines if a given MenuItem should be shown for a context menu click, | 
|  | // based on the context (e.g., link, image, or selection) and URL. | 
|  | // | 
|  | // params The properties of the context menu click. | 
|  | // item The extension menu item to be evaluated. | 
|  | // Returns whether the menu item should be displayed in the context menu. | 
|  | bool MenuItemMatchesParams(const content::ContextMenuParams& params, | 
|  | const MenuItem* item); | 
|  |  | 
|  | // Prepares user-selected text for display in a context menu item, by truncating | 
|  | // the string to a maximum length (`kMaxSelectionTextLength`) and escaping | 
|  | // ampersands to prevent them from being interpreted as UI mnemonic character | 
|  | // shortcuts. | 
|  | // | 
|  | // selection_text The raw text selected by the user. | 
|  | // Returns a truncated and escaped version of the input string suitable for | 
|  | // display. | 
|  | std::u16string PrintableSelectionText(const std::u16string& selection_text); | 
|  |  | 
|  | // Populates a ContextMenuMatcher with all relevant context menu items from | 
|  | // enabled extensions, sorted and grouped appropriately. | 
|  | // | 
|  | // params The parameters of the context menu click. This is used to get | 
|  | //     the selected text for menu items that include it (e.g., "Search for %s"). | 
|  | // matcher The `ContextMenuMatcher` that will be cleared and then | 
|  | //     populated with the extension menu items. | 
|  | void PopulateExtensionItems(content::BrowserContext* browser_context, | 
|  | const content::ContextMenuParams& params, | 
|  | ContextMenuMatcher& matcher); | 
|  |  | 
|  | // Creates and adds a menu item from `create_properties`. | 
|  | template <typename PropertyWithEnumT> | 
|  | bool CreateMenuItem(const PropertyWithEnumT& create_properties, | 
|  | content::BrowserContext* browser_context, | 
|  | const Extension* extension, | 
|  | const MenuItem::Id& item_id, | 
|  | std::string* error) { | 
|  | bool is_webview = item_id.extension_key.webview_instance_id != 0; | 
|  | MenuManager* menu_manager = MenuManager::Get(browser_context); | 
|  |  | 
|  | if (menu_manager->MenuItemsSize(item_id.extension_key) >= | 
|  | MenuManager::kMaxItemsPerExtension) { | 
|  | *error = ErrorUtils::FormatErrorMessage( | 
|  | kTooManyMenuItems, | 
|  | base::NumberToString(MenuManager::kMaxItemsPerExtension)); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (menu_manager->GetItemById(item_id)) { | 
|  | *error = | 
|  | ErrorUtils::FormatErrorMessage(kDuplicateIDError, GetIDString(item_id)); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!is_webview && BackgroundInfo::HasLazyContext(extension) && | 
|  | create_properties.onclick) { | 
|  | *error = kOnclickDisallowedError; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Contexts. | 
|  | MenuItem::ContextList contexts; | 
|  | if (create_properties.contexts) | 
|  | contexts = GetContexts(*create_properties.contexts); | 
|  | else | 
|  | contexts.Add(MenuItem::PAGE); | 
|  |  | 
|  | if (contexts.Contains(MenuItem::LAUNCHER)) { | 
|  | // Launcher item is not allowed for <webview>. | 
|  | if (is_webview || !extension->is_platform_app()) { | 
|  | *error = kLauncherNotAllowedError; | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (contexts.Contains(MenuItem::BROWSER_ACTION) || | 
|  | contexts.Contains(MenuItem::PAGE_ACTION) || | 
|  | contexts.Contains(MenuItem::ACTION)) { | 
|  | // Action items are not allowed for <webview>. | 
|  | if (is_webview || !extension->is_extension()) { | 
|  | *error = kActionNotAllowedError; | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Title. | 
|  | std::string title; | 
|  | if (create_properties.title) | 
|  | title = *create_properties.title; | 
|  |  | 
|  | MenuItem::Type type = GetType(create_properties.type, MenuItem::NORMAL); | 
|  | if (title.empty() && type != MenuItem::SEPARATOR) { | 
|  | *error = kTitleNeededError; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Visibility state. | 
|  | bool visible = create_properties.visible.value_or(true); | 
|  |  | 
|  | // Checked state. | 
|  | bool checked = create_properties.checked.value_or(false); | 
|  |  | 
|  | // Enabled. | 
|  | bool enabled = create_properties.enabled.value_or(true); | 
|  |  | 
|  | std::unique_ptr<MenuItem> item( | 
|  | new MenuItem(item_id, title, checked, visible, enabled, type, contexts)); | 
|  |  | 
|  | // URL Patterns. | 
|  | if (!item->PopulateURLPatterns( | 
|  | base::OptionalToPtr(create_properties.document_url_patterns), | 
|  | base::OptionalToPtr(create_properties.target_url_patterns), error)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Parent id. | 
|  | bool success = true; | 
|  | std::unique_ptr<MenuItem::Id> parent_id( | 
|  | GetParentId(create_properties, browser_context->IsOffTheRecord(), | 
|  | item_id.extension_key)); | 
|  | if (parent_id.get()) { | 
|  | MenuItem* parent = GetParent(*parent_id, menu_manager, error); | 
|  | if (!parent) | 
|  | return false; | 
|  | success = menu_manager->AddChildItem(parent->id(), std::move(item)); | 
|  | } else { | 
|  | success = menu_manager->AddContextItem(extension, std::move(item)); | 
|  | } | 
|  |  | 
|  | if (!success) | 
|  | return false; | 
|  |  | 
|  | menu_manager->WriteToStorage(extension, item_id.extension_key); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Updates a menu item from `update_properties`. | 
|  | template <typename PropertyWithEnumT> | 
|  | bool UpdateMenuItem(const PropertyWithEnumT& update_properties, | 
|  | content::BrowserContext* browser_context, | 
|  | const Extension* extension, | 
|  | const MenuItem::Id& item_id, | 
|  | std::string* error) { | 
|  | bool radio_item_updated = false; | 
|  | bool is_webview = item_id.extension_key.webview_instance_id != 0; | 
|  | MenuManager* menu_manager = MenuManager::Get(browser_context); | 
|  |  | 
|  | MenuItem* item = menu_manager->GetItemById(item_id); | 
|  | const ExtensionId& extension_id = MaybeGetExtensionId(extension); | 
|  | if (!item || item->extension_id() != extension_id) { | 
|  | *error = ErrorUtils::FormatErrorMessage(kCannotFindItemError, | 
|  | GetIDString(item_id)); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Type. | 
|  | MenuItem::Type type = GetType(update_properties.type, item->type()); | 
|  |  | 
|  | if (type != item->type()) { | 
|  | if (type == MenuItem::RADIO || item->type() == MenuItem::RADIO) { | 
|  | radio_item_updated = true; | 
|  | } | 
|  | item->set_type(type); | 
|  | } | 
|  |  | 
|  | // Title. | 
|  | if (update_properties.title) { | 
|  | std::string title(*update_properties.title); | 
|  | if (title.empty() && item->type() != MenuItem::SEPARATOR) { | 
|  | *error = kTitleNeededError; | 
|  | return false; | 
|  | } | 
|  | item->set_title(title); | 
|  | } | 
|  |  | 
|  | // Checked state. | 
|  | if (update_properties.checked) { | 
|  | bool checked = *update_properties.checked; | 
|  | if (checked && item->type() != MenuItem::CHECKBOX && | 
|  | item->type() != MenuItem::RADIO) { | 
|  | *error = kCheckedError; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const bool should_toggle_checked = | 
|  | // If radio item was unchecked nothing should happen. The radio item | 
|  | // should remain checked because there should always be one item checked | 
|  | // in the radio list. | 
|  | (item->type() == MenuItem::RADIO && checked) || | 
|  | // Checkboxes are always updated. | 
|  | item->type() == MenuItem::CHECKBOX; | 
|  |  | 
|  | if (should_toggle_checked) { | 
|  | if (!item->SetChecked(checked)) { | 
|  | *error = kCheckedError; | 
|  | return false; | 
|  | } | 
|  | radio_item_updated = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Visibility state. | 
|  | if (update_properties.visible) | 
|  | item->set_visible(*update_properties.visible); | 
|  |  | 
|  | // Enabled. | 
|  | if (update_properties.enabled) | 
|  | item->set_enabled(*update_properties.enabled); | 
|  |  | 
|  | // Contexts. | 
|  | MenuItem::ContextList contexts; | 
|  | if (update_properties.contexts) { | 
|  | contexts = GetContexts(*update_properties.contexts); | 
|  |  | 
|  | if (contexts.Contains(MenuItem::LAUNCHER)) { | 
|  | // Launcher item is not allowed for <webview>. | 
|  | if (is_webview || !extension->is_platform_app()) { | 
|  | *error = kLauncherNotAllowedError; | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (contexts != item->contexts()) | 
|  | item->set_contexts(contexts); | 
|  | } | 
|  |  | 
|  | // Parent id. | 
|  | std::unique_ptr<MenuItem::Id> parent_id( | 
|  | GetParentId(update_properties, browser_context->IsOffTheRecord(), | 
|  | item_id.extension_key)); | 
|  | if (parent_id.get()) { | 
|  | MenuItem* parent = GetParent(*parent_id, menu_manager, error); | 
|  | if (!parent || !menu_manager->ChangeParent(item->id(), &parent->id())) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // URL Patterns. | 
|  | if (!item->PopulateURLPatterns( | 
|  | base::OptionalToPtr(update_properties.document_url_patterns), | 
|  | base::OptionalToPtr(update_properties.target_url_patterns), error)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // There is no need to call ItemUpdated if ChangeParent is called because | 
|  | // all sanitation is taken care of in ChangeParent. | 
|  | if (radio_item_updated && !menu_manager->ItemUpdated(item->id())) | 
|  | return false; | 
|  |  | 
|  | menu_manager->WriteToStorage(extension, item_id.extension_key); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace extensions::context_menu_helpers | 
|  |  | 
|  | #endif  // CHROME_BROWSER_EXTENSIONS_CONTEXT_MENU_HELPERS_H_ |