diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 0569452f..f72b5525 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -37,6 +37,7 @@

Guide

  • Blacklist
  • Search engines
  • Properties
  • +
  • SendMessage
  • diff --git a/docs/sendmessage.md b/docs/sendmessage.md new file mode 100644 index 00000000..50a38b33 --- /dev/null +++ b/docs/sendmessage.md @@ -0,0 +1,31 @@ +--- +title: SendMessage +--- + +# SendMessage + +Vim Vixen can send messages to other add-ons to controll their functionality by keyboard, if they support. To use this feature, you need to specify `addon.sendmessage` as `type`, `extensionId` and `message` (this can be string or object) for keymaps. + +Note that, currently this feature can be set only when using "Use plain JSON". + +## Example + +Following example enables to toggle collapsed state of [Tree Style Tab](https://addons.mozilla.org/firefox/addon/tree-style-tab/)'s active tab by pressing zc. This kind of API might be described in add-ons' web site, for example you can find Tree Style Tab's API reference [here](https://github.com/piroor/treestyletab/wiki/API-for-other-addons). + +```json +{ + "keymaps": { + "zc": { + "type": "addon.sendmessage", + "extensionId": "treestyletab@piro.sakura.ne.jp", + "message": { + "type": "toggle-tree-collapsed", + "tab": "active" + } + } + } +} +``` + +You may want to see [the Wiki page for the same feature on Gesturefy](https://twitter.com/tomo_ahm/status/1297849816907575296) for more example. + diff --git a/src/content/controllers/KeymapController.ts b/src/content/controllers/KeymapController.ts index 092e55ce..72ef46d9 100644 --- a/src/content/controllers/KeymapController.ts +++ b/src/content/controllers/KeymapController.ts @@ -2,6 +2,7 @@ import { injectable, inject } from "tsyringe"; import * as operations from "../../shared/operations"; import KeymapUseCase from "../usecases/KeymapUseCase"; import AddonEnabledUseCase from "../usecases/AddonEnabledUseCase"; +import AddonSendmessageUseCase from "../usecases/AddonSendmessageUseCase"; import FindSlaveUseCase from "../usecases/FindSlaveUseCase"; import ScrollUseCase from "../usecases/ScrollUseCase"; import FocusUseCase from "../usecases/FocusUseCase"; @@ -16,6 +17,7 @@ export default class KeymapController { constructor( private keymapUseCase: KeymapUseCase, private addonEnabledUseCase: AddonEnabledUseCase, + private addonSendmessageUseCase: AddonSendmessageUseCase, private findSlaveUseCase: FindSlaveUseCase, private scrollUseCase: ScrollUseCase, private focusUseCase: FocusUseCase, @@ -48,6 +50,12 @@ export default class KeymapController { return () => this.addonEnabledUseCase.disable(); case operations.ADDON_TOGGLE_ENABLED: return () => this.addonEnabledUseCase.toggle(); + case operations.ADDON_SENDMESSAGE: + return () => + this.addonSendmessageUseCase.sendMessage( + op.extensionId, + op.message + ); case operations.FIND_NEXT: return () => this.findSlaveUseCase.findNext(); case operations.FIND_PREV: diff --git a/src/content/usecases/AddonSendmessageUseCase.ts b/src/content/usecases/AddonSendmessageUseCase.ts new file mode 100644 index 00000000..b5d50d3b --- /dev/null +++ b/src/content/usecases/AddonSendmessageUseCase.ts @@ -0,0 +1,17 @@ +import { injectable, inject } from "tsyringe"; +import ConsoleClient from "../client/ConsoleClient"; + +@injectable() +export default class AddonSendmessageUseCase { + constructor(@inject("ConsoleClient") private consoleClient: ConsoleClient) {} + + async sendMessage(extensionId: string, message: any) { + const sending = browser.runtime.sendMessage(extensionId, message); + sending.catch((reason: any) => { + this.consoleClient.error( + `Error on sending message to ${extensionId}: ${reason}` + ); + }); + return sending; + } +} diff --git a/src/shared/operations.ts b/src/shared/operations.ts index 35445020..21653df6 100644 --- a/src/shared/operations.ts +++ b/src/shared/operations.ts @@ -5,6 +5,7 @@ export const CANCEL = "cancel"; export const ADDON_ENABLE = "addon.enable"; export const ADDON_DISABLE = "addon.disable"; export const ADDON_TOGGLE_ENABLED = "addon.toggle.enabled"; +export const ADDON_SENDMESSAGE = "addon.sendmessage"; // Command export const COMMAND_SHOW = "command.show"; @@ -97,6 +98,12 @@ export interface AddonToggleEnabledOperation { type: typeof ADDON_TOGGLE_ENABLED; } +export interface AddonSendmessageOperation { + type: typeof ADDON_SENDMESSAGE; + extensionId: string; + message: string | Record; +} + export interface CommandShowOperation { type: typeof COMMAND_SHOW; } @@ -315,6 +322,7 @@ export type Operation = | AddonEnableOperation | AddonDisableOperation | AddonToggleEnabledOperation + | AddonSendmessageOperation | CommandShowOperation | CommandShowOpenOperation | CommandShowTabopenOperation @@ -409,12 +417,29 @@ const assertRequiredString = (obj: any, name: string) => { } }; +const assertRequiredObjectOrString = (obj: any, name: string) => { + if ( + !Object.prototype.hasOwnProperty.call(obj, name) || + !(typeof obj[name] === "string" || typeof obj[name] == "object") + ) { + throw new TypeError(`Missing object or string parameter: '${name}`); + } +}; + // eslint-disable-next-line complexity, max-lines-per-function export const valueOf = (o: any): Operation => { if (!Object.prototype.hasOwnProperty.call(o, "type")) { throw new TypeError(`Missing 'type' field`); } switch (o.type) { + case ADDON_SENDMESSAGE: + assertRequiredString(o, "extensionId"); + assertRequiredObjectOrString(o, "message"); + return { + type: o.type, + extensionId: o.extensionId, + message: o.message, + }; case COMMAND_SHOW_OPEN: case COMMAND_SHOW_TABOPEN: case COMMAND_SHOW_WINOPEN: