From 6b6ffbdd107f4633b2b70d0e41be64aa65efc540 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 20 Oct 2020 10:33:06 +0900 Subject: [PATCH] feat: add support for share menu on macOS (#25629) --- docs/api/menu-item.md | 12 ++- docs/api/share-menu.md | 45 +++++++++ docs/api/structures/sharing-item.md | 5 + filenames.auto.gni | 3 + lib/browser/api/menu-item-roles.ts | 7 +- lib/browser/api/menu.ts | 6 ++ lib/browser/api/module-list.ts | 1 + lib/browser/api/module-names.ts | 1 + lib/browser/api/share-menu.ts | 19 ++++ shell/browser/api/electron_api_menu.cc | 49 ++++++++++ shell/browser/api/electron_api_menu.h | 5 + .../ui/cocoa/electron_menu_controller.h | 3 +- .../ui/cocoa/electron_menu_controller.mm | 92 ++++++++++++++++++- shell/browser/ui/electron_menu_model.cc | 25 +++++ shell/browser/ui/electron_menu_model.h | 34 +++++++ shell/common/gin_helper/dictionary.h | 14 +++ typings/internal-electron.d.ts | 1 + 17 files changed, 316 insertions(+), 6 deletions(-) create mode 100644 docs/api/share-menu.md create mode 100644 docs/api/structures/sharing-item.md create mode 100644 lib/browser/api/share-menu.ts diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 1586f8925326..fe5bc12c187a 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -14,7 +14,7 @@ See [`Menu`](menu.md) for examples. * `menuItem` MenuItem * `browserWindow` [BrowserWindow](browser-window.md) | undefined - This will not be defined if no window is open. * `event` [KeyboardEvent](structures/keyboard-event.md) - * `role` String (optional) - Can be `undo`, `redo`, `cut`, `copy`, `paste`, `pasteAndMatchStyle`, `delete`, `selectAll`, `reload`, `forceReload`, `toggleDevTools`, `resetZoom`, `zoomIn`, `zoomOut`, `togglefullscreen`, `window`, `minimize`, `close`, `help`, `about`, `services`, `hide`, `hideOthers`, `unhide`, `quit`, `startSpeaking`, `stopSpeaking`, `zoom`, `front`, `appMenu`, `fileMenu`, `editMenu`, `viewMenu`, `recentDocuments`, `toggleTabBar`, `selectNextTab`, `selectPreviousTab`, `mergeAllWindows`, `clearRecentDocuments`, `moveTabToNewWindow` or `windowMenu` - Define the action of the menu item, when specified the + * `role` String (optional) - Can be `undo`, `redo`, `cut`, `copy`, `paste`, `pasteAndMatchStyle`, `delete`, `selectAll`, `reload`, `forceReload`, `toggleDevTools`, `resetZoom`, `zoomIn`, `zoomOut`, `togglefullscreen`, `window`, `minimize`, `close`, `help`, `about`, `services`, `hide`, `hideOthers`, `unhide`, `quit`, `startSpeaking`, `stopSpeaking`, `zoom`, `front`, `appMenu`, `fileMenu`, `editMenu`, `viewMenu`, `shareMenu`, `recentDocuments`, `toggleTabBar`, `selectNextTab`, `selectPreviousTab`, `mergeAllWindows`, `clearRecentDocuments`, `moveTabToNewWindow` or `windowMenu` - Define the action of the menu item, when specified the `click` property will be ignored. See [roles](#roles). * `type` String (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or `radio`. @@ -31,6 +31,7 @@ See [`Menu`](menu.md) for examples. menu items. * `registerAccelerator` Boolean (optional) _Linux_ _Windows_ - If false, the accelerator won't be registered with the system, but it will still be displayed. Defaults to true. + * `sharingItem` SharingItem (optional) _macOS_ - The item to share when the `role` is `shareMenu`. * `submenu` (MenuItemConstructorOptions[] | [Menu](menu.md)) (optional) - Should be specified for `submenu` type menu items. If `submenu` is specified, the `type: 'submenu'` can be omitted. If the value is not a [`Menu`](menu.md) then it will be automatically converted to one using @@ -112,6 +113,7 @@ The following additional roles are available on _macOS_: * `services` - The submenu is a ["Services"](https://developer.apple.com/documentation/appkit/nsapplication/1428608-servicesmenu?language=objc) menu. This is only intended for use in the Application Menu and is *not* the same as the "Services" submenu used in context menus in macOS apps, which is not implemented in Electron. * `recentDocuments` - The submenu is an "Open Recent" menu. * `clearRecentDocuments` - Map to the `clearRecentDocuments` action. +* `shareMenu` - The submenu is [share menu][ShareMenu]. The `sharingItem` property must also be set to indicate the item to share. When specifying a `role` on macOS, `label` and `accelerator` are the only options that will affect the menu item. All other options will be ignored. @@ -200,6 +202,12 @@ system or just displayed. This property can be dynamically changed. +#### `menuItem.sharingItem` _macOS_ + +A `SharingItem` indicating the item to share when the `role` is `shareMenu`. + +This property can be dynamically changed. + #### `menuItem.commandId` A `Number` indicating an item's sequential unique id. @@ -207,3 +215,5 @@ A `Number` indicating an item's sequential unique id. #### `menuItem.menu` A `Menu` that the item is a part of. + +[ShareMenu]: https://developer.apple.com/design/human-interface-guidelines/macos/extensions/share-extensions/ diff --git a/docs/api/share-menu.md b/docs/api/share-menu.md new file mode 100644 index 000000000000..007028db3bc9 --- /dev/null +++ b/docs/api/share-menu.md @@ -0,0 +1,45 @@ +## Class: ShareMenu + +> Create share menu on macOS. + +Process: [Main](../glossary.md#main-process) + +The `ShareMenu` class creates [Share Menu][share-menu] on macOS, which can be +used to share information from the current context to apps, social media +accounts, and other services. + +For including the share menu as a submenu of other menus, please use the +`shareMenu` role of [`MenuItem`](menu-item.md). + +### `new ShareMenu(sharingItem)` + +* `sharingItem` SharingItem - The item to share. + +Creates a new share menu. + +### Instance Methods + +The `shareMenu` object has the following instance methods: + +#### `shareMenu.popup([options])` + +* `options` PopupOptions (optional) + * `browserWindow` [BrowserWindow](browser-window.md) (optional) - Default is the focused window. + * `x` Number (optional) - Default is the current mouse cursor position. + Must be declared if `y` is declared. + * `y` Number (optional) - Default is the current mouse cursor position. + Must be declared if `x` is declared. + * `positioningItem` Number (optional) _macOS_ - The index of the menu item to + be positioned under the mouse cursor at the specified coordinates. Default + is -1. + * `callback` Function (optional) - Called when menu is closed. + +Pops up this menu as a context menu in the [`BrowserWindow`](browser-window.md). + +#### `shareMenu.closePopup([browserWindow])` + +* `browserWindow` [BrowserWindow](browser-window.md) (optional) - Default is the focused window. + +Closes the context menu in the `browserWindow`. + +[share-menu]: https://developer.apple.com/design/human-interface-guidelines/macos/extensions/share-extensions/ diff --git a/docs/api/structures/sharing-item.md b/docs/api/structures/sharing-item.md new file mode 100644 index 000000000000..aab5b09c10f6 --- /dev/null +++ b/docs/api/structures/sharing-item.md @@ -0,0 +1,5 @@ +# SharingItem Object + +* `texts` String[] (optional) - An array of text to share. +* `filePaths` String[] (optional) - An array of files to share. +* `urls` String[] (optional) - An array of URLs to share. diff --git a/filenames.auto.gni b/filenames.auto.gni index e2f0d163abf5..f68861f533dc 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -49,6 +49,7 @@ auto_filenames = { "docs/api/screen.md", "docs/api/service-workers.md", "docs/api/session.md", + "docs/api/share-menu.md", "docs/api/shell.md", "docs/api/structures", "docs/api/synopsis.md", @@ -119,6 +120,7 @@ auto_filenames = { "docs/api/structures/serial-port.md", "docs/api/structures/service-worker-info.md", "docs/api/structures/shared-worker-info.md", + "docs/api/structures/sharing-item.md", "docs/api/structures/shortcut-details.md", "docs/api/structures/size.md", "docs/api/structures/task.md", @@ -219,6 +221,7 @@ auto_filenames = { "lib/browser/api/protocol.ts", "lib/browser/api/screen.ts", "lib/browser/api/session.ts", + "lib/browser/api/share-menu.ts", "lib/browser/api/system-preferences.ts", "lib/browser/api/touch-bar.ts", "lib/browser/api/tray.ts", diff --git a/lib/browser/api/menu-item-roles.ts b/lib/browser/api/menu-item-roles.ts index d844b702d7c3..e733848e2a24 100644 --- a/lib/browser/api/menu-item-roles.ts +++ b/lib/browser/api/menu-item-roles.ts @@ -6,7 +6,7 @@ const isLinux = process.platform === 'linux'; type RoleId = 'about' | 'close' | 'copy' | 'cut' | 'delete' | 'forcereload' | 'front' | 'help' | 'hide' | 'hideothers' | 'minimize' | 'paste' | 'pasteandmatchstyle' | 'quit' | 'redo' | 'reload' | 'resetzoom' | 'selectall' | 'services' | 'recentdocuments' | 'clearrecentdocuments' | 'startspeaking' | 'stopspeaking' | - 'toggledevtools' | 'togglefullscreen' | 'undo' | 'unhide' | 'window' | 'zoom' | 'zoomin' | 'zoomout' | 'appmenu' | 'filemenu' | 'editmenu' | 'viewmenu' | 'windowmenu' + 'toggledevtools' | 'togglefullscreen' | 'undo' | 'unhide' | 'window' | 'zoom' | 'zoomin' | 'zoomout' | 'appmenu' | 'filemenu' | 'editmenu' | 'viewmenu' | 'windowmenu' | 'sharemenu' interface Role { label: string; accelerator?: string; @@ -261,6 +261,11 @@ export const roleList: Record = { { role: 'close' } ] as MenuItemConstructorOptions[]) ] + }, + // Share submenu + sharemenu: { + label: 'Share', + submenu: [] } }; diff --git a/lib/browser/api/menu.ts b/lib/browser/api/menu.ts index fe0fb9d5aad1..384209c45777 100644 --- a/lib/browser/api/menu.ts +++ b/lib/browser/api/menu.ts @@ -41,6 +41,12 @@ Menu.prototype._shouldRegisterAcceleratorForCommandId = function (id) { return this.commandsMap[id] ? this.commandsMap[id].registerAccelerator : false; }; +if (process.platform === 'darwin') { + Menu.prototype._getSharingItemForCommandId = function (id) { + return this.commandsMap[id] ? this.commandsMap[id].sharingItem : null; + }; +} + Menu.prototype._executeCommand = function (event, id) { const command = this.commandsMap[id]; if (!command) return; diff --git a/lib/browser/api/module-list.ts b/lib/browser/api/module-list.ts index 3caead0ee91d..8c6894556fc9 100644 --- a/lib/browser/api/module-list.ts +++ b/lib/browser/api/module-list.ts @@ -26,6 +26,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [ { name: 'protocol', loader: () => require('./protocol') }, { name: 'screen', loader: () => require('./screen') }, { name: 'session', loader: () => require('./session') }, + { name: 'ShareMenu', loader: () => require('./share-menu') }, { name: 'systemPreferences', loader: () => require('./system-preferences') }, { name: 'TouchBar', loader: () => require('./touch-bar') }, { name: 'Tray', loader: () => require('./tray') }, diff --git a/lib/browser/api/module-names.ts b/lib/browser/api/module-names.ts index 58eec750444a..ba628497f86a 100644 --- a/lib/browser/api/module-names.ts +++ b/lib/browser/api/module-names.ts @@ -29,6 +29,7 @@ export const browserModuleNames = [ 'protocol', 'screen', 'session', + 'ShareMenu', 'systemPreferences', 'TouchBar', 'Tray', diff --git a/lib/browser/api/share-menu.ts b/lib/browser/api/share-menu.ts new file mode 100644 index 000000000000..b033cd297b93 --- /dev/null +++ b/lib/browser/api/share-menu.ts @@ -0,0 +1,19 @@ +import { BrowserWindow, Menu, SharingItem, PopupOptions } from 'electron/main'; + +class ShareMenu { + private menu: Menu; + + constructor (sharingItem: SharingItem) { + this.menu = new (Menu as any)({ sharingItem }); + } + + popup (options?: PopupOptions) { + this.menu.popup(options); + } + + closePopup (browserWindow?: BrowserWindow) { + this.menu.closePopup(browserWindow); + } +} + +export default ShareMenu; diff --git a/shell/browser/api/electron_api_menu.cc b/shell/browser/api/electron_api_menu.cc index e5b473763450..2ae9d02dce15 100644 --- a/shell/browser/api/electron_api_menu.cc +++ b/shell/browser/api/electron_api_menu.cc @@ -12,12 +12,39 @@ #include "shell/browser/native_window.h" #include "shell/common/gin_converters/accelerator_converter.h" #include "shell/common/gin_converters/callback_converter.h" +#include "shell/common/gin_converters/file_path_converter.h" +#include "shell/common/gin_converters/gurl_converter.h" #include "shell/common/gin_converters/image_converter.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/object_template_builder.h" #include "shell/common/node_includes.h" #include "ui/base/models/image_model.h" +#if defined(OS_MAC) + +namespace gin { + +using SharingItem = electron::ElectronMenuModel::SharingItem; + +template <> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + SharingItem* out) { + gin_helper::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + dict.GetOptional("texts", &(out->texts)); + dict.GetOptional("filePaths", &(out->file_paths)); + dict.GetOptional("urls", &(out->urls)); + return true; + } +}; + +} // namespace gin + +#endif + namespace electron { namespace api { @@ -26,6 +53,15 @@ gin::WrapperInfo Menu::kWrapperInfo = {gin::kEmbedderNativeGin}; Menu::Menu(gin::Arguments* args) : model_(new ElectronMenuModel(this)) { model_->AddObserver(this); + +#if defined(OS_MAC) + gin_helper::Dictionary options; + if (args->GetNext(&options)) { + ElectronMenuModel::SharingItem item; + if (options.Get("sharingItem", &item)) + model_->SetSharingItem(std::move(item)); + } +#endif } Menu::~Menu() { @@ -81,6 +117,19 @@ bool Menu::ShouldRegisterAcceleratorForCommandId(int command_id) const { command_id); } +#if defined(OS_MAC) +bool Menu::GetSharingItemForCommandId( + int command_id, + ElectronMenuModel::SharingItem* item) const { + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + v8::Local val = + gin_helper::CallMethod(isolate, const_cast(this), + "_getSharingItemForCommandId", command_id); + return gin::ConvertFromV8(isolate, val, item); +} +#endif + void Menu::ExecuteCommand(int command_id, int flags) { v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); v8::HandleScope scope(isolate); diff --git a/shell/browser/api/electron_api_menu.h b/shell/browser/api/electron_api_menu.h index 480a7b34c206..5c47990fa69f 100644 --- a/shell/browser/api/electron_api_menu.h +++ b/shell/browser/api/electron_api_menu.h @@ -64,6 +64,11 @@ class Menu : public gin::Wrappable, bool use_default_accelerator, ui::Accelerator* accelerator) const override; bool ShouldRegisterAcceleratorForCommandId(int command_id) const override; +#if defined(OS_MAC) + bool GetSharingItemForCommandId( + int command_id, + ElectronMenuModel::SharingItem* item) const override; +#endif void ExecuteCommand(int command_id, int event_flags) override; void OnMenuWillShow(ui::SimpleMenuModel* source) override; diff --git a/shell/browser/ui/cocoa/electron_menu_controller.h b/shell/browser/ui/cocoa/electron_menu_controller.h index 5cc94a2e526b..af080b1071e1 100644 --- a/shell/browser/ui/cocoa/electron_menu_controller.h +++ b/shell/browser/ui/cocoa/electron_menu_controller.h @@ -23,7 +23,8 @@ class ElectronMenuModel; // allow for hierarchical menus). The tag is the index into that model for // that particular item. It is important that the model outlives this object // as it only maintains weak references. -@interface ElectronMenuController : NSObject { +@interface ElectronMenuController + : NSObject { @protected base::WeakPtr model_; base::scoped_nsobject menu_; diff --git a/shell/browser/ui/cocoa/electron_menu_controller.mm b/shell/browser/ui/cocoa/electron_menu_controller.mm index 31e43c8c2cbe..4de4248a5f6f 100644 --- a/shell/browser/ui/cocoa/electron_menu_controller.mm +++ b/shell/browser/ui/cocoa/electron_menu_controller.mm @@ -5,6 +5,7 @@ #import "shell/browser/ui/cocoa/electron_menu_controller.h" +#include #include #include "base/logging.h" @@ -14,8 +15,11 @@ #include "base/task/post_task.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" +#include "net/base/mac/url_conversions.h" #include "shell/browser/mac/electron_application.h" +#include "shell/browser/native_window.h" #include "shell/browser/ui/electron_menu_model.h" +#include "shell/browser/window_list.h" #include "ui/base/accelerators/accelerator.h" #include "ui/base/accelerators/platform_accelerator_cocoa.h" #include "ui/base/l10n/l10n_util_mac.h" @@ -24,6 +28,7 @@ #include "ui/strings/grit/ui_strings.h" using content::BrowserThread; +using SharingItem = electron::ElectronMenuModel::SharingItem; namespace { @@ -86,6 +91,24 @@ NSMenu* MakeEmptySubmenu() { return submenu.autorelease(); } +// Convert an SharingItem to an array of NSObjects. +NSArray* ConvertSharingItemToNS(const SharingItem& item) { + NSMutableArray* result = [NSMutableArray array]; + if (item.texts) { + for (const std::string& str : *item.texts) + [result addObject:base::SysUTF8ToNSString(str)]; + } + if (item.file_paths) { + for (const base::FilePath& path : *item.file_paths) + [result addObject:base::mac::FilePathToNSURL(path)]; + } + if (item.urls) { + for (const GURL& url : *item.urls) + [result addObject:net::NSURLWithGURL(url)]; + } + return result; +} + } // namespace // This class stores a base::WeakPtr as an @@ -267,6 +290,31 @@ static base::scoped_nsobject recentDocumentsMenuSwap_; recentDocumentsMenuItem_.reset([item retain]); } +// Fill the menu with Share Menu items. +- (NSMenu*)createShareMenuForItem:(const SharingItem&)item { + NSArray* items = ConvertSharingItemToNS(item); + if ([items count] == 0) + return MakeEmptySubmenu(); + base::scoped_nsobject menu([[NSMenu alloc] init]); + NSArray* services = [NSSharingService sharingServicesForItems:items]; + for (NSSharingService* service in services) + [menu addItem:[self menuItemForService:service withItems:items]]; + return menu.autorelease(); +} + +// Creates a menu item that calls |service| when invoked. +- (NSMenuItem*)menuItemForService:(NSSharingService*)service + withItems:(NSArray*)items { + base::scoped_nsobject item([[NSMenuItem alloc] + initWithTitle:service.menuItemTitle + action:@selector(performShare:) + keyEquivalent:@""]); + [item setTarget:self]; + [item setImage:service.image]; + [item setRepresentedObject:@{@"service" : service, @"items" : items}]; + return item.autorelease(); +} + // Adds an item or a hierarchical menu to the item at the |index|, // associated with the entry in the model identified by |modelIndex|. - (void)addItemToMenu:(NSMenu*)menu @@ -300,6 +348,12 @@ static base::scoped_nsobject recentDocumentsMenuSwap_; NSMenu* submenu = [[[NSMenu alloc] initWithTitle:label] autorelease]; [item setSubmenu:submenu]; [NSApp setServicesMenu:submenu]; + } else if (role == base::ASCIIToUTF16("sharemenu")) { + SharingItem sharing_item; + model->GetSharingItemAt(index, &sharing_item); + [item setTarget:nil]; + [item setAction:nil]; + [item setSubmenu:[self createShareMenuForItem:sharing_item]]; } else if (type == electron::ElectronMenuModel::TYPE_SUBMENU && model->IsVisibleAt(index)) { // We need to specifically check that the submenu top-level item has been @@ -372,6 +426,8 @@ static base::scoped_nsobject recentDocumentsMenuSwap_; // radio, etc) of each item in the menu. - (BOOL)validateUserInterfaceItem:(id)item { SEL action = [item action]; + if (action == @selector(performShare:)) + return YES; if (action != @selector(itemSelected:)) return NO; @@ -405,14 +461,30 @@ static base::scoped_nsobject recentDocumentsMenuSwap_; } } +// Performs the share action using the sharing service represented by |sender|. +- (void)performShare:(NSMenuItem*)sender { + NSDictionary* object = + base::mac::ObjCCastStrict([sender representedObject]); + NSSharingService* service = + base::mac::ObjCCastStrict(object[@"service"]); + NSArray* items = base::mac::ObjCCastStrict(object[@"items"]); + [service setDelegate:self]; + [service performWithItems:items]; +} + - (NSMenu*)menu { if (menu_) return menu_.get(); - menu_.reset([[NSMenu alloc] initWithTitle:@""]); + if (model_ && model_->GetSharingItem()) { + NSMenu* menu = [self createShareMenuForItem:*model_->GetSharingItem()]; + menu_.reset([menu retain]); + } else { + menu_.reset([[NSMenu alloc] initWithTitle:@""]); + if (model_) + [self populateWithModel:model_.get()]; + } [menu_ setDelegate:self]; - if (model_) - [self populateWithModel:model_.get()]; return menu_.get(); } @@ -439,4 +511,18 @@ static base::scoped_nsobject recentDocumentsMenuSwap_; } } +// NSSharingServiceDelegate + +- (NSWindow*)sharingService:(NSSharingService*)service + sourceWindowForShareItems:(NSArray*)items + sharingContentScope:(NSSharingContentScope*)scope { + // Return the current active window. + const auto& list = electron::WindowList::GetWindows(); + for (electron::NativeWindow* window : list) { + if (window->IsFocused()) + return window->GetNativeWindow().GetNativeNSWindow(); + } + return nil; +} + @end diff --git a/shell/browser/ui/electron_menu_model.cc b/shell/browser/ui/electron_menu_model.cc index d76c09e4c8d5..b4680e64f92c 100644 --- a/shell/browser/ui/electron_menu_model.cc +++ b/shell/browser/ui/electron_menu_model.cc @@ -4,10 +4,18 @@ #include "shell/browser/ui/electron_menu_model.h" +#include + #include "base/stl_util.h" namespace electron { +#if defined(OS_MAC) +ElectronMenuModel::SharingItem::SharingItem() = default; +ElectronMenuModel::SharingItem::SharingItem(SharingItem&&) = default; +ElectronMenuModel::SharingItem::~SharingItem() = default; +#endif + bool ElectronMenuModel::Delegate::GetAcceleratorForCommandId( int command_id, ui::Accelerator* accelerator) const { @@ -79,6 +87,23 @@ bool ElectronMenuModel::WorksWhenHiddenAt(int index) const { return true; } +#if defined(OS_MAC) +bool ElectronMenuModel::GetSharingItemAt(int index, SharingItem* item) const { + if (delegate_) + return delegate_->GetSharingItemForCommandId(GetCommandIdAt(index), item); + return false; +} + +void ElectronMenuModel::SetSharingItem(SharingItem item) { + sharing_item_.emplace(std::move(item)); +} + +const base::Optional& +ElectronMenuModel::GetSharingItem() const { + return sharing_item_; +} +#endif + void ElectronMenuModel::MenuWillClose() { ui::SimpleMenuModel::MenuWillClose(); for (Observer& observer : observers_) { diff --git a/shell/browser/ui/electron_menu_model.h b/shell/browser/ui/electron_menu_model.h index 59db82712267..de2ebd07899c 100644 --- a/shell/browser/ui/electron_menu_model.h +++ b/shell/browser/ui/electron_menu_model.h @@ -6,16 +6,34 @@ #define SHELL_BROWSER_UI_ELECTRON_MENU_MODEL_H_ #include +#include +#include +#include "base/files/file_path.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "base/observer_list_types.h" +#include "base/optional.h" #include "ui/base/models/simple_menu_model.h" +#include "url/gurl.h" namespace electron { class ElectronMenuModel : public ui::SimpleMenuModel { public: +#if defined(OS_MAC) + struct SharingItem { + SharingItem(); + SharingItem(SharingItem&&); + SharingItem(const SharingItem&) = delete; + ~SharingItem(); + + base::Optional> texts; + base::Optional> urls; + base::Optional> file_paths; + }; +#endif + class Delegate : public ui::SimpleMenuModel::Delegate { public: ~Delegate() override {} @@ -30,6 +48,11 @@ class ElectronMenuModel : public ui::SimpleMenuModel { virtual bool ShouldCommandIdWorkWhenHidden(int command_id) const = 0; +#if defined(OS_MAC) + virtual bool GetSharingItemForCommandId(int command_id, + SharingItem* item) const = 0; +#endif + private: // ui::SimpleMenuModel::Delegate: bool GetAcceleratorForCommandId( @@ -65,6 +88,13 @@ class ElectronMenuModel : public ui::SimpleMenuModel { ui::Accelerator* accelerator) const; bool ShouldRegisterAcceleratorAt(int index) const; bool WorksWhenHiddenAt(int index) const; +#if defined(OS_MAC) + // Return the SharingItem of menu item. + bool GetSharingItemAt(int index, SharingItem* item) const; + // Set/Get the SharingItem of this menu. + void SetSharingItem(SharingItem item); + const base::Optional& GetSharingItem() const; +#endif // ui::SimpleMenuModel: void MenuWillClose() override; @@ -80,6 +110,10 @@ class ElectronMenuModel : public ui::SimpleMenuModel { private: Delegate* delegate_; // weak ref. +#if defined(OS_MAC) + base::Optional sharing_item_; +#endif + std::map toolTips_; // command id -> tooltip std::map roles_; // command id -> role std::map sublabels_; // command id -> sublabel diff --git a/shell/common/gin_helper/dictionary.h b/shell/common/gin_helper/dictionary.h index 86b9ae7416bf..4ca241f3b91a 100644 --- a/shell/common/gin_helper/dictionary.h +++ b/shell/common/gin_helper/dictionary.h @@ -6,7 +6,9 @@ #define SHELL_COMMON_GIN_HELPER_DICTIONARY_H_ #include +#include +#include "base/optional.h" #include "gin/dictionary.h" #include "shell/common/gin_converters/std_converter.h" #include "shell/common/gin_helper/function_template.h" @@ -59,6 +61,18 @@ class Dictionary : public gin::Dictionary { return !result.IsNothing() && result.FromJust(); } + // Like normal Get but put result in an base::Optional. + template + bool GetOptional(base::StringPiece key, base::Optional* out) const { + T ret; + if (Get(key, &ret)) { + out->emplace(std::move(ret)); + return true; + } else { + return false; + } + } + template bool GetHidden(base::StringPiece key, T* out) const { v8::Local context = isolate()->GetCurrentContext(); diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 38d0e739419e..98ac320252d7 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -108,6 +108,7 @@ declare namespace Electron { _isCommandIdVisible(id: string): boolean; _getAcceleratorForCommandId(id: string, useDefaultAccelerator: boolean): Accelerator | undefined; _shouldRegisterAcceleratorForCommandId(id: string): boolean; + _getSharingItemForCommandId(id: string): SharingItem | null; _callMenuWillShow(): void; _executeCommand(event: any, id: number): void; _menuWillShow(): void;