feat: add support for share menu on macOS (#25629)
This commit is contained in:
		
					parent
					
						
							
								89c04b3c6c
							
						
					
				
			
			
				commit
				
					
						6b6ffbdd10
					
				
			
		
					 17 changed files with 316 additions and 6 deletions
				
			
		| 
						 | 
				
			
			@ -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/
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										45
									
								
								docs/api/share-menu.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								docs/api/share-menu.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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/
 | 
			
		||||
							
								
								
									
										5
									
								
								docs/api/structures/sharing-item.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docs/api/structures/sharing-item.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<RoleId, Role> = {
 | 
			
		|||
        { role: 'close' }
 | 
			
		||||
      ] as MenuItemConstructorOptions[])
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  // Share submenu
 | 
			
		||||
  sharemenu: {
 | 
			
		||||
    label: 'Share',
 | 
			
		||||
    submenu: []
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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') },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,7 @@ export const browserModuleNames = [
 | 
			
		|||
  'protocol',
 | 
			
		||||
  'screen',
 | 
			
		||||
  'session',
 | 
			
		||||
  'ShareMenu',
 | 
			
		||||
  'systemPreferences',
 | 
			
		||||
  'TouchBar',
 | 
			
		||||
  'Tray',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								lib/browser/api/share-menu.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								lib/browser/api/share-menu.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			@ -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<SharingItem> {
 | 
			
		||||
  static bool FromV8(v8::Isolate* isolate,
 | 
			
		||||
                     v8::Local<v8::Value> 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<v8::Value> val =
 | 
			
		||||
      gin_helper::CallMethod(isolate, const_cast<Menu*>(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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,6 +64,11 @@ class Menu : public gin::Wrappable<Menu>,
 | 
			
		|||
      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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 <NSMenuDelegate> {
 | 
			
		||||
@interface ElectronMenuController
 | 
			
		||||
    : NSObject <NSMenuDelegate, NSSharingServiceDelegate> {
 | 
			
		||||
 @protected
 | 
			
		||||
  base::WeakPtr<electron::ElectronMenuModel> model_;
 | 
			
		||||
  base::scoped_nsobject<NSMenu> menu_;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
 | 
			
		||||
#import "shell/browser/ui/cocoa/electron_menu_controller.h"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#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<electron::ElectronMenuModel> as an
 | 
			
		||||
| 
						 | 
				
			
			@ -267,6 +290,31 @@ static base::scoped_nsobject<NSMenu> 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<NSMenu> 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<NSMenuItem> 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<NSMenu> 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<NSMenu> recentDocumentsMenuSwap_;
 | 
			
		|||
// radio, etc) of each item in the menu.
 | 
			
		||||
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)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<NSMenu> recentDocumentsMenuSwap_;
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Performs the share action using the sharing service represented by |sender|.
 | 
			
		||||
- (void)performShare:(NSMenuItem*)sender {
 | 
			
		||||
  NSDictionary* object =
 | 
			
		||||
      base::mac::ObjCCastStrict<NSDictionary>([sender representedObject]);
 | 
			
		||||
  NSSharingService* service =
 | 
			
		||||
      base::mac::ObjCCastStrict<NSSharingService>(object[@"service"]);
 | 
			
		||||
  NSArray* items = base::mac::ObjCCastStrict<NSArray>(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<NSMenu> 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,10 +4,18 @@
 | 
			
		|||
 | 
			
		||||
#include "shell/browser/ui/electron_menu_model.h"
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#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::SharingItem>&
 | 
			
		||||
ElectronMenuModel::GetSharingItem() const {
 | 
			
		||||
  return sharing_item_;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void ElectronMenuModel::MenuWillClose() {
 | 
			
		||||
  ui::SimpleMenuModel::MenuWillClose();
 | 
			
		||||
  for (Observer& observer : observers_) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,16 +6,34 @@
 | 
			
		|||
#define SHELL_BROWSER_UI_ELECTRON_MENU_MODEL_H_
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#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<std::vector<std::string>> texts;
 | 
			
		||||
    base::Optional<std::vector<GURL>> urls;
 | 
			
		||||
    base::Optional<std::vector<base::FilePath>> 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<SharingItem>& 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<SharingItem> sharing_item_;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  std::map<int, base::string16> toolTips_;   // command id -> tooltip
 | 
			
		||||
  std::map<int, base::string16> roles_;      // command id -> role
 | 
			
		||||
  std::map<int, base::string16> sublabels_;  // command id -> sublabel
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,9 @@
 | 
			
		|||
#define SHELL_COMMON_GIN_HELPER_DICTIONARY_H_
 | 
			
		||||
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#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 <typename T>
 | 
			
		||||
  bool GetOptional(base::StringPiece key, base::Optional<T>* out) const {
 | 
			
		||||
    T ret;
 | 
			
		||||
    if (Get(key, &ret)) {
 | 
			
		||||
      out->emplace(std::move(ret));
 | 
			
		||||
      return true;
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  bool GetHidden(base::StringPiece key, T* out) const {
 | 
			
		||||
    v8::Local<v8::Context> context = isolate()->GetCurrentContext();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								typings/internal-electron.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								typings/internal-electron.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue