Skip to content

Commit

Permalink
Add GM.registerMenuCommand().
Browse files Browse the repository at this point in the history
Resolves one part of greasemonkey#2714 .
  • Loading branch information
esperecyan committed Jan 20, 2018
1 parent 2d2ff82 commit eb603cf
Show file tree
Hide file tree
Showing 12 changed files with 593 additions and 6 deletions.
54 changes: 54 additions & 0 deletions doc/Messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,24 @@ Received by: `bg/is-enabled.js`.

Send this to toggle the global enabled status. No data.

# ListMenuCommands
Sent by: `browser/monkey-menu.js`
Received by: `bg/on-user-script-menu-command.js`

Triggered when the popup menu is loaded.
Lists menu commands registered by the `GM.registerMenuCommand()` method called by user scripts on the specified tab.

Data:

* `tabId` A tab's ID (integer).

Response data:

* An array of command objects. Each object has
- `id` A command id that can be used as HTML/XML ID.
- `caption` A string given as the first parameter of the `GM.registerMenuCommand()` method.
- `accessKey` A code point (string) or empty string given as the third parameter of the `GM.registerMenuCommand()` method.

# ListUserScripts
Received by: `bg/user-script-registry.js`.

Expand Down Expand Up @@ -132,6 +150,22 @@ progress as a percentage. Sent specifically back to the content process
* `errors` A (possibly empty) list of string error messages.
* `progress` A number, 0.0 to 1.0, representing the completion so far.

# MenuCommandClick
Sent by: `browser/monkey-menu.js`
Received by: `bg/on-user-script-menu-command.js`

Triggered when a command button on the popup menu is clicked by the user.
Posts message `{type: 'onclick'}` on `UserScriptMenuCommand` channel
to call a function given as the second parameter of the `GM.registerMenuCommand()` method.

Data:

* `id` The command id (string) as returned by `ListMenuCommands` message.

Response data:

* `undefined`

# UserScriptChanged
Sent by: `bg/user-script-registry.js`

Expand Down Expand Up @@ -165,6 +199,26 @@ user. Data:

Callers should specify one or the other, not both.

# UserScriptMenuCommand
Sent by: `content/api-provider-source.js`
Received by: `bg/on-user-script-menu-command.js`

This is a channel (not a message).
Triggered when the `GM.registerMenuCommand()` method is called by an user script.

Data:

* `details` The details object specifying the command. It has
- `caption` A string given as the first parameter of the `GM.registerMenuCommand()` method.
- `accessKey` A code point (string) or empty string given as the third parameter of the `GM.registerMenuCommand()` method.

Response data:

* `undefined`

Messages exchanged via this channel are private to its implementation.
See sender and receiver for further detail.

# UserScriptToggleEnabled
Sent by: `content/manage-user-scripts.js`
Received by: `bg/user-script-registry.js`
Expand Down
1 change: 1 addition & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = function(config) {
config.set({
files: [
'./node_modules/sinon-chrome/bundle/sinon-chrome-webextensions.min.js',
'./test/setup.js',
'./third-party/convert2RegExp.js',
'./third-party/MatchPattern.js',
Expand Down
1 change: 1 addition & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"src/bg/on-open-install-dialog.js",
"src/bg/on-user-script-notification.js",
"src/bg/on-user-script-open-in-tab.js",
"src/bg/on-user-script-menu-command.js",
"src/bg/on-user-script-xhr.js",
"src/bg/user-script-install.js",
"src/bg/user-script-registry.js",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"karma-mocha": "^1.3.0",
"karma-sinon": "^1.0.5",
"karma-sinon-chrome": "^0.2.0",
"mocha": "^3.1.2"
"mocha": "^3.1.2",
"sinon-chrome": "^2.2.1"
}
}
36 changes: 35 additions & 1 deletion src/bg/api-provider-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const SUPPORTED_APIS = new Set([
'GM.getResourceUrl',
'GM.notification',
'GM.openInTab',
'GM.registerMenuCommand',
'GM.setClipboard',
'GM.xmlHttpRequest',
]);
Expand Down Expand Up @@ -55,6 +56,10 @@ function apiProviderSource(userScript) {
source += 'GM.openInTab = ' + GM_openInTab.toString() + ';\n\n';
}

if (grants.includes('GM.registerMenuCommand')) {
source += 'GM.registerMenuCommand = ' + GM_registerMenuCommand.toString() + ';\n\n';
}

if (grants.includes('GM.setClipboard')) {
source += 'GM.setClipboard = ' + GM_setClipboard.toString() + ';\n\n';
}
Expand All @@ -63,7 +68,6 @@ function apiProviderSource(userScript) {
source += 'GM.xmlHttpRequest = ' + GM_xmlHttpRequest.toString() + ';\n\n';
}

// TODO: GM_registerMenuCommand -- maybe.
// TODO: GM_getResourceText -- maybe.

source += '})();';
Expand Down Expand Up @@ -198,6 +202,36 @@ function GM_openInTab(url, openInBackground) {
}


function GM_registerMenuCommand(caption, commandFunc, accessKey = '') {
if (typeof caption != 'string') {
caption = String(caption);
}

if (typeof commandFunc != 'function') {
throw new Error('GM.registerMenuCommand: "commandFunc" must be a function');
}

if (typeof accessKey != 'string' || Array.from(accessKey).length > 1) {
throw new Error('GM.registerMenuCommand: "accessKey" must be a single character');
}

let port = chrome.runtime.connect({name: 'UserScriptMenuCommand'});
port.onMessage.addListener(msg => {
if (msg.type === 'onclick') {
commandFunc();
}
});
port.postMessage({
'details': {
caption,
accessKey,
},
'name': 'register',
'uuid': _uuid,
});
}


function GM_setClipboard(text) {
// TODO: This. The check only works background side, but this implementation
// relies on clipboardWrite permission leaking to the content script so we
Expand Down
71 changes: 71 additions & 0 deletions src/bg/on-user-script-menu-command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
This file is responsible for providing the GM.registerMenuCommand API method.
*/

// Private implementation
(function () {

const commandMap = new Map();


function _randomId() {
return 'id' + window.crypto.getRandomValues(new Uint8Array(16)).join('');
}


function onListMenuCommands(message, sender, sendResponse) {
sendResponse(
Array.from(commandMap.values())
.filter(command => command.port.sender.tab.id === message.tabId)
.map(command => ({
id: command.id,
caption: command.caption,
accessKey: command.accessKey,
})));
}
window.onListMenuCommands = onListMenuCommands;


function onMenuCommandClick(message, sender, sendResponse) {
if (commandMap.has(message.id)) {
commandMap.get(message.id).port.postMessage({type: 'onclick'});
}
}
window.onMenuCommandClick = onMenuCommandClick;


function registerMenuCommand({port, caption, accessKey}) {
const command = {
id: _randomId(),
port,
caption,
accessKey,
};

commandMap.set(command.id, command);

port.onDisconnect.addListener(port => {
commandMap.delete(command.id);
});
}


function onUserScriptMenuCommand(port) {
if (port.name != 'UserScriptMenuCommand') return;

port.onMessage.addListener((msg, port) => {
checkApiCallAllowed('GM.registerMenuCommand', msg.uuid);
switch (msg.name) {
case 'register':
registerMenuCommand(Object.assign({
port,
}, msg.details));
break;
default:
console.warn('UserScriptMenuCommand port un-handled message name:', msg.name);
}
});
}
chrome.runtime.onConnect.addListener(onUserScriptMenuCommand);

})();
14 changes: 11 additions & 3 deletions src/browser/monkey-menu.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,20 @@ body #menu {
transition-property: margin;
}

body.detail #menu {
body.detail #menu,
body.commands #menu {
margin-left: -22.35em;
}

body #user-script-detail {
body #user-script-detail,
body #menu-commands {
visibility: hidden;
transition-duration: 0.25s;
transition-property: visibility;
}

body.detail #user-script-detail {
body.detail #user-script-detail,
body.commands #menu-commands {
visibility: visible;
}

Expand Down Expand Up @@ -194,6 +197,11 @@ section {
text-overflow: ellipsis;
}

.subview-item .access-key {
text-decoration: underline;
text-transform: uppercase;
}

.subview-item::after {
margin-inline-start: 10px;
content: attr(after);
Expand Down
27 changes: 27 additions & 0 deletions src/browser/monkey-menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
</div>
-->

<hr rv-hide="menuCommands | empty">
<a href="#menu-commands" class="subview-item next-menu"
id="open-menu-commands" rv-hide="menuCommands | empty">
<div class="icon fa fa-fw fa-list-alt "></div>
<div class="text">User script commands ...</div>
</a>

<div rv-hide="userScripts.active | empty">
<hr>
<div class="subview-item heading">
Expand Down Expand Up @@ -119,6 +126,26 @@
</section>


<section id="menu-commands" class="subview">
<header class="subview-header">
<a href="#open-menu-commands" class="subview-back"></a>
<div class="subview-title">User script commands</div>
</header>

<div class="subview-body">
<a rv-each-command="menuCommands"
rv-href="command.id | mmUuidMenu"
rv-aria-keyshortcuts="command.accessKey"
class="subview-item">
<div class="icon fa fa-terminal"></div>
<div class="text">
{command.caption}<span rv-if="command.accessKey">(<span class="access-key">{command.accessKey}</span>)</span>
</div>
</a>
</div>
</section>


<script src="/third-party/rivets/rivets.bundled.min.js"></script>
<script src="/third-party/convert2RegExp.js"></script>
<script src="/third-party/MatchPattern.js"></script>
Expand Down

0 comments on commit eb603cf

Please sign in to comment.