Types src/content

jh-changes
Shin'ya Ueoka 6 years ago
parent 992b3ac65d
commit d01db82c0d
  1. 2
      .eslintrc
  2. 1
      package.json
  3. 4
      src/background/controllers/OperationController.ts
  4. 2
      src/background/controllers/VersionController.ts
  5. 20
      src/background/domains/Setting.ts
  6. 2
      src/background/infrastructures/ConsoleClient.ts
  7. 2
      src/background/infrastructures/ContentMessageClient.ts
  8. 6
      src/background/infrastructures/ContentMessageListener.ts
  9. 6
      src/background/presenters/NotifyPresenter.ts
  10. 2
      src/background/usecases/VersionUseCase.ts
  11. 2
      src/console/actions/console.ts
  12. 11
      src/console/index.tsx
  13. 32
      src/content/MessageListener.ts
  14. 10
      src/content/actions/addon.ts
  15. 40
      src/content/actions/find.ts
  16. 12
      src/content/actions/follow-controller.ts
  17. 129
      src/content/actions/index.ts
  18. 6
      src/content/actions/input.ts
  19. 20
      src/content/actions/mark.ts
  20. 25
      src/content/actions/operation.ts
  21. 14
      src/content/actions/setting.ts
  22. 79
      src/content/components/common/follow.ts
  23. 33
      src/content/components/common/hint.ts
  24. 33
      src/content/components/common/index.ts
  25. 46
      src/content/components/common/input.ts
  26. 8
      src/content/components/common/keymapper.ts
  27. 2
      src/content/components/common/mark.ts
  28. 25
      src/content/components/top-content/find.ts
  29. 65
      src/content/components/top-content/follow-controller.ts
  30. 28
      src/content/components/top-content/index.ts
  31. 18
      src/content/console-frames.ts
  32. 8
      src/content/focuses.ts
  33. 10
      src/content/hint-key-producer.ts
  34. 9
      src/content/index.ts
  35. 45
      src/content/navigates.ts
  36. 13
      src/content/reducers/addon.ts
  37. 14
      src/content/reducers/find.ts
  38. 16
      src/content/reducers/follow-controller.ts
  39. 22
      src/content/reducers/index.ts
  40. 13
      src/content/reducers/input.ts
  41. 20
      src/content/reducers/mark.ts
  42. 11
      src/content/reducers/setting.ts
  43. 38
      src/content/scrolls.ts
  44. 8
      src/content/store/index.ts
  45. 8
      src/content/urls.ts
  46. 346
      src/shared/messages.ts
  47. 475
      src/shared/operations.ts
  48. 2
      src/shared/settings/validator.ts
  49. 2
      src/shared/utils/keys.ts
  50. 2
      test/content/actions/follow-controller.test.ts
  51. 2
      test/content/actions/input.test.ts
  52. 2
      test/content/actions/mark.test.ts
  53. 2
      test/content/actions/setting.test.ts
  54. 14
      test/content/components/common/input.test.ts
  55. 2
      test/content/reducers/addon.test.ts
  56. 2
      test/content/reducers/find.test.ts
  57. 2
      test/content/reducers/follow-controller.test.ts
  58. 2
      test/content/reducers/input.test.ts
  59. 2
      test/content/reducers/mark.test.ts
  60. 2
      test/content/reducers/setting.test.ts
  61. 41
      test/shared/operations.test.ts
  62. 9
      tsconfig.json

@ -35,6 +35,7 @@
"indent": ["error", 2], "indent": ["error", 2],
"jsx-quotes": ["error", "prefer-single"], "jsx-quotes": ["error", "prefer-single"],
"max-classes-per-file": "off", "max-classes-per-file": "off",
"max-lines": "off",
"max-params": ["error", 5], "max-params": ["error", 5],
"max-statements": ["error", 15], "max-statements": ["error", 15],
"multiline-comment-style": "off", "multiline-comment-style": "off",
@ -47,6 +48,7 @@
"no-console": ["error", { "allow": ["warn", "error"] }], "no-console": ["error", { "allow": ["warn", "error"] }],
"no-continue": "off", "no-continue": "off",
"no-empty-function": "off", "no-empty-function": "off",
"no-extra-parens": "off",
"no-magic-numbers": "off", "no-magic-numbers": "off",
"no-mixed-operators": "off", "no-mixed-operators": "off",
"no-plusplus": "off", "no-plusplus": "off",

@ -6,6 +6,7 @@
"build": "NODE_ENV=production webpack --mode production --progress --display-error-details", "build": "NODE_ENV=production webpack --mode production --progress --display-error-details",
"package": "npm run build && script/package", "package": "npm run build && script/package",
"lint": "eslint --ext .js,.jsx,.ts,.tsx src", "lint": "eslint --ext .js,.jsx,.ts,.tsx src",
"type-checks": "tsc",
"test": "karma start", "test": "karma start",
"test:e2e": "mocha --timeout 8000 e2e" "test:e2e": "mocha --timeout 8000 e2e"
}, },

@ -1,4 +1,4 @@
import operations from '../../shared/operations'; import * as operations from '../../shared/operations';
import FindUseCase from '../usecases/FindUseCase'; import FindUseCase from '../usecases/FindUseCase';
import ConsoleUseCase from '../usecases/ConsoleUseCase'; import ConsoleUseCase from '../usecases/ConsoleUseCase';
import TabUseCase from '../usecases/TabUseCase'; import TabUseCase from '../usecases/TabUseCase';
@ -25,7 +25,7 @@ export default class OperationController {
} }
// eslint-disable-next-line complexity, max-lines-per-function // eslint-disable-next-line complexity, max-lines-per-function
exec(operation: any): Promise<any> { exec(operation: operations.Operation): Promise<any> {
switch (operation.type) { switch (operation.type) {
case operations.TAB_CLOSE: case operations.TAB_CLOSE:
return this.tabUseCase.close(false); return this.tabUseCase.close(false);

@ -7,7 +7,7 @@ export default class VersionController {
this.versionUseCase = new VersionUseCase(); this.versionUseCase = new VersionUseCase();
} }
notify(): void { notify(): Promise<void> {
return this.versionUseCase.notify(); return this.versionUseCase.notify();
} }
} }

@ -1,22 +1,30 @@
import DefaultSettings from '../../shared/settings/default'; import DefaultSettings from '../../shared/settings/default';
import * as settingsValues from '../../shared/settings/values'; import * as settingsValues from '../../shared/settings/values';
type SettingValue = {
source: string,
json: string,
form: any
}
export default class Setting { export default class Setting {
constructor({ source, json, form }) { private obj: SettingValue;
constructor({ source, json, form }: SettingValue) {
this.obj = { this.obj = {
source, json, form source, json, form
}; };
} }
get source() { get source(): string {
return this.obj.source; return this.obj.source;
} }
get json() { get json(): string {
return this.obj.json; return this.obj.json;
} }
get form() { get form(): any {
return this.obj.form; return this.obj.form;
} }
@ -33,11 +41,11 @@ export default class Setting {
return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value }; return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value };
} }
serialize() { serialize(): SettingValue {
return this.obj; return this.obj;
} }
static deserialize(obj) { static deserialize(obj: SettingValue): Setting {
return new Setting({ source: obj.source, json: obj.json, form: obj.form }); return new Setting({ source: obj.source, json: obj.json, form: obj.form });
} }

@ -1,4 +1,4 @@
import messages from '../../shared/messages'; import * as messages from '../../shared/messages';
export default class ConsoleClient { export default class ConsoleClient {
showCommand(tabId: number, command: string): Promise<any> { showCommand(tabId: number, command: string): Promise<any> {

@ -1,4 +1,4 @@
import messages from '../../shared/messages'; import * as messages from '../../shared/messages';
export default class ContentMessageClient { export default class ContentMessageClient {
async broadcastSettingsChanged(): Promise<void> { async broadcastSettingsChanged(): Promise<void> {

@ -1,4 +1,4 @@
import messages from '../../shared/messages'; import * as messages from '../../shared/messages';
import CompletionGroup from '../domains/CompletionGroup'; import CompletionGroup from '../domains/CompletionGroup';
import CommandController from '../controllers/CommandController'; import CommandController from '../controllers/CommandController';
import SettingController from '../controllers/SettingController'; import SettingController from '../controllers/SettingController';
@ -68,7 +68,9 @@ export default class ContentMessageListener {
browser.runtime.onConnect.addListener(this.onConnected.bind(this)); browser.runtime.onConnect.addListener(this.onConnected.bind(this));
} }
onMessage(message: any, senderTab: browser.tabs.Tab): Promise<any> | any { onMessage(
message: messages.Message, senderTab: browser.tabs.Tab,
): Promise<any> | any {
switch (message.type) { switch (message.type) {
case messages.CONSOLE_QUERY_COMPLETIONS: case messages.CONSOLE_QUERY_COMPLETIONS:
return this.onConsoleQueryCompletions(message.text); return this.onConsoleQueryCompletions(message.text);

@ -1,11 +1,11 @@
const NOTIFICATION_ID = 'vimvixen-update'; const NOTIFICATION_ID = 'vimvixen-update';
export default class NotifyPresenter { export default class NotifyPresenter {
notify( async notify(
title: string, title: string,
message: string, message: string,
onclick: () => void, onclick: () => void,
): Promise<string> { ): Promise<void> {
const listener = (id: string) => { const listener = (id: string) => {
if (id !== NOTIFICATION_ID) { if (id !== NOTIFICATION_ID) {
return; return;
@ -17,7 +17,7 @@ export default class NotifyPresenter {
}; };
browser.notifications.onClicked.addListener(listener); browser.notifications.onClicked.addListener(listener);
return browser.notifications.create(NOTIFICATION_ID, { await browser.notifications.create(NOTIFICATION_ID, {
'type': 'basic', 'type': 'basic',
'iconUrl': browser.extension.getURL('resources/icon_48x48.png'), 'iconUrl': browser.extension.getURL('resources/icon_48x48.png'),
title, title,

@ -12,7 +12,7 @@ export default class VersionUseCase {
this.notifyPresenter = new NotifyPresenter(); this.notifyPresenter = new NotifyPresenter();
} }
notify(): Promise<string> { notify(): Promise<void> {
let title = `Vim Vixen ${manifest.version} has been installed`; let title = `Vim Vixen ${manifest.version} has been installed`;
let message = 'Click here to see release notes'; let message = 'Click here to see release notes';
let url = this.releaseNoteUrl(manifest.version); let url = this.releaseNoteUrl(manifest.version);

@ -1,4 +1,4 @@
import messages from '../../shared/messages'; import * as messages from '../../shared/messages';
import * as actions from './index'; import * as actions from './index';
const hide = (): actions.ConsoleAction => { const hide = (): actions.ConsoleAction => {

@ -1,4 +1,4 @@
import messages from '../shared/messages'; import * as messages from '../shared/messages';
import reducers from './reducers'; import reducers from './reducers';
import { createStore, applyMiddleware } from 'redux'; import { createStore, applyMiddleware } from 'redux';
import promise from 'redux-promise'; import promise from 'redux-promise';
@ -23,15 +23,16 @@ window.addEventListener('load', () => {
}); });
const onMessage = (message: any): any => { const onMessage = (message: any): any => {
switch (message.type) { let msg = messages.valueOf(message);
switch (msg.type) {
case messages.CONSOLE_SHOW_COMMAND: case messages.CONSOLE_SHOW_COMMAND:
return store.dispatch(consoleActions.showCommand(message.command)); return store.dispatch(consoleActions.showCommand(msg.command));
case messages.CONSOLE_SHOW_FIND: case messages.CONSOLE_SHOW_FIND:
return store.dispatch(consoleActions.showFind()); return store.dispatch(consoleActions.showFind());
case messages.CONSOLE_SHOW_ERROR: case messages.CONSOLE_SHOW_ERROR:
return store.dispatch(consoleActions.showError(message.text)); return store.dispatch(consoleActions.showError(msg.text));
case messages.CONSOLE_SHOW_INFO: case messages.CONSOLE_SHOW_INFO:
return store.dispatch(consoleActions.showInfo(message.text)); return store.dispatch(consoleActions.showInfo(msg.text));
case messages.CONSOLE_HIDE: case messages.CONSOLE_HIDE:
return store.dispatch(consoleActions.hide()); return store.dispatch(consoleActions.hide());
} }

@ -0,0 +1,32 @@
import { Message, valueOf } from '../shared/messages';
export type WebMessageSender = Window | MessagePort | ServiceWorker | null;
export type WebExtMessageSender = browser.runtime.MessageSender;
export default class MessageListener {
onWebMessage(
listener: (msg: Message, sender: WebMessageSender) => void,
) {
window.addEventListener('message', (event: MessageEvent) => {
let sender = event.source;
let message = null;
try {
message = JSON.parse(event.data);
} catch (e) {
// ignore unexpected message
return;
}
listener(message, sender);
});
}
onBackgroundMessage(
listener: (msg: Message, sender: WebExtMessageSender) => any,
) {
browser.runtime.onMessage.addListener(
(msg: any, sender: WebExtMessageSender) => {
listener(valueOf(msg), sender);
},
);
}
}

@ -1,11 +1,11 @@
import messages from 'shared/messages'; import * as messages from '../../shared/messages';
import actions from 'content/actions'; import * as actions from './index';
const enable = () => setEnabled(true); const enable = (): Promise<actions.AddonAction> => setEnabled(true);
const disable = () => setEnabled(false); const disable = (): Promise<actions.AddonAction> => setEnabled(false);
const setEnabled = async(enabled) => { const setEnabled = async(enabled: boolean): Promise<actions.AddonAction> => {
await browser.runtime.sendMessage({ await browser.runtime.sendMessage({
type: messages.ADDON_ENABLED_RESPONSE, type: messages.ADDON_ENABLED_RESPONSE,
enabled, enabled,

@ -5,28 +5,41 @@
// NOTE: window.find is not standard API // NOTE: window.find is not standard API
// https://developer.mozilla.org/en-US/docs/Web/API/Window/find // https://developer.mozilla.org/en-US/docs/Web/API/Window/find
import messages from 'shared/messages'; import * as messages from '../../shared/messages';
import actions from 'content/actions'; import * as actions from './index';
import * as consoleFrames from '../console-frames'; import * as consoleFrames from '../console-frames';
const find = (string, backwards) => { const find = (str: string, backwards: boolean): boolean => {
let caseSensitive = false; let caseSensitive = false;
let wrapScan = true; let wrapScan = true;
// NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work // NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work
// because of same origin policy // because of same origin policy
let found = window.find(string, caseSensitive, backwards, wrapScan);
// eslint-disable-next-line no-extra-parens
let found = (<any>window).find(str, caseSensitive, backwards, wrapScan);
if (found) { if (found) {
return found; return found;
} }
window.getSelection().removeAllRanges(); let sel = window.getSelection();
return window.find(string, caseSensitive, backwards, wrapScan); if (sel) {
sel.removeAllRanges();
}
// eslint-disable-next-line no-extra-parens
return (<any>window).find(str, caseSensitive, backwards, wrapScan);
}; };
const findNext = async(currentKeyword, reset, backwards) => { // eslint-disable-next-line max-statements
const findNext = async(
currentKeyword: string, reset: boolean, backwards: boolean,
): Promise<actions.FindAction> => {
if (reset) { if (reset) {
window.getSelection().removeAllRanges(); let sel = window.getSelection();
if (sel) {
sel.removeAllRanges();
}
} }
let keyword = currentKeyword; let keyword = currentKeyword;
@ -41,7 +54,8 @@ const findNext = async(currentKeyword, reset, backwards) => {
}); });
} }
if (!keyword) { if (!keyword) {
return consoleFrames.postError('No previous search keywords'); await consoleFrames.postError('No previous search keywords');
return { type: actions.NOOP };
} }
let found = find(keyword, backwards); let found = find(keyword, backwards);
if (found) { if (found) {
@ -57,11 +71,15 @@ const findNext = async(currentKeyword, reset, backwards) => {
}; };
}; };
const next = (currentKeyword, reset) => { const next = (
currentKeyword: string, reset: boolean,
): Promise<actions.FindAction> => {
return findNext(currentKeyword, reset, false); return findNext(currentKeyword, reset, false);
}; };
const prev = (currentKeyword, reset) => { const prev = (
currentKeyword: string, reset: boolean,
): Promise<actions.FindAction> => {
return findNext(currentKeyword, reset, true); return findNext(currentKeyword, reset, true);
}; };

@ -1,6 +1,8 @@
import actions from 'content/actions'; import * as actions from './index';
const enable = (newTab, background) => { const enable = (
newTab: boolean, background: boolean,
): actions.FollowAction => {
return { return {
type: actions.FOLLOW_CONTROLLER_ENABLE, type: actions.FOLLOW_CONTROLLER_ENABLE,
newTab, newTab,
@ -8,20 +10,20 @@ const enable = (newTab, background) => {
}; };
}; };
const disable = () => { const disable = (): actions.FollowAction => {
return { return {
type: actions.FOLLOW_CONTROLLER_DISABLE, type: actions.FOLLOW_CONTROLLER_DISABLE,
}; };
}; };
const keyPress = (key) => { const keyPress = (key: string): actions.FollowAction => {
return { return {
type: actions.FOLLOW_CONTROLLER_KEY_PRESS, type: actions.FOLLOW_CONTROLLER_KEY_PRESS,
key: key key: key
}; };
}; };
const backspace = () => { const backspace = (): actions.FollowAction => {
return { return {
type: actions.FOLLOW_CONTROLLER_BACKSPACE, type: actions.FOLLOW_CONTROLLER_BACKSPACE,
}; };

@ -1,31 +1,120 @@
export default { import Redux from 'redux';
// Enable/disable // Enable/disable
ADDON_SET_ENABLED: 'addon.set.enabled', export const ADDON_SET_ENABLED = 'addon.set.enabled';
// Find
export const FIND_SET_KEYWORD = 'find.set.keyword';
// Settings // Settings
SETTING_SET: 'setting.set', export const SETTING_SET = 'setting.set';
// User input // User input
INPUT_KEY_PRESS: 'input.key.press', export const INPUT_KEY_PRESS = 'input.key.press';
INPUT_CLEAR_KEYS: 'input.clear.keys', export const INPUT_CLEAR_KEYS = 'input.clear.keys';
// Completion // Completion
COMPLETION_SET_ITEMS: 'completion.set.items', export const COMPLETION_SET_ITEMS = 'completion.set.items';
COMPLETION_SELECT_NEXT: 'completions.select.next', export const COMPLETION_SELECT_NEXT = 'completions.select.next';
COMPLETION_SELECT_PREV: 'completions.select.prev', export const COMPLETION_SELECT_PREV = 'completions.select.prev';
// Follow // Follow
FOLLOW_CONTROLLER_ENABLE: 'follow.controller.enable', export const FOLLOW_CONTROLLER_ENABLE = 'follow.controller.enable';
FOLLOW_CONTROLLER_DISABLE: 'follow.controller.disable', export const FOLLOW_CONTROLLER_DISABLE = 'follow.controller.disable';
FOLLOW_CONTROLLER_KEY_PRESS: 'follow.controller.key.press', export const FOLLOW_CONTROLLER_KEY_PRESS = 'follow.controller.key.press';
FOLLOW_CONTROLLER_BACKSPACE: 'follow.controller.backspace', export const FOLLOW_CONTROLLER_BACKSPACE = 'follow.controller.backspace';
// Find
FIND_SET_KEYWORD: 'find.set.keyword',
// Mark // Mark
MARK_START_SET: 'mark.start.set', export const MARK_START_SET = 'mark.start.set';
MARK_START_JUMP: 'mark.start.jump', export const MARK_START_JUMP = 'mark.start.jump';
MARK_CANCEL: 'mark.cancel', export const MARK_CANCEL = 'mark.cancel';
MARK_SET_LOCAL: 'mark.set.local', export const MARK_SET_LOCAL = 'mark.set.local';
};
export const NOOP = 'noop';
export interface AddonSetEnabledAction extends Redux.Action {
type: typeof ADDON_SET_ENABLED;
enabled: boolean;
}
export interface FindSetKeywordAction extends Redux.Action {
type: typeof FIND_SET_KEYWORD;
keyword: string;
found: boolean;
}
export interface SettingSetAction extends Redux.Action {
type: typeof SETTING_SET;
value: any;
}
export interface InputKeyPressAction extends Redux.Action {
type: typeof INPUT_KEY_PRESS;
key: string;
}
export interface InputClearKeysAction extends Redux.Action {
type: typeof INPUT_CLEAR_KEYS;
}
export interface FollowControllerEnableAction extends Redux.Action {
type: typeof FOLLOW_CONTROLLER_ENABLE;
newTab: boolean;
background: boolean;
}
export interface FollowControllerDisableAction extends Redux.Action {
type: typeof FOLLOW_CONTROLLER_DISABLE;
}
export interface FollowControllerKeyPressAction extends Redux.Action {
type: typeof FOLLOW_CONTROLLER_KEY_PRESS;
key: string;
}
export interface FollowControllerBackspaceAction extends Redux.Action {
type: typeof FOLLOW_CONTROLLER_BACKSPACE;
}
export interface MarkStartSetAction extends Redux.Action {
type: typeof MARK_START_SET;
}
export interface MarkStartJumpAction extends Redux.Action {
type: typeof MARK_START_JUMP;
}
export interface MarkCancelAction extends Redux.Action {
type: typeof MARK_CANCEL;
}
export interface MarkSetLocalAction extends Redux.Action {
type: typeof MARK_SET_LOCAL;
key: string;
x: number;
y: number;
}
export interface NoopAction extends Redux.Action {
type: typeof NOOP;
}
export type AddonAction = AddonSetEnabledAction;
export type FindAction = FindSetKeywordAction | NoopAction;
export type SettingAction = SettingSetAction;
export type InputAction = InputKeyPressAction | InputClearKeysAction;
export type FollowAction =
FollowControllerEnableAction | FollowControllerDisableAction |
FollowControllerKeyPressAction | FollowControllerBackspaceAction;
export type MarkAction =
MarkStartSetAction | MarkStartJumpAction |
MarkCancelAction | MarkSetLocalAction | NoopAction;
export type Action =
AddonAction |
FindAction |
SettingAction |
InputAction |
FollowAction |
MarkAction |
NoopAction;

@ -1,13 +1,13 @@
import actions from 'content/actions'; import * as actions from './index';
const keyPress = (key) => { const keyPress = (key: string): actions.InputAction => {
return { return {
type: actions.INPUT_KEY_PRESS, type: actions.INPUT_KEY_PRESS,
key, key,
}; };
}; };
const clearKeys = () => { const clearKeys = (): actions.InputAction => {
return { return {
type: actions.INPUT_CLEAR_KEYS type: actions.INPUT_CLEAR_KEYS
}; };

@ -1,19 +1,19 @@
import actions from 'content/actions'; import * as actions from './index';
import messages from 'shared/messages'; import * as messages from '../../shared/messages';
const startSet = () => { const startSet = (): actions.MarkAction => {
return { type: actions.MARK_START_SET }; return { type: actions.MARK_START_SET };
}; };
const startJump = () => { const startJump = (): actions.MarkAction => {
return { type: actions.MARK_START_JUMP }; return { type: actions.MARK_START_JUMP };
}; };
const cancel = () => { const cancel = (): actions.MarkAction => {
return { type: actions.MARK_CANCEL }; return { type: actions.MARK_CANCEL };
}; };
const setLocal = (key, x, y) => { const setLocal = (key: string, x: number, y: number): actions.MarkAction => {
return { return {
type: actions.MARK_SET_LOCAL, type: actions.MARK_SET_LOCAL,
key, key,
@ -22,22 +22,22 @@ const setLocal = (key, x, y) => {
}; };
}; };
const setGlobal = (key, x, y) => { const setGlobal = (key: string, x: number, y: number): actions.MarkAction => {
browser.runtime.sendMessage({ browser.runtime.sendMessage({
type: messages.MARK_SET_GLOBAL, type: messages.MARK_SET_GLOBAL,
key, key,
x, x,
y, y,
}); });
return { type: '' }; return { type: actions.NOOP };
}; };
const jumpGlobal = (key) => { const jumpGlobal = (key: string): actions.MarkAction => {
browser.runtime.sendMessage({ browser.runtime.sendMessage({
type: messages.MARK_JUMP_GLOBAL, type: messages.MARK_JUMP_GLOBAL,
key, key,
}); });
return { type: '' }; return { type: actions.NOOP };
}; };
export { export {

@ -1,16 +1,21 @@
import operations from 'shared/operations'; import * as operations from '../../shared/operations';
import messages from 'shared/messages'; import * as actions from './index';
import * as scrolls from 'content/scrolls'; import * as messages from '../../shared/messages';
import * as navigates from 'content/navigates'; import * as scrolls from '../scrolls';
import * as focuses from 'content/focuses'; import * as navigates from '../navigates';
import * as urls from 'content/urls'; import * as focuses from '../focuses';
import * as consoleFrames from 'content/console-frames'; import * as urls from '../urls';
import * as consoleFrames from '../console-frames';
import * as addonActions from './addon'; import * as addonActions from './addon';
import * as markActions from './mark'; import * as markActions from './mark';
import * as properties from 'shared/settings/properties'; import * as properties from '../../shared/settings/properties';
// eslint-disable-next-line complexity, max-lines-per-function // eslint-disable-next-line complexity, max-lines-per-function
const exec = (operation, settings, addonEnabled) => { const exec = (
operation: operations.Operation,
settings: any,
addonEnabled: boolean,
): Promise<actions.Action> | actions.Action => {
let smoothscroll = settings.properties.smoothscroll || let smoothscroll = settings.properties.smoothscroll ||
properties.defaults.smoothscroll; properties.defaults.smoothscroll;
switch (operation.type) { switch (operation.type) {
@ -98,7 +103,7 @@ const exec = (operation, settings, addonEnabled) => {
operation, operation,
}); });
} }
return { type: '' }; return { type: actions.NOOP };
}; };
export { exec }; export { exec };

@ -1,15 +1,15 @@
import actions from 'content/actions'; import * as actions from './index';
import * as keyUtils from 'shared/utils/keys'; import * as keyUtils from '../../shared/utils/keys';
import operations from 'shared/operations'; import * as operations from '../../shared/operations';
import messages from 'shared/messages'; import * as messages from '../../shared/messages';
const reservedKeymaps = { const reservedKeymaps = {
'<Esc>': { type: operations.CANCEL }, '<Esc>': { type: operations.CANCEL },
'<C-[>': { type: operations.CANCEL }, '<C-[>': { type: operations.CANCEL },
}; };
const set = (value) => { const set = (value: any): actions.SettingAction => {
let entries = []; let entries: any[] = [];
if (value.keymaps) { if (value.keymaps) {
let keymaps = { ...value.keymaps, ...reservedKeymaps }; let keymaps = { ...value.keymaps, ...reservedKeymaps };
entries = Object.entries(keymaps).map((entry) => { entries = Object.entries(keymaps).map((entry) => {
@ -27,7 +27,7 @@ const set = (value) => {
}; };
}; };
const load = async() => { const load = async(): Promise<actions.SettingAction> => {
let settings = await browser.runtime.sendMessage({ let settings = await browser.runtime.sendMessage({
type: messages.SETTINGS_QUERY, type: messages.SETTINGS_QUERY,
}); });

@ -1,6 +1,8 @@
import messages from 'shared/messages'; import MessageListener from '../../MessageListener';
import Hint from './hint'; import Hint from './hint';
import * as dom from 'shared/utils/dom'; import * as dom from '../../../shared/utils/dom';
import * as messages from '../../../shared/messages';
import * as keyUtils from '../../../shared/utils/keys';
const TARGET_SELECTOR = [ const TARGET_SELECTOR = [
'a', 'button', 'input', 'textarea', 'area', 'a', 'button', 'input', 'textarea', 'area',
@ -8,8 +10,22 @@ const TARGET_SELECTOR = [
'[role="button"]', 'summary' '[role="button"]', 'summary'
].join(','); ].join(',');
interface Size {
width: number;
height: number;
}
interface Point {
x: number;
y: number;
}
const inViewport = (win, element, viewSize, framePosition) => { const inViewport = (
win: Window,
element: Element,
viewSize: Size,
framePosition: Point,
): boolean => {
let { let {
top, left, bottom, right top, left, bottom, right
} = dom.viewportRect(element); } = dom.viewportRect(element);
@ -30,34 +46,44 @@ const inViewport = (win, element, viewSize, framePosition) => {
return true; return true;
}; };
const isAriaHiddenOrAriaDisabled = (win, element) => { const isAriaHiddenOrAriaDisabled = (win: Window, element: Element): boolean => {
if (!element || win.document.documentElement === element) { if (!element || win.document.documentElement === element) {
return false; return false;
} }
for (let attr of ['aria-hidden', 'aria-disabled']) { for (let attr of ['aria-hidden', 'aria-disabled']) {
if (element.hasAttribute(attr)) { let value = element.getAttribute(attr);
let hidden = element.getAttribute(attr).toLowerCase(); if (value !== null) {
let hidden = value.toLowerCase();
if (hidden === '' || hidden === 'true') { if (hidden === '' || hidden === 'true') {
return true; return true;
} }
} }
} }
return isAriaHiddenOrAriaDisabled(win, element.parentNode); return isAriaHiddenOrAriaDisabled(win, element.parentElement as Element);
}; };
export default class Follow { export default class Follow {
constructor(win, store) { private win: Window;
private newTab: boolean;
private background: boolean;
private hints: {[key: string]: Hint };
private targets: HTMLElement[] = [];
constructor(win: Window) {
this.win = win; this.win = win;
this.store = store;
this.newTab = false; this.newTab = false;
this.background = false; this.background = false;
this.hints = {}; this.hints = {};
this.targets = []; this.targets = [];
messages.onMessage(this.onMessage.bind(this)); new MessageListener().onWebMessage(this.onMessage.bind(this));
} }
key(key) { key(key: keyUtils.Key): boolean {
if (Object.keys(this.hints).length === 0) { if (Object.keys(this.hints).length === 0) {
return false; return false;
} }
@ -69,7 +95,7 @@ export default class Follow {
return true; return true;
} }
openLink(element) { openLink(element: HTMLAreaElement|HTMLAnchorElement) {
// Browser prevent new tab by link with target='_blank' // Browser prevent new tab by link with target='_blank'
if (!this.newTab && element.getAttribute('target') !== '_blank') { if (!this.newTab && element.getAttribute('target') !== '_blank') {
element.click(); element.click();
@ -90,7 +116,7 @@ export default class Follow {
}); });
} }
countHints(sender, viewSize, framePosition) { countHints(sender: any, viewSize: Size, framePosition: Point) {
this.targets = Follow.getTargetElements(this.win, viewSize, framePosition); this.targets = Follow.getTargetElements(this.win, viewSize, framePosition);
sender.postMessage(JSON.stringify({ sender.postMessage(JSON.stringify({
type: messages.FOLLOW_RESPONSE_COUNT_TARGETS, type: messages.FOLLOW_RESPONSE_COUNT_TARGETS,
@ -98,7 +124,7 @@ export default class Follow {
}), '*'); }), '*');
} }
createHints(keysArray, newTab, background) { createHints(keysArray: string[], newTab: boolean, background: boolean) {
if (keysArray.length !== this.targets.length) { if (keysArray.length !== this.targets.length) {
throw new Error('illegal hint count'); throw new Error('illegal hint count');
} }
@ -113,7 +139,7 @@ export default class Follow {
} }
} }
showHints(keys) { showHints(keys: string) {
Object.keys(this.hints).filter(key => key.startsWith(keys)) Object.keys(this.hints).filter(key => key.startsWith(keys))
.forEach(key => this.hints[key].show()); .forEach(key => this.hints[key].show());
Object.keys(this.hints).filter(key => !key.startsWith(keys)) Object.keys(this.hints).filter(key => !key.startsWith(keys))
@ -128,18 +154,19 @@ export default class Follow {
this.targets = []; this.targets = [];
} }
activateHints(keys) { activateHints(keys: string) {
let hint = this.hints[keys]; let hint = this.hints[keys];
if (!hint) { if (!hint) {
return; return;
} }
let element = hint.target; let element = hint.getTarget();
switch (element.tagName.toLowerCase()) { switch (element.tagName.toLowerCase()) {
case 'a': case 'a':
return this.openLink(element as HTMLAnchorElement);
case 'area': case 'area':
return this.openLink(element); return this.openLink(element as HTMLAreaElement);
case 'input': case 'input':
switch (element.type) { switch ((element as HTMLInputElement).type) {
case 'file': case 'file':
case 'checkbox': case 'checkbox':
case 'radio': case 'radio':
@ -166,7 +193,7 @@ export default class Follow {
} }
} }
onMessage(message, sender) { onMessage(message: messages.Message, sender: any) {
switch (message.type) { switch (message.type) {
case messages.FOLLOW_REQUEST_COUNT_TARGETS: case messages.FOLLOW_REQUEST_COUNT_TARGETS:
return this.countHints(sender, message.viewSize, message.framePosition); return this.countHints(sender, message.viewSize, message.framePosition);
@ -178,19 +205,23 @@ export default class Follow {
case messages.FOLLOW_ACTIVATE: case messages.FOLLOW_ACTIVATE:
return this.activateHints(message.keys); return this.activateHints(message.keys);
case messages.FOLLOW_REMOVE_HINTS: case messages.FOLLOW_REMOVE_HINTS:
return this.removeHints(message.keys); return this.removeHints();
} }
} }
static getTargetElements(win, viewSize, framePosition) { static getTargetElements(
win: Window,
viewSize:
Size, framePosition: Point,
): HTMLElement[] {
let all = win.document.querySelectorAll(TARGET_SELECTOR); let all = win.document.querySelectorAll(TARGET_SELECTOR);
let filtered = Array.prototype.filter.call(all, (element) => { let filtered = Array.prototype.filter.call(all, (element: HTMLElement) => {
let style = win.getComputedStyle(element); let style = win.getComputedStyle(element);
// AREA's 'display' in Browser style is 'none' // AREA's 'display' in Browser style is 'none'
return (element.tagName === 'AREA' || style.display !== 'none') && return (element.tagName === 'AREA' || style.display !== 'none') &&
style.visibility !== 'hidden' && style.visibility !== 'hidden' &&
element.type !== 'hidden' && (element as HTMLInputElement).type !== 'hidden' &&
element.offsetHeight > 0 && element.offsetHeight > 0 &&
!isAriaHiddenOrAriaDisabled(win, element) && !isAriaHiddenOrAriaDisabled(win, element) &&
inViewport(win, element, viewSize, framePosition); inViewport(win, element, viewSize, framePosition);

@ -1,6 +1,11 @@
import * as dom from 'shared/utils/dom'; import * as dom from '../../../shared/utils/dom';
const hintPosition = (element) => { interface Point {
x: number;
y: number;
}
const hintPosition = (element: Element): Point => {
let { left, top, right, bottom } = dom.viewportRect(element); let { left, top, right, bottom } = dom.viewportRect(element);
if (element.tagName !== 'AREA') { if (element.tagName !== 'AREA') {
@ -14,17 +19,21 @@ const hintPosition = (element) => {
}; };
export default class Hint { export default class Hint {
constructor(target, tag) { private target: HTMLElement;
if (!(document.body instanceof HTMLElement)) {
throw new TypeError('target is not an HTMLElement');
}
this.target = target; private element: HTMLElement;
constructor(target: HTMLElement, tag: string) {
let doc = target.ownerDocument; let doc = target.ownerDocument;
if (doc === null) {
throw new TypeError('ownerDocument is null');
}
let { x, y } = hintPosition(target); let { x, y } = hintPosition(target);
let { scrollX, scrollY } = window; let { scrollX, scrollY } = window;
this.target = target;
this.element = doc.createElement('span'); this.element = doc.createElement('span');
this.element.className = 'vimvixen-hint'; this.element.className = 'vimvixen-hint';
this.element.textContent = tag; this.element.textContent = tag;
@ -35,15 +44,19 @@ export default class Hint {
doc.body.append(this.element); doc.body.append(this.element);
} }
show() { show(): void {
this.element.style.display = 'inline'; this.element.style.display = 'inline';
} }
hide() { hide(): void {
this.element.style.display = 'none'; this.element.style.display = 'none';
} }
remove() { remove(): void {
this.element.remove(); this.element.remove();
} }
getTarget(): HTMLElement {
return this.target;
}
} }

@ -2,33 +2,37 @@ import InputComponent from './input';
import FollowComponent from './follow'; import FollowComponent from './follow';
import MarkComponent from './mark'; import MarkComponent from './mark';
import KeymapperComponent from './keymapper'; import KeymapperComponent from './keymapper';
import * as settingActions from 'content/actions/setting'; import * as settingActions from '../../actions/setting';
import messages from 'shared/messages'; import * as messages from '../../../shared/messages';
import MessageListener from '../../MessageListener';
import * as addonActions from '../../actions/addon'; import * as addonActions from '../../actions/addon';
import * as blacklists from 'shared/blacklists'; import * as blacklists from '../../../shared/blacklists';
import * as keys from '../../../shared/utils/keys';
export default class Common { export default class Common {
constructor(win, store) { private win: Window;
const input = new InputComponent(win.document.body, store);
const follow = new FollowComponent(win, store); private store: any;
constructor(win: Window, store: any) {
const input = new InputComponent(win.document.body);
const follow = new FollowComponent(win);
const mark = new MarkComponent(win.document.body, store); const mark = new MarkComponent(win.document.body, store);
const keymapper = new KeymapperComponent(store); const keymapper = new KeymapperComponent(store);
input.onKey(key => follow.key(key)); input.onKey((key: keys.Key) => follow.key(key));
input.onKey(key => mark.key(key)); input.onKey((key: keys.Key) => mark.key(key));
input.onKey(key => keymapper.key(key)); input.onKey((key: keys.Key) => keymapper.key(key));
this.win = win; this.win = win;
this.store = store; this.store = store;
this.prevEnabled = undefined;
this.prevBlacklist = undefined;
this.reloadSettings(); this.reloadSettings();
messages.onMessage(this.onMessage.bind(this)); new MessageListener().onBackgroundMessage(this.onMessage.bind(this));
} }
onMessage(message) { onMessage(message: messages.Message) {
let { enabled } = this.store.getState().addon; let { enabled } = this.store.getState().addon;
switch (message.type) { switch (message.type) {
case messages.SETTINGS_CHANGED: case messages.SETTINGS_CHANGED:
@ -40,7 +44,8 @@ export default class Common {
reloadSettings() { reloadSettings() {
try { try {
this.store.dispatch(settingActions.load()).then(({ value: settings }) => { this.store.dispatch(settingActions.load())
.then(({ value: settings }: any) => {
let enabled = !blacklists.includes( let enabled = !blacklists.includes(
settings.blacklist, this.win.location.href settings.blacklist, this.win.location.href
); );

@ -1,12 +1,16 @@
import * as dom from 'shared/utils/dom'; import * as dom from '../../../shared/utils/dom';
import * as keys from 'shared/utils/keys'; import * as keys from '../../../shared/utils/keys';
const cancelKey = (e) => { const cancelKey = (e: KeyboardEvent): boolean => {
return e.key === 'Escape' || e.key === '[' && e.ctrlKey; return e.key === 'Escape' || e.key === '[' && e.ctrlKey;
}; };
export default class InputComponent { export default class InputComponent {
constructor(target) { private pressed: {[key: string]: string} = {};
private onKeyListeners: ((key: keys.Key) => boolean)[] = [];
constructor(target: HTMLElement) {
this.pressed = {}; this.pressed = {};
this.onKeyListeners = []; this.onKeyListeners = [];
@ -15,11 +19,11 @@ export default class InputComponent {
target.addEventListener('keyup', this.onKeyUp.bind(this)); target.addEventListener('keyup', this.onKeyUp.bind(this));
} }
onKey(cb) { onKey(cb: (key: keys.Key) => boolean) {
this.onKeyListeners.push(cb); this.onKeyListeners.push(cb);
} }
onKeyPress(e) { onKeyPress(e: KeyboardEvent) {
if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') { if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') {
return; return;
} }
@ -27,7 +31,7 @@ export default class InputComponent {
this.capture(e); this.capture(e);
} }
onKeyDown(e) { onKeyDown(e: KeyboardEvent) {
if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') { if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') {
return; return;
} }
@ -35,14 +39,19 @@ export default class InputComponent {
this.capture(e); this.capture(e);
} }
onKeyUp(e) { onKeyUp(e: KeyboardEvent) {
delete this.pressed[e.key]; delete this.pressed[e.key];
} }
capture(e) { // eslint-disable-next-line max-statements
if (this.fromInput(e)) { capture(e: KeyboardEvent) {
if (cancelKey(e) && e.target.blur) { let target = e.target;
e.target.blur(); if (!(target instanceof HTMLElement)) {
return;
}
if (this.fromInput(target)) {
if (cancelKey(e) && target.blur) {
target.blur();
} }
return; return;
} }
@ -63,13 +72,10 @@ export default class InputComponent {
} }
} }
fromInput(e) { fromInput(e: Element) {
if (!e.target) { return e instanceof HTMLInputElement ||
return false; e instanceof HTMLTextAreaElement ||
} e instanceof HTMLSelectElement ||
return e.target instanceof HTMLInputElement || dom.isContentEditable(e);
e.target instanceof HTMLTextAreaElement ||
e.target instanceof HTMLSelectElement ||
dom.isContentEditable(e.target);
} }
} }

@ -1,7 +1,7 @@
import * as inputActions from 'content/actions/input'; import * as inputActions from '../../actions/input';
import * as operationActions from 'content/actions/operation'; import * as operationActions from '../../actions/operation';
import operations from 'shared/operations'; import * as operations from '../../../shared/operations';
import * as keyUtils from 'shared/utils/keys'; import * as keyUtils from '../../../shared/utils/keys';
const mapStartsWith = (mapping, keys) => { const mapStartsWith = (mapping, keys) => {
if (mapping.length < keys.length) { if (mapping.length < keys.length) {

@ -3,7 +3,7 @@ import * as scrolls from 'content/scrolls';
import * as consoleFrames from 'content/console-frames'; import * as consoleFrames from 'content/console-frames';
import * as properties from 'shared/settings/properties'; import * as properties from 'shared/settings/properties';
const cancelKey = (key) => { const cancelKey = (key): boolean => {
return key.key === 'Esc' || key.key === '[' && key.ctrlKey; return key.key === 'Esc' || key.key === '[' && key.ctrlKey;
}; };

@ -1,15 +1,17 @@
import * as findActions from 'content/actions/find'; import * as findActions from '../../actions/find';
import messages from 'shared/messages'; import * as messages from '../../../shared/messages';
import MessageListener from '../../MessageListener';
export default class FindComponent { export default class FindComponent {
constructor(win, store) { private store: any;
this.win = win;
constructor(store: any) {
this.store = store; this.store = store;
messages.onMessage(this.onMessage.bind(this)); new MessageListener().onWebMessage(this.onMessage.bind(this));
} }
onMessage(message) { onMessage(message: messages.Message) {
switch (message.type) { switch (message.type) {
case messages.CONSOLE_ENTER_FIND: case messages.CONSOLE_ENTER_FIND:
return this.start(message.text); return this.start(message.text);
@ -20,22 +22,25 @@ export default class FindComponent {
} }
} }
start(text) { start(text: string) {
let state = this.store.getState().find; let state = this.store.getState().find;
if (text.length === 0) { if (text.length === 0) {
return this.store.dispatch(findActions.next(state.keyword, true)); return this.store.dispatch(
findActions.next(state.keyword as string, true));
} }
return this.store.dispatch(findActions.next(text, true)); return this.store.dispatch(findActions.next(text, true));
} }
next() { next() {
let state = this.store.getState().find; let state = this.store.getState().find;
return this.store.dispatch(findActions.next(state.keyword, false)); return this.store.dispatch(
findActions.next(state.keyword as string, false));
} }
prev() { prev() {
let state = this.store.getState().find; let state = this.store.getState().find;
return this.store.dispatch(findActions.prev(state.keyword, false)); return this.store.dispatch(
findActions.prev(state.keyword as string, false));
} }
} }

@ -1,30 +1,46 @@
import * as followControllerActions from 'content/actions/follow-controller'; import * as followControllerActions from '../../actions/follow-controller';
import messages from 'shared/messages'; import * as messages from '../../../shared/messages';
import HintKeyProducer from 'content/hint-key-producer'; import MessageListener, { WebMessageSender } from '../../MessageListener';
import * as properties from 'shared/settings/properties'; import HintKeyProducer from '../../hint-key-producer';
import * as properties from '../../../shared/settings/properties';
const broadcastMessage = (win, message) => { const broadcastMessage = (win: Window, message: messages.Message): void => {
let json = JSON.stringify(message); let json = JSON.stringify(message);
let frames = [window.self].concat(Array.from(window.frames)); let frames = [win.self].concat(Array.from(win.frames as any));
frames.forEach(frame => frame.postMessage(json, '*')); frames.forEach(frame => frame.postMessage(json, '*'));
}; };
export default class FollowController { export default class FollowController {
constructor(win, store) { private win: Window;
private store: any;
private state: {
enabled?: boolean;
newTab?: boolean;
background?: boolean;
keys?: string,
};
private keys: string[];
private producer: HintKeyProducer | null;
constructor(win: Window, store: any) {
this.win = win; this.win = win;
this.store = store; this.store = store;
this.state = {}; this.state = {};
this.keys = []; this.keys = [];
this.producer = null; this.producer = null;
messages.onMessage(this.onMessage.bind(this)); new MessageListener().onWebMessage(this.onMessage.bind(this));
store.subscribe(() => { store.subscribe(() => {
this.update(); this.update();
}); });
} }
onMessage(message, sender) { onMessage(message: messages.Message, sender: WebMessageSender) {
switch (message.type) { switch (message.type) {
case messages.FOLLOW_START: case messages.FOLLOW_START:
return this.store.dispatch( return this.store.dispatch(
@ -36,7 +52,7 @@ export default class FollowController {
} }
} }
update() { update(): void {
let prevState = this.state; let prevState = this.state;
this.state = this.store.getState().followController; this.state = this.store.getState().followController;
@ -49,8 +65,10 @@ export default class FollowController {
} }
} }
updateHints() { updateHints(): void {
let shown = this.keys.filter(key => key.startsWith(this.state.keys)); let shown = this.keys.filter((key) => {
return key.startsWith(this.state.keys as string);
});
if (shown.length === 1) { if (shown.length === 1) {
this.activate(); this.activate();
this.store.dispatch(followControllerActions.disable()); this.store.dispatch(followControllerActions.disable());
@ -58,18 +76,18 @@ export default class FollowController {
broadcastMessage(this.win, { broadcastMessage(this.win, {
type: messages.FOLLOW_SHOW_HINTS, type: messages.FOLLOW_SHOW_HINTS,
keys: this.state.keys, keys: this.state.keys as string,
}); });
} }
activate() { activate(): void {
broadcastMessage(this.win, { broadcastMessage(this.win, {
type: messages.FOLLOW_ACTIVATE, type: messages.FOLLOW_ACTIVATE,
keys: this.state.keys, keys: this.state.keys as string,
}); });
} }
keyPress(key, ctrlKey) { keyPress(key: string, ctrlKey: boolean): boolean {
if (key === '[' && ctrlKey) { if (key === '[' && ctrlKey) {
this.store.dispatch(followControllerActions.disable()); this.store.dispatch(followControllerActions.disable());
return true; return true;
@ -107,25 +125,28 @@ export default class FollowController {
viewSize: { width: viewWidth, height: viewHeight }, viewSize: { width: viewWidth, height: viewHeight },
framePosition: { x: 0, y: 0 }, framePosition: { x: 0, y: 0 },
}), '*'); }), '*');
frameElements.forEach((element) => { frameElements.forEach((ele) => {
let { left: frameX, top: frameY } = element.getBoundingClientRect(); let { left: frameX, top: frameY } = ele.getBoundingClientRect();
let message = JSON.stringify({ let message = JSON.stringify({
type: messages.FOLLOW_REQUEST_COUNT_TARGETS, type: messages.FOLLOW_REQUEST_COUNT_TARGETS,
viewSize: { width: viewWidth, height: viewHeight }, viewSize: { width: viewWidth, height: viewHeight },
framePosition: { x: frameX, y: frameY }, framePosition: { x: frameX, y: frameY },
}); });
element.contentWindow.postMessage(message, '*'); if (ele instanceof HTMLFrameElement && ele.contentWindow ||
ele instanceof HTMLIFrameElement && ele.contentWindow) {
ele.contentWindow.postMessage(message, '*');
}
}); });
} }
create(count, sender) { create(count: number, sender: WebMessageSender) {
let produced = []; let produced = [];
for (let i = 0; i < count; ++i) { for (let i = 0; i < count; ++i) {
produced.push(this.producer.produce()); produced.push((this.producer as HintKeyProducer).produce());
} }
this.keys = this.keys.concat(produced); this.keys = this.keys.concat(produced);
sender.postMessage(JSON.stringify({ (sender as Window).postMessage(JSON.stringify({
type: messages.FOLLOW_CREATE_HINTS, type: messages.FOLLOW_CREATE_HINTS,
keysArray: produced, keysArray: produced,
newTab: this.state.newTab, newTab: this.state.newTab,

@ -2,33 +2,43 @@ import CommonComponent from '../common';
import FollowController from './follow-controller'; import FollowController from './follow-controller';
import FindComponent from './find'; import FindComponent from './find';
import * as consoleFrames from '../../console-frames'; import * as consoleFrames from '../../console-frames';
import messages from 'shared/messages'; import * as messages from '../../../shared/messages';
import * as scrolls from 'content/scrolls'; import MessageListener from '../../MessageListener';
import * as scrolls from '../../scrolls';
export default class TopContent { export default class TopContent {
private win: Window;
constructor(win, store) { private store: any;
constructor(win: Window, store: any) {
this.win = win; this.win = win;
this.store = store; this.store = store;
new CommonComponent(win, store); // eslint-disable-line no-new new CommonComponent(win, store); // eslint-disable-line no-new
new FollowController(win, store); // eslint-disable-line no-new new FollowController(win, store); // eslint-disable-line no-new
new FindComponent(win, store); // eslint-disable-line no-new new FindComponent(store); // eslint-disable-line no-new
// TODO make component // TODO make component
consoleFrames.initialize(this.win.document); consoleFrames.initialize(this.win.document);
messages.onMessage(this.onMessage.bind(this)); new MessageListener().onWebMessage(this.onWebMessage.bind(this));
new MessageListener().onBackgroundMessage(
this.onBackgroundMessage.bind(this));
} }
onMessage(message) { onWebMessage(message: messages.Message) {
let addonState = this.store.getState().addon;
switch (message.type) { switch (message.type) {
case messages.CONSOLE_UNFOCUS: case messages.CONSOLE_UNFOCUS:
this.win.focus(); this.win.focus();
consoleFrames.blur(window.document); consoleFrames.blur(window.document);
return Promise.resolve(); }
}
onBackgroundMessage(message: messages.Message) {
let addonState = this.store.getState().addon;
switch (message.type) {
case messages.ADDON_ENABLED_QUERY: case messages.ADDON_ENABLED_QUERY:
return Promise.resolve({ return Promise.resolve({
type: messages.ADDON_ENABLED_RESPONSE, type: messages.ADDON_ENABLED_RESPONSE,

@ -1,6 +1,6 @@
import messages from 'shared/messages'; import * as messages from '../shared/messages';
const initialize = (doc) => { const initialize = (doc: Document): HTMLIFrameElement => {
let iframe = doc.createElement('iframe'); let iframe = doc.createElement('iframe');
iframe.src = browser.runtime.getURL('build/console.html'); iframe.src = browser.runtime.getURL('build/console.html');
iframe.id = 'vimvixen-console-frame'; iframe.id = 'vimvixen-console-frame';
@ -10,13 +10,13 @@ const initialize = (doc) => {
return iframe; return iframe;
}; };
const blur = (doc) => { const blur = (doc: Document) => {
let iframe = doc.getElementById('vimvixen-console-frame'); let ele = doc.getElementById('vimvixen-console-frame') as HTMLIFrameElement;
iframe.blur(); ele.blur();
}; };
const postError = (text) => { const postError = (text: string): Promise<any> => {
browser.runtime.sendMessage({ return browser.runtime.sendMessage({
type: messages.CONSOLE_FRAME_MESSAGE, type: messages.CONSOLE_FRAME_MESSAGE,
message: { message: {
type: messages.CONSOLE_SHOW_ERROR, type: messages.CONSOLE_SHOW_ERROR,
@ -25,8 +25,8 @@ const postError = (text) => {
}); });
}; };
const postInfo = (text) => { const postInfo = (text: string): Promise<any> => {
browser.runtime.sendMessage({ return browser.runtime.sendMessage({
type: messages.CONSOLE_FRAME_MESSAGE, type: messages.CONSOLE_FRAME_MESSAGE,
message: { message: {
type: messages.CONSOLE_SHOW_INFO, type: messages.CONSOLE_SHOW_INFO,

@ -1,11 +1,13 @@
import * as doms from 'shared/utils/dom'; import * as doms from '../shared/utils/dom';
const focusInput = () => { const focusInput = (): void => {
let inputTypes = ['email', 'number', 'search', 'tel', 'text', 'url']; let inputTypes = ['email', 'number', 'search', 'tel', 'text', 'url'];
let inputSelector = inputTypes.map(type => `input[type=${type}]`).join(','); let inputSelector = inputTypes.map(type => `input[type=${type}]`).join(',');
let targets = window.document.querySelectorAll(inputSelector + ',textarea'); let targets = window.document.querySelectorAll(inputSelector + ',textarea');
let target = Array.from(targets).find(doms.isVisible); let target = Array.from(targets).find(doms.isVisible);
if (target) { if (target instanceof HTMLInputElement) {
target.focus();
} else if (target instanceof HTMLTextAreaElement) {
target.focus(); target.focus();
} }
}; };

@ -1,5 +1,9 @@
export default class HintKeyProducer { export default class HintKeyProducer {
constructor(charset) { private charset: string;
private counter: number[];
constructor(charset: string) {
if (charset.length === 0) { if (charset.length === 0) {
throw new TypeError('charset is empty'); throw new TypeError('charset is empty');
} }
@ -8,13 +12,13 @@ export default class HintKeyProducer {
this.counter = []; this.counter = [];
} }
produce() { produce(): string {
this.increment(); this.increment();
return this.counter.map(x => this.charset[x]).join(''); return this.counter.map(x => this.charset[x]).join('');
} }
increment() { private increment(): void {
let max = this.charset.length - 1; let max = this.charset.length - 1;
if (this.counter.every(x => x === max)) { if (this.counter.every(x => x === max)) {
this.counter = new Array(this.counter.length + 1).fill(0); this.counter = new Array(this.counter.length + 1).fill(0);

@ -1,14 +1,9 @@
import { createStore, applyMiddleware } from 'redux';
import promise from 'redux-promise';
import reducers from 'content/reducers';
import TopContentComponent from './components/top-content'; import TopContentComponent from './components/top-content';
import FrameContentComponent from './components/frame-content'; import FrameContentComponent from './components/frame-content';
import consoleFrameStyle from './site-style'; import consoleFrameStyle from './site-style';
import { newStore } from './store';
const store = createStore( const store = newStore();
reducers,
applyMiddleware(promise),
);
if (window.self === window.top) { if (window.self === window.top) {
new TopContentComponent(window, store); // eslint-disable-line no-new new TopContentComponent(window, store); // eslint-disable-line no-new

@ -1,58 +1,63 @@
const REL_PATTERN = { const REL_PATTERN: {[key: string]: RegExp} = {
prev: /^(?:prev(?:ious)?|older)\b|\u2039|\u2190|\xab|\u226a|<</i, prev: /^(?:prev(?:ious)?|older)\b|\u2039|\u2190|\xab|\u226a|<</i,
next: /^(?:next|newer)\b|\u203a|\u2192|\xbb|\u226b|>>/i, next: /^(?:next|newer)\b|\u203a|\u2192|\xbb|\u226b|>>/i,
}; };
// Return the last element in the document matching the supplied selector // Return the last element in the document matching the supplied selector
// and the optional filter, or null if there are no matches. // and the optional filter, or null if there are no matches.
const selectLast = (win, selector, filter) => { // eslint-disable-next-line func-style
let nodes = win.document.querySelectorAll(selector); function selectLast<E extends Element>(
win: Window,
selector: string,
filter?: (e: E) => boolean,
): E | null {
let nodes = Array.from(
win.document.querySelectorAll(selector) as NodeListOf<E>
);
if (filter) { if (filter) {
nodes = Array.from(nodes).filter(filter); nodes = nodes.filter(filter);
} }
return nodes.length ? nodes[nodes.length - 1] : null; return nodes.length ? nodes[nodes.length - 1] : null;
}; }
const historyPrev = (win) => { const historyPrev = (win: Window): void => {
win.history.back(); win.history.back();
}; };
const historyNext = (win) => { const historyNext = (win: Window): void => {
win.history.forward(); win.history.forward();
}; };
// Code common to linkPrev and linkNext which navigates to the specified page. // Code common to linkPrev and linkNext which navigates to the specified page.
const linkRel = (win, rel) => { const linkRel = (win: Window, rel: string): void => {
let link = selectLast(win, `link[rel~=${rel}][href]`); let link = selectLast<HTMLLinkElement>(win, `link[rel~=${rel}][href]`);
if (link) { if (link) {
win.location = link.href; win.location.href = link.href;
return; return;
} }
const pattern = REL_PATTERN[rel]; const pattern = REL_PATTERN[rel];
link = selectLast(win, `a[rel~=${rel}][href]`) || let a = selectLast<HTMLAnchorElement>(win, `a[rel~=${rel}][href]`) ||
// `innerText` is much slower than `textContent`, but produces much better // `innerText` is much slower than `textContent`, but produces much better
// (i.e. less unexpected) results // (i.e. less unexpected) results
selectLast(win, 'a[href]', lnk => pattern.test(lnk.innerText)); selectLast(win, 'a[href]', lnk => pattern.test(lnk.innerText));
if (link) { if (a) {
link.click(); a.click();
} }
}; };
const linkPrev = (win) => { const linkPrev = (win: Window): void => {
linkRel(win, 'prev'); linkRel(win, 'prev');
}; };
const linkNext = (win) => { const linkNext = (win: Window): void => {
linkRel(win, 'next'); linkRel(win, 'next');
}; };
const parent = (win) => { const parent = (win: Window): void => {
const loc = win.location; const loc = win.location;
if (loc.hash !== '') { if (loc.hash !== '') {
loc.hash = ''; loc.hash = '';
@ -71,8 +76,8 @@ const parent = (win) => {
} }
}; };
const root = (win) => { const root = (win: Window): void => {
win.location = win.location.origin; win.location.href = win.location.origin;
}; };
export { historyPrev, historyNext, linkPrev, linkNext, parent, root }; export { historyPrev, historyNext, linkPrev, linkNext, parent, root };

@ -1,10 +1,17 @@
import actions from 'content/actions'; import * as actions from '../actions';
const defaultState = { export interface State {
enabled: boolean;
}
const defaultState: State = {
enabled: true, enabled: true,
}; };
export default function reducer(state = defaultState, action = {}) { export default function reducer(
state: State = defaultState,
action: actions.AddonAction,
): State {
switch (action.type) { switch (action.type) {
case actions.ADDON_SET_ENABLED: case actions.ADDON_SET_ENABLED:
return { ...state, return { ...state,

@ -1,11 +1,19 @@
import actions from 'content/actions'; import * as actions from '../actions';
const defaultState = { export interface State {
keyword: string | null;
found: boolean;
}
const defaultState: State = {
keyword: null, keyword: null,
found: false, found: false,
}; };
export default function reducer(state = defaultState, action = {}) { export default function reducer(
state: State = defaultState,
action: actions.FindAction,
): State {
switch (action.type) { switch (action.type) {
case actions.FIND_SET_KEYWORD: case actions.FIND_SET_KEYWORD:
return { ...state, return { ...state,

@ -1,13 +1,23 @@
import actions from 'content/actions'; import * as actions from '../actions';
const defaultState = { export interface State {
enabled: boolean;
newTab: boolean;
background: boolean;
keys: string,
}
const defaultState: State = {
enabled: false, enabled: false,
newTab: false, newTab: false,
background: false, background: false,
keys: '', keys: '',
}; };
export default function reducer(state = defaultState, action = {}) { export default function reducer(
state: State = defaultState,
action: actions.FollowAction,
): State {
switch (action.type) { switch (action.type) {
case actions.FOLLOW_CONTROLLER_ENABLE: case actions.FOLLOW_CONTROLLER_ENABLE:
return { ...state, return { ...state,

@ -1,10 +1,20 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import addon from './addon'; import addon, { State as AddonState } from './addon';
import find from './find'; import find, { State as FindState } from './find';
import setting from './setting'; import setting, { State as SettingState } from './setting';
import input from './input'; import input, { State as InputState } from './input';
import followController from './follow-controller'; import followController, { State as FollowControllerState }
import mark from './mark'; from './follow-controller';
import mark, { State as MarkState } from './mark';
export interface State {
addon: AddonState;
find: FindState;
setting: SettingState;
input: InputState;
followController: FollowControllerState;
mark: MarkState;
}
export default combineReducers({ export default combineReducers({
addon, find, setting, input, followController, mark, addon, find, setting, input, followController, mark,

@ -1,10 +1,17 @@
import actions from 'content/actions'; import * as actions from '../actions';
const defaultState = { export interface State {
keys: string[];
}
const defaultState: State = {
keys: [] keys: []
}; };
export default function reducer(state = defaultState, action = {}) { export default function reducer(
state: State = defaultState,
action: actions.InputAction,
): State {
switch (action.type) { switch (action.type) {
case actions.INPUT_KEY_PRESS: case actions.INPUT_KEY_PRESS:
return { ...state, return { ...state,

@ -1,12 +1,26 @@
import actions from 'content/actions'; import * as actions from '../actions';
const defaultState = { interface Mark {
x: number;
y: number;
}
export interface State {
setMode: boolean;
jumpMode: boolean;
marks: { [key: string]: Mark };
}
const defaultState: State = {
setMode: false, setMode: false,
jumpMode: false, jumpMode: false,
marks: {}, marks: {},
}; };
export default function reducer(state = defaultState, action = {}) { export default function reducer(
state: State = defaultState,
action: actions.MarkAction,
): State {
switch (action.type) { switch (action.type) {
case actions.MARK_START_SET: case actions.MARK_START_SET:
return { ...state, setMode: true }; return { ...state, setMode: true };

@ -1,11 +1,18 @@
import actions from 'content/actions'; import * as actions from '../actions';
export interface State {
keymaps: any[];
}
const defaultState = { const defaultState = {
// keymaps is and arrays of key-binding pairs, which is entries of Map // keymaps is and arrays of key-binding pairs, which is entries of Map
keymaps: [], keymaps: [],
}; };
export default function reducer(state = defaultState, action = {}) { export default function reducer(
state: State = defaultState,
action: actions.SettingAction,
): State {
switch (action.type) { switch (action.type) {
case actions.SETTING_SET: case actions.SETTING_SET:
return { ...action.value }; return { ...action.value };

@ -1,19 +1,19 @@
import * as doms from 'shared/utils/dom'; import * as doms from '../shared/utils/dom';
const SCROLL_DELTA_X = 64; const SCROLL_DELTA_X = 64;
const SCROLL_DELTA_Y = 64; const SCROLL_DELTA_Y = 64;
// dirty way to store scrolling state on globally // dirty way to store scrolling state on globally
let scrolling = false; let scrolling = false;
let lastTimeoutId = null; let lastTimeoutId: number | null = null;
const isScrollableStyle = (element) => { const isScrollableStyle = (element: Element): boolean => {
let { overflowX, overflowY } = window.getComputedStyle(element); let { overflowX, overflowY } = window.getComputedStyle(element);
return !(overflowX !== 'scroll' && overflowX !== 'auto' && return !(overflowX !== 'scroll' && overflowX !== 'auto' &&
overflowY !== 'scroll' && overflowY !== 'auto'); overflowY !== 'scroll' && overflowY !== 'auto');
}; };
const isOverflowed = (element) => { const isOverflowed = (element: Element): boolean => {
return element.scrollWidth > element.clientWidth || return element.scrollWidth > element.clientWidth ||
element.scrollHeight > element.clientHeight; element.scrollHeight > element.clientHeight;
}; };
@ -22,7 +22,7 @@ const isOverflowed = (element) => {
// this method is called by each scrolling, and the returned value of this // this method is called by each scrolling, and the returned value of this
// method is not cached. That does not cause performance issue because in the // method is not cached. That does not cause performance issue because in the
// most pages, the window is root element i,e, documentElement. // most pages, the window is root element i,e, documentElement.
const findScrollable = (element) => { const findScrollable = (element: Element): Element | null => {
if (isScrollableStyle(element) && isOverflowed(element)) { if (isScrollableStyle(element) && isOverflowed(element)) {
return element; return element;
} }
@ -56,12 +56,16 @@ const resetScrolling = () => {
}; };
class Scroller { class Scroller {
constructor(element, smooth) { private element: Element;
private smooth: boolean;
constructor(element: Element, smooth: boolean) {
this.element = element; this.element = element;
this.smooth = smooth; this.smooth = smooth;
} }
scrollTo(x, y) { scrollTo(x: number, y: number): void {
if (!this.smooth) { if (!this.smooth) {
this.element.scrollTo(x, y); this.element.scrollTo(x, y);
return; return;
@ -74,13 +78,13 @@ class Scroller {
this.prepareReset(); this.prepareReset();
} }
scrollBy(x, y) { scrollBy(x: number, y: number): void {
let left = this.element.scrollLeft + x; let left = this.element.scrollLeft + x;
let top = this.element.scrollTop + y; let top = this.element.scrollTop + y;
this.scrollTo(left, top); this.scrollTo(left, top);
} }
prepareReset() { prepareReset(): void {
scrolling = true; scrolling = true;
if (lastTimeoutId) { if (lastTimeoutId) {
clearTimeout(lastTimeoutId); clearTimeout(lastTimeoutId);
@ -95,7 +99,7 @@ const getScroll = () => {
return { x: target.scrollLeft, y: target.scrollTop }; return { x: target.scrollLeft, y: target.scrollTop };
}; };
const scrollVertically = (count, smooth) => { const scrollVertically = (count: number, smooth: boolean): void => {
let target = scrollTarget(); let target = scrollTarget();
let delta = SCROLL_DELTA_Y * count; let delta = SCROLL_DELTA_Y * count;
if (scrolling) { if (scrolling) {
@ -104,7 +108,7 @@ const scrollVertically = (count, smooth) => {
new Scroller(target, smooth).scrollBy(0, delta); new Scroller(target, smooth).scrollBy(0, delta);
}; };
const scrollHorizonally = (count, smooth) => { const scrollHorizonally = (count: number, smooth: boolean): void => {
let target = scrollTarget(); let target = scrollTarget();
let delta = SCROLL_DELTA_X * count; let delta = SCROLL_DELTA_X * count;
if (scrolling) { if (scrolling) {
@ -113,7 +117,7 @@ const scrollHorizonally = (count, smooth) => {
new Scroller(target, smooth).scrollBy(delta, 0); new Scroller(target, smooth).scrollBy(delta, 0);
}; };
const scrollPages = (count, smooth) => { const scrollPages = (count: number, smooth: boolean): void => {
let target = scrollTarget(); let target = scrollTarget();
let height = target.clientHeight; let height = target.clientHeight;
let delta = height * count; let delta = height * count;
@ -123,33 +127,33 @@ const scrollPages = (count, smooth) => {
new Scroller(target, smooth).scrollBy(0, delta); new Scroller(target, smooth).scrollBy(0, delta);
}; };
const scrollTo = (x, y, smooth) => { const scrollTo = (x: number, y: number, smooth: boolean): void => {
let target = scrollTarget(); let target = scrollTarget();
new Scroller(target, smooth).scrollTo(x, y); new Scroller(target, smooth).scrollTo(x, y);
}; };
const scrollToTop = (smooth) => { const scrollToTop = (smooth: boolean): void => {
let target = scrollTarget(); let target = scrollTarget();
let x = target.scrollLeft; let x = target.scrollLeft;
let y = 0; let y = 0;
new Scroller(target, smooth).scrollTo(x, y); new Scroller(target, smooth).scrollTo(x, y);
}; };
const scrollToBottom = (smooth) => { const scrollToBottom = (smooth: boolean): void => {
let target = scrollTarget(); let target = scrollTarget();
let x = target.scrollLeft; let x = target.scrollLeft;
let y = target.scrollHeight; let y = target.scrollHeight;
new Scroller(target, smooth).scrollTo(x, y); new Scroller(target, smooth).scrollTo(x, y);
}; };
const scrollToHome = (smooth) => { const scrollToHome = (smooth: boolean): void => {
let target = scrollTarget(); let target = scrollTarget();
let x = 0; let x = 0;
let y = target.scrollTop; let y = target.scrollTop;
new Scroller(target, smooth).scrollTo(x, y); new Scroller(target, smooth).scrollTo(x, y);
}; };
const scrollToEnd = (smooth) => { const scrollToEnd = (smooth: boolean): void => {
let target = scrollTarget(); let target = scrollTarget();
let x = target.scrollWidth; let x = target.scrollWidth;
let y = target.scrollTop; let y = target.scrollTop;

@ -0,0 +1,8 @@
import promise from 'redux-promise';
import reducers from '../reducers';
import { createStore, applyMiddleware } from 'redux';
export const newStore = () => createStore(
reducers,
applyMiddleware(promise),
);

@ -1,7 +1,7 @@
import messages from 'shared/messages'; import * as messages from '../shared/messages';
import * as urls from '../shared/urls'; import * as urls from '../shared/urls';
const yank = (win) => { const yank = (win: Window) => {
let input = win.document.createElement('input'); let input = win.document.createElement('input');
win.document.body.append(input); win.document.body.append(input);
@ -15,7 +15,7 @@ const yank = (win) => {
input.remove(); input.remove();
}; };
const paste = (win, newTab, searchSettings) => { const paste = (win: Window, newTab: boolean, searchSettings: any) => {
let textarea = win.document.createElement('textarea'); let textarea = win.document.createElement('textarea');
win.document.body.append(textarea); win.document.body.append(textarea);
@ -25,7 +25,7 @@ const paste = (win, newTab, searchSettings) => {
textarea.focus(); textarea.focus();
if (win.document.execCommand('paste')) { if (win.document.execCommand('paste')) {
let value = textarea.textContent; let value = textarea.textContent as string;
let url = urls.searchUrl(value, searchSettings); let url = urls.searchUrl(value, searchSettings);
browser.runtime.sendMessage({ browser.runtime.sendMessage({
type: messages.OPEN_URL, type: messages.OPEN_URL,

@ -1,78 +1,276 @@
type WebMessageSender = Window | MessagePort | ServiceWorker | null; import * as operations from './operations';
type WebMessageListener = (msg: any, sender: WebMessageSender | null) => void;
const onWebMessage = (listener: WebMessageListener) => {
window.addEventListener('message', (event: MessageEvent) => {
let sender = event.source;
let message = null;
try {
message = JSON.parse(event.data);
} catch (e) {
// ignore unexpected message
return;
}
listener(message, sender);
});
};
const onBackgroundMessage = ( export const BACKGROUND_OPERATION = 'background.operation';
listener: (msg: any, sender: browser.runtime.MessageSender,
) => void) => {
browser.runtime.onMessage.addListener(listener);
};
const onMessage = ( export const CONSOLE_UNFOCUS = 'console.unfocus';
listener: (msg: any, sender: WebMessageSender | browser.runtime.MessageSender, export const CONSOLE_ENTER_COMMAND = 'console.enter.command';
) => void) => { export const CONSOLE_ENTER_FIND = 'console.enter.find';
onWebMessage(listener); export const CONSOLE_QUERY_COMPLETIONS = 'console.query.completions';
onBackgroundMessage(listener); export const CONSOLE_SHOW_COMMAND = 'console.show.command';
}; export const CONSOLE_SHOW_ERROR = 'console.show.error';
export const CONSOLE_SHOW_INFO = 'console.show.info';
export const CONSOLE_SHOW_FIND = 'console.show.find';
export const CONSOLE_HIDE = 'console.hide';
export const FOLLOW_START = 'follow.start';
export const FOLLOW_REQUEST_COUNT_TARGETS = 'follow.request.count.targets';
export const FOLLOW_RESPONSE_COUNT_TARGETS = 'follow.response.count.targets';
export const FOLLOW_CREATE_HINTS = 'follow.create.hints';
export const FOLLOW_SHOW_HINTS = 'follow.update.hints';
export const FOLLOW_REMOVE_HINTS = 'follow.remove.hints';
export const FOLLOW_ACTIVATE = 'follow.activate';
export const FOLLOW_KEY_PRESS = 'follow.key.press';
export const MARK_SET_GLOBAL = 'mark.set.global';
export const MARK_JUMP_GLOBAL = 'mark.jump.global';
export const TAB_SCROLL_TO = 'tab.scroll.to';
export const FIND_NEXT = 'find.next';
export const FIND_PREV = 'find.prev';
export const FIND_GET_KEYWORD = 'find.get.keyword';
export const FIND_SET_KEYWORD = 'find.set.keyword';
export const ADDON_ENABLED_QUERY = 'addon.enabled.query';
export const ADDON_ENABLED_RESPONSE = 'addon.enabled.response';
export const ADDON_TOGGLE_ENABLED = 'addon.toggle.enabled';
export const OPEN_URL = 'open.url';
export const SETTINGS_CHANGED = 'settings.changed';
export const SETTINGS_QUERY = 'settings.query';
export const CONSOLE_FRAME_MESSAGE = 'console.frame.message';
interface BackgroundOperationMessage {
type: typeof BACKGROUND_OPERATION;
operation: operations.Operation;
}
interface ConsoleUnfocusMessage {
type: typeof CONSOLE_UNFOCUS;
}
interface ConsoleEnterCommandMessage {
type: typeof CONSOLE_ENTER_COMMAND;
text: string;
}
interface ConsoleEnterFindMessage {
type: typeof CONSOLE_ENTER_FIND;
text: string;
}
interface ConsoleQueryCompletionsMessage {
type: typeof CONSOLE_QUERY_COMPLETIONS;
text: string;
}
interface ConsoleShowCommandMessage {
type: typeof CONSOLE_SHOW_COMMAND;
command: string;
}
interface ConsoleShowErrorMessage {
type: typeof CONSOLE_SHOW_ERROR;
text: string;
}
interface ConsoleShowInfoMessage {
type: typeof CONSOLE_SHOW_INFO;
text: string;
}
interface ConsoleShowFindMessage {
type: typeof CONSOLE_SHOW_FIND;
}
interface ConsoleHideMessage {
type: typeof CONSOLE_HIDE;
}
interface FollowStartMessage {
type: typeof FOLLOW_START;
newTab: boolean;
background: boolean;
}
interface FollowRequestCountTargetsMessage {
type: typeof FOLLOW_REQUEST_COUNT_TARGETS;
viewSize: { width: number, height: number };
framePosition: { x: number, y: number };
}
interface FollowResponseCountTargetsMessage {
type: typeof FOLLOW_RESPONSE_COUNT_TARGETS;
count: number;
}
interface FollowCreateHintsMessage {
type: typeof FOLLOW_CREATE_HINTS;
keysArray: string[];
newTab: boolean;
background: boolean;
}
export default { interface FollowShowHintsMessage {
BACKGROUND_OPERATION: 'background.operation', type: typeof FOLLOW_SHOW_HINTS;
keys: string;
CONSOLE_UNFOCUS: 'console.unfocus', }
CONSOLE_ENTER_COMMAND: 'console.enter.command',
CONSOLE_ENTER_FIND: 'console.enter.find', interface FollowRemoveHintsMessage {
CONSOLE_QUERY_COMPLETIONS: 'console.query.completions', type: typeof FOLLOW_REMOVE_HINTS;
CONSOLE_SHOW_COMMAND: 'console.show.command', }
CONSOLE_SHOW_ERROR: 'console.show.error',
CONSOLE_SHOW_INFO: 'console.show.info', interface FollowActivateMessage {
CONSOLE_SHOW_FIND: 'console.show.find', type: typeof FOLLOW_ACTIVATE;
CONSOLE_HIDE: 'console.hide', keys: string;
}
FOLLOW_START: 'follow.start',
FOLLOW_REQUEST_COUNT_TARGETS: 'follow.request.count.targets', interface FollowKeyPressMessage {
FOLLOW_RESPONSE_COUNT_TARGETS: 'follow.response.count.targets', type: typeof FOLLOW_KEY_PRESS;
FOLLOW_CREATE_HINTS: 'follow.create.hints', key: string;
FOLLOW_SHOW_HINTS: 'follow.update.hints', ctrlKey: boolean;
FOLLOW_REMOVE_HINTS: 'follow.remove.hints', }
FOLLOW_ACTIVATE: 'follow.activate',
FOLLOW_KEY_PRESS: 'follow.key.press', interface MarkSetGlobalMessage {
type: typeof MARK_SET_GLOBAL;
MARK_SET_GLOBAL: 'mark.set.global', key: string;
MARK_JUMP_GLOBAL: 'mark.jump.global', x: number;
y: number;
TAB_SCROLL_TO: 'tab.scroll.to', }
FIND_NEXT: 'find.next', interface MarkJumpGlobalMessage {
FIND_PREV: 'find.prev', type: typeof MARK_JUMP_GLOBAL;
FIND_GET_KEYWORD: 'find.get.keyword', key: string;
FIND_SET_KEYWORD: 'find.set.keyword', }
ADDON_ENABLED_QUERY: 'addon.enabled.query', interface TabScrollToMessage {
ADDON_ENABLED_RESPONSE: 'addon.enabled.response', type: typeof TAB_SCROLL_TO;
ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled', x: number;
y: number;
OPEN_URL: 'open.url', }
SETTINGS_CHANGED: 'settings.changed', interface FindNextMessage {
SETTINGS_QUERY: 'settings.query', type: typeof FIND_NEXT;
}
WINDOW_TOP_MESSAGE: 'window.top.message',
CONSOLE_FRAME_MESSAGE: 'console.frame.message', interface FindPrevMessage {
type: typeof FIND_PREV;
onWebMessage, }
onBackgroundMessage,
onMessage, interface FindGetKeywordMessage {
type: typeof FIND_GET_KEYWORD;
}
interface FindSetKeywordMessage {
type: typeof FIND_SET_KEYWORD;
keyword: string;
found: boolean;
}
interface AddonEnabledQueryMessage {
type: typeof ADDON_ENABLED_QUERY;
}
interface AddonEnabledResponseMessage {
type: typeof ADDON_ENABLED_RESPONSE;
enabled: boolean;
}
interface AddonToggleEnabledMessage {
type: typeof ADDON_TOGGLE_ENABLED;
}
interface OpenUrlMessage {
type: typeof OPEN_URL;
url: string;
newTab: boolean;
background: boolean;
}
interface SettingsChangedMessage {
type: typeof SETTINGS_CHANGED;
}
interface SettingsQueryMessage {
type: typeof SETTINGS_QUERY;
}
interface ConsoleFrameMessageMessage {
type: typeof CONSOLE_FRAME_MESSAGE;
message: any;
}
export type Message =
BackgroundOperationMessage |
ConsoleUnfocusMessage |
ConsoleEnterCommandMessage |
ConsoleEnterFindMessage |
ConsoleQueryCompletionsMessage |
ConsoleShowCommandMessage |
ConsoleShowErrorMessage |
ConsoleShowInfoMessage |
ConsoleShowFindMessage |
ConsoleHideMessage |
FollowStartMessage |
FollowRequestCountTargetsMessage |
FollowResponseCountTargetsMessage |
FollowCreateHintsMessage |
FollowShowHintsMessage |
FollowRemoveHintsMessage |
FollowActivateMessage |
FollowKeyPressMessage |
MarkSetGlobalMessage |
MarkJumpGlobalMessage |
TabScrollToMessage |
FindNextMessage |
FindPrevMessage |
FindGetKeywordMessage |
FindSetKeywordMessage |
AddonEnabledQueryMessage |
AddonEnabledResponseMessage |
AddonToggleEnabledMessage |
OpenUrlMessage |
SettingsChangedMessage |
SettingsQueryMessage |
ConsoleFrameMessageMessage;
// eslint-disable-next-line complexity
export const valueOf = (o: any): Message => {
switch (o.type) {
case CONSOLE_UNFOCUS:
case CONSOLE_ENTER_COMMAND:
case CONSOLE_ENTER_FIND:
case CONSOLE_QUERY_COMPLETIONS:
case CONSOLE_SHOW_COMMAND:
case CONSOLE_SHOW_ERROR:
case CONSOLE_SHOW_INFO:
case CONSOLE_SHOW_FIND:
case CONSOLE_HIDE:
case FOLLOW_START:
case FOLLOW_REQUEST_COUNT_TARGETS:
case FOLLOW_RESPONSE_COUNT_TARGETS:
case FOLLOW_CREATE_HINTS:
case FOLLOW_SHOW_HINTS:
case FOLLOW_REMOVE_HINTS:
case FOLLOW_ACTIVATE:
case FOLLOW_KEY_PRESS:
case MARK_SET_GLOBAL:
case MARK_JUMP_GLOBAL:
case TAB_SCROLL_TO:
case FIND_NEXT:
case FIND_PREV:
case FIND_GET_KEYWORD:
case FIND_SET_KEYWORD:
case ADDON_ENABLED_QUERY:
case ADDON_ENABLED_RESPONSE:
case ADDON_TOGGLE_ENABLED:
case OPEN_URL:
case SETTINGS_CHANGED:
case SETTINGS_QUERY:
case CONSOLE_FRAME_MESSAGE:
return o;
}
throw new Error('unknown operation type: ' + o.type);
}; };

@ -1,80 +1,447 @@
const operations: { [key: string]: string } = { // Hide console; or cancel some user actions
// Hide console, or cancel some user actions export const CANCEL = 'cancel';
CANCEL: 'cancel',
// Addons // Addons
ADDON_ENABLE: 'addon.enable', export const ADDON_ENABLE = 'addon.enable';
ADDON_DISABLE: 'addon.disable', export const ADDON_DISABLE = 'addon.disable';
ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled', export const ADDON_TOGGLE_ENABLED = 'addon.toggle.enabled';
// Command // Command
COMMAND_SHOW: 'command.show', export const COMMAND_SHOW = 'command.show';
COMMAND_SHOW_OPEN: 'command.show.open', export const COMMAND_SHOW_OPEN = 'command.show.open';
COMMAND_SHOW_TABOPEN: 'command.show.tabopen', export const COMMAND_SHOW_TABOPEN = 'command.show.tabopen';
COMMAND_SHOW_WINOPEN: 'command.show.winopen', export const COMMAND_SHOW_WINOPEN = 'command.show.winopen';
COMMAND_SHOW_BUFFER: 'command.show.buffer', export const COMMAND_SHOW_BUFFER = 'command.show.buffer';
COMMAND_SHOW_ADDBOOKMARK: 'command.show.addbookmark', export const COMMAND_SHOW_ADDBOOKMARK = 'command.show.addbookmark';
// Scrolls // Scrolls
SCROLL_VERTICALLY: 'scroll.vertically', export const SCROLL_VERTICALLY = 'scroll.vertically';
SCROLL_HORIZONALLY: 'scroll.horizonally', export const SCROLL_HORIZONALLY = 'scroll.horizonally';
SCROLL_PAGES: 'scroll.pages', export const SCROLL_PAGES = 'scroll.pages';
SCROLL_TOP: 'scroll.top', export const SCROLL_TOP = 'scroll.top';
SCROLL_BOTTOM: 'scroll.bottom', export const SCROLL_BOTTOM = 'scroll.bottom';
SCROLL_HOME: 'scroll.home', export const SCROLL_HOME = 'scroll.home';
SCROLL_END: 'scroll.end', export const SCROLL_END = 'scroll.end';
// Follows // Follows
FOLLOW_START: 'follow.start', export const FOLLOW_START = 'follow.start';
// Navigations // Navigations
NAVIGATE_HISTORY_PREV: 'navigate.history.prev', export const NAVIGATE_HISTORY_PREV = 'navigate.history.prev';
NAVIGATE_HISTORY_NEXT: 'navigate.history.next', export const NAVIGATE_HISTORY_NEXT = 'navigate.history.next';
NAVIGATE_LINK_PREV: 'navigate.link.prev', export const NAVIGATE_LINK_PREV = 'navigate.link.prev';
NAVIGATE_LINK_NEXT: 'navigate.link.next', export const NAVIGATE_LINK_NEXT = 'navigate.link.next';
NAVIGATE_PARENT: 'navigate.parent', export const NAVIGATE_PARENT = 'navigate.parent';
NAVIGATE_ROOT: 'navigate.root', export const NAVIGATE_ROOT = 'navigate.root';
// Focus // Focus
FOCUS_INPUT: 'focus.input', export const FOCUS_INPUT = 'focus.input';
// Page // Page
PAGE_SOURCE: 'page.source', export const PAGE_SOURCE = 'page.source';
PAGE_HOME: 'page.home', export const PAGE_HOME = 'page.home';
// Tabs // Tabs
TAB_CLOSE: 'tabs.close', export const TAB_CLOSE = 'tabs.close';
TAB_CLOSE_FORCE: 'tabs.close.force', export const TAB_CLOSE_FORCE = 'tabs.close.force';
TAB_CLOSE_RIGHT: 'tabs.close.right', export const TAB_CLOSE_RIGHT = 'tabs.close.right';
TAB_REOPEN: 'tabs.reopen', export const TAB_REOPEN = 'tabs.reopen';
TAB_PREV: 'tabs.prev', export const TAB_PREV = 'tabs.prev';
TAB_NEXT: 'tabs.next', export const TAB_NEXT = 'tabs.next';
TAB_FIRST: 'tabs.first', export const TAB_FIRST = 'tabs.first';
TAB_LAST: 'tabs.last', export const TAB_LAST = 'tabs.last';
TAB_PREV_SEL: 'tabs.prevsel', export const TAB_PREV_SEL = 'tabs.prevsel';
TAB_RELOAD: 'tabs.reload', export const TAB_RELOAD = 'tabs.reload';
TAB_PIN: 'tabs.pin', export const TAB_PIN = 'tabs.pin';
TAB_UNPIN: 'tabs.unpin', export const TAB_UNPIN = 'tabs.unpin';
TAB_TOGGLE_PINNED: 'tabs.pin.toggle', export const TAB_TOGGLE_PINNED = 'tabs.pin.toggle';
TAB_DUPLICATE: 'tabs.duplicate', export const TAB_DUPLICATE = 'tabs.duplicate';
// Zooms // Zooms
ZOOM_IN: 'zoom.in', export const ZOOM_IN = 'zoom.in';
ZOOM_OUT: 'zoom.out', export const ZOOM_OUT = 'zoom.out';
ZOOM_NEUTRAL: 'zoom.neutral', export const ZOOM_NEUTRAL = 'zoom.neutral';
// Url yank/paste // Url yank/paste
URLS_YANK: 'urls.yank', export const URLS_YANK = 'urls.yank';
URLS_PASTE: 'urls.paste', export const URLS_PASTE = 'urls.paste';
// Find // Find
FIND_START: 'find.start', export const FIND_START = 'find.start';
FIND_NEXT: 'find.next', export const FIND_NEXT = 'find.next';
FIND_PREV: 'find.prev', export const FIND_PREV = 'find.prev';
// Mark // Mark
MARK_SET_PREFIX: 'mark.set.prefix', export const MARK_SET_PREFIX = 'mark.set.prefix';
MARK_JUMP_PREFIX: 'mark.jump.prefix', export const MARK_JUMP_PREFIX = 'mark.jump.prefix';
export interface CancelOperation {
type: typeof CANCEL;
}
export interface AddonEnableOperation {
type: typeof ADDON_ENABLE;
}
export interface AddonDisableOperation {
type: typeof ADDON_DISABLE;
}
export interface AddonToggleEnabledOperation {
type: typeof ADDON_TOGGLE_ENABLED;
}
export interface CommandShowOperation {
type: typeof COMMAND_SHOW;
}
export interface CommandShowOpenOperation {
type: typeof COMMAND_SHOW_OPEN;
alter: boolean;
}
export interface CommandShowTabopenOperation {
type: typeof COMMAND_SHOW_TABOPEN;
alter: boolean;
}
export interface CommandShowWinopenOperation {
type: typeof COMMAND_SHOW_WINOPEN;
alter: boolean;
}
export interface CommandShowBufferOperation {
type: typeof COMMAND_SHOW_BUFFER;
}
export interface CommandShowAddbookmarkOperation {
type: typeof COMMAND_SHOW_ADDBOOKMARK;
alter: boolean;
}
export interface ScrollVerticallyOperation {
type: typeof SCROLL_VERTICALLY;
count: number;
}
export interface ScrollHorizonallyOperation {
type: typeof SCROLL_HORIZONALLY;
count: number;
}
export interface ScrollPagesOperation {
type: typeof SCROLL_PAGES;
count: number;
}
export interface ScrollTopOperation {
type: typeof SCROLL_TOP;
}
export interface ScrollBottomOperation {
type: typeof SCROLL_BOTTOM;
}
export interface ScrollHomeOperation {
type: typeof SCROLL_HOME;
}
export interface ScrollEndOperation {
type: typeof SCROLL_END;
}
export interface FollowStartOperation {
type: typeof FOLLOW_START;
newTab: boolean;
background: boolean;
}
export interface NavigateHistoryPrevOperation {
type: typeof NAVIGATE_HISTORY_PREV;
}
export interface NavigateHistoryNextOperation {
type: typeof NAVIGATE_HISTORY_NEXT;
}
export interface NavigateLinkPrevOperation {
type: typeof NAVIGATE_LINK_PREV;
}
export interface NavigateLinkNextOperation {
type: typeof NAVIGATE_LINK_NEXT;
}
export interface NavigateParentOperation {
type: typeof NAVIGATE_PARENT;
}
export interface NavigateRootOperation {
type: typeof NAVIGATE_ROOT;
}
export interface FocusInputOperation {
type: typeof FOCUS_INPUT;
}
export interface PageSourceOperation {
type: typeof PAGE_SOURCE;
}
export interface PageHomeOperation {
type: typeof PAGE_HOME;
newTab: boolean;
}
export interface TabCloseOperation {
type: typeof TAB_CLOSE;
}
export interface TabCloseForceOperation {
type: typeof TAB_CLOSE_FORCE;
}
export interface TabCloseRightOperation {
type: typeof TAB_CLOSE_RIGHT;
}
export interface TabReopenOperation {
type: typeof TAB_REOPEN;
}
export interface TabPrevOperation {
type: typeof TAB_PREV;
}
export interface TabNextOperation {
type: typeof TAB_NEXT;
}
export interface TabFirstOperation {
type: typeof TAB_FIRST;
}
export interface TabLastOperation {
type: typeof TAB_LAST;
}
export interface TabPrevSelOperation {
type: typeof TAB_PREV_SEL;
}
export interface TabReloadOperation {
type: typeof TAB_RELOAD;
cache: boolean;
}
export interface TabPinOperation {
type: typeof TAB_PIN;
}
export interface TabUnpinOperation {
type: typeof TAB_UNPIN;
}
export interface TabTogglePinnedOperation {
type: typeof TAB_TOGGLE_PINNED;
}
export interface TabDuplicateOperation {
type: typeof TAB_DUPLICATE;
}
export interface ZoomInOperation {
type: typeof ZOOM_IN;
}
export interface ZoomOutOperation {
type: typeof ZOOM_OUT;
}
export interface ZoomNeutralOperation {
type: typeof ZOOM_NEUTRAL;
}
export interface UrlsYankOperation {
type: typeof URLS_YANK;
}
export interface UrlsPasteOperation {
type: typeof URLS_PASTE;
newTab: boolean;
}
export interface FindStartOperation {
type: typeof FIND_START;
}
export interface FindNextOperation {
type: typeof FIND_NEXT;
}
export interface FindPrevOperation {
type: typeof FIND_PREV;
}
export interface MarkSetPrefixOperation {
type: typeof MARK_SET_PREFIX;
}
export interface MarkJumpPrefixOperation {
type: typeof MARK_JUMP_PREFIX;
}
export type Operation =
CancelOperation |
AddonEnableOperation |
AddonDisableOperation |
AddonToggleEnabledOperation |
CommandShowOperation |
CommandShowOpenOperation |
CommandShowTabopenOperation |
CommandShowWinopenOperation |
CommandShowBufferOperation |
CommandShowAddbookmarkOperation |
ScrollVerticallyOperation |
ScrollHorizonallyOperation |
ScrollPagesOperation |
ScrollTopOperation |
ScrollBottomOperation |
ScrollHomeOperation |
ScrollEndOperation |
FollowStartOperation |
NavigateHistoryPrevOperation |
NavigateHistoryNextOperation |
NavigateLinkPrevOperation |
NavigateLinkNextOperation |
NavigateParentOperation |
NavigateRootOperation |
FocusInputOperation |
PageSourceOperation |
PageHomeOperation |
TabCloseOperation |
TabCloseForceOperation |
TabCloseRightOperation |
TabReopenOperation |
TabPrevOperation |
TabNextOperation |
TabFirstOperation |
TabLastOperation |
TabPrevSelOperation |
TabReloadOperation |
TabPinOperation |
TabUnpinOperation |
TabTogglePinnedOperation |
TabDuplicateOperation |
ZoomInOperation |
ZoomOutOperation |
ZoomNeutralOperation |
UrlsYankOperation |
UrlsPasteOperation |
FindStartOperation |
FindNextOperation |
FindPrevOperation |
MarkSetPrefixOperation |
MarkJumpPrefixOperation;
const assertOptionalBoolean = (obj: any, name: string) => {
if (Object.prototype.hasOwnProperty.call(obj, name) &&
typeof obj[name] !== 'boolean') {
throw new TypeError(`Not a boolean parameter '${name}'`);
}
}; };
export default operations; const assertRequiredNumber = (obj: any, name: string) => {
if (!Object.prototype.hasOwnProperty.call(obj, name) ||
typeof obj[name] !== 'number') {
throw new TypeError(`Missing number 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 COMMAND_SHOW_OPEN:
case COMMAND_SHOW_TABOPEN:
case COMMAND_SHOW_WINOPEN:
case COMMAND_SHOW_ADDBOOKMARK:
assertOptionalBoolean(o, 'alter');
return { type: o.type, alter: Boolean(o.alter) };
case SCROLL_VERTICALLY:
case SCROLL_HORIZONALLY:
case SCROLL_PAGES:
assertRequiredNumber(o, 'count');
return { type: o.type, count: Number(o.count) };
case FOLLOW_START:
assertOptionalBoolean(o, 'newTab');
assertOptionalBoolean(o, 'background');
return {
type: FOLLOW_START,
newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
background: Boolean(typeof o.background === undefined ? true : o.background), // eslint-disable-line max-len
};
case PAGE_HOME:
assertOptionalBoolean(o, 'newTab');
return {
type: PAGE_HOME,
newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
};
case TAB_RELOAD:
assertOptionalBoolean(o, 'cache');
return {
type: TAB_RELOAD,
cache: Boolean(typeof o.cache === undefined ? false : o.cache),
};
case URLS_PASTE:
assertOptionalBoolean(o, 'newTab');
return {
type: URLS_PASTE,
newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
};
case CANCEL:
case ADDON_ENABLE:
case ADDON_DISABLE:
case ADDON_TOGGLE_ENABLED:
case COMMAND_SHOW:
case COMMAND_SHOW_BUFFER:
case SCROLL_TOP:
case SCROLL_BOTTOM:
case SCROLL_HOME:
case SCROLL_END:
case NAVIGATE_HISTORY_PREV:
case NAVIGATE_HISTORY_NEXT:
case NAVIGATE_LINK_PREV:
case NAVIGATE_LINK_NEXT:
case NAVIGATE_PARENT:
case NAVIGATE_ROOT:
case FOCUS_INPUT:
case PAGE_SOURCE:
case TAB_CLOSE:
case TAB_CLOSE_FORCE:
case TAB_CLOSE_RIGHT:
case TAB_REOPEN:
case TAB_PREV:
case TAB_NEXT:
case TAB_FIRST:
case TAB_LAST:
case TAB_PREV_SEL:
case TAB_PIN:
case TAB_UNPIN:
case TAB_TOGGLE_PINNED:
case TAB_DUPLICATE:
case ZOOM_IN:
case ZOOM_OUT:
case ZOOM_NEUTRAL:
case URLS_YANK:
case FIND_START:
case FIND_NEXT:
case FIND_PREV:
case MARK_SET_PREFIX:
case MARK_JUMP_PREFIX:
return { type: o.type };
}
throw new Error('unknown operation type: ' + o.type);
};

@ -1,4 +1,4 @@
import operations from '../operations'; import * as operations from '../operations';
import * as properties from './properties'; import * as properties from './properties';
const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties']; const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties'];

@ -1,4 +1,4 @@
interface Key { export interface Key {
key: string; key: string;
shiftKey: boolean | undefined; shiftKey: boolean | undefined;
ctrlKey: boolean | undefined; ctrlKey: boolean | undefined;

@ -1,4 +1,4 @@
import actions from 'content/actions'; import * as actions from 'content/actions';
import * as followControllerActions from 'content/actions/follow-controller'; import * as followControllerActions from 'content/actions/follow-controller';
describe('follow-controller actions', () => { describe('follow-controller actions', () => {

@ -1,4 +1,4 @@
import actions from 'content/actions'; import * as actions from 'content/actions';
import * as inputActions from 'content/actions/input'; import * as inputActions from 'content/actions/input';
describe("input actions", () => { describe("input actions", () => {

@ -1,4 +1,4 @@
import actions from 'content/actions'; import * as actions from 'content/actions';
import * as markActions from 'content/actions/mark'; import * as markActions from 'content/actions/mark';
describe('mark actions', () => { describe('mark actions', () => {

@ -1,4 +1,4 @@
import actions from 'content/actions'; import * as actions from 'content/actions';
import * as settingActions from 'content/actions/setting'; import * as settingActions from 'content/actions/setting';
describe("setting actions", () => { describe("setting actions", () => {

@ -21,12 +21,14 @@ describe('InputComponent', () => {
++b; ++b;
} }
}); });
component.onKeyDown({ key: 'a' });
component.onKeyDown({ key: 'b' }); let elem = document.body;
component.onKeyPress({ key: 'a' }); component.onKeyDown({ key: 'a', target: elem });
component.onKeyUp({ key: 'a' }); component.onKeyDown({ key: 'b', target: elem });
component.onKeyPress({ key: 'b' }); component.onKeyPress({ key: 'a', target: elem });
component.onKeyUp({ key: 'b' }); component.onKeyUp({ key: 'a', target: elem });
component.onKeyPress({ key: 'b', target: elem });
component.onKeyUp({ key: 'b', target: elem });
expect(a).is.equals(1); expect(a).is.equals(1);
expect(b).is.equals(1); expect(b).is.equals(1);

@ -1,4 +1,4 @@
import actions from 'content/actions'; import * as actions from 'content/actions';
import addonReducer from 'content/reducers/addon'; import addonReducer from 'content/reducers/addon';
describe("addon reducer", () => { describe("addon reducer", () => {

@ -1,4 +1,4 @@
import actions from 'content/actions'; import * as actions from 'content/actions';
import findReducer from 'content/reducers/find'; import findReducer from 'content/reducers/find';
describe("find reducer", () => { describe("find reducer", () => {

@ -1,4 +1,4 @@
import actions from 'content/actions'; import * as actions from 'content/actions';
import followControllerReducer from 'content/reducers/follow-controller'; import followControllerReducer from 'content/reducers/follow-controller';
describe('follow-controller reducer', () => { describe('follow-controller reducer', () => {

@ -1,4 +1,4 @@
import actions from 'content/actions'; import * as actions from 'content/actions';
import inputReducer from 'content/reducers/input'; import inputReducer from 'content/reducers/input';
describe("input reducer", () => { describe("input reducer", () => {

@ -1,4 +1,4 @@
import actions from 'content/actions'; import * as actions from 'content/actions';
import reducer from 'content/reducers/mark'; import reducer from 'content/reducers/mark';
describe("mark reducer", () => { describe("mark reducer", () => {

@ -1,4 +1,4 @@
import actions from 'content/actions'; import * as actions from 'content/actions';
import settingReducer from 'content/reducers/setting'; import settingReducer from 'content/reducers/setting';
describe("content setting reducer", () => { describe("content setting reducer", () => {

@ -0,0 +1,41 @@
import * as operations from 'shared/operations';
describe('operations', () => {
describe('#valueOf', () => {
it('returns an Operation', () => {
let op: operations.Operation = operations.valueOf({
type: operations.SCROLL_VERTICALLY,
count: 10,
});
expect(op.type).to.equal(operations.SCROLL_VERTICALLY);
expect(op.count).to.equal(10);
});
it('throws an Error on missing required parameter', () => {
expect(() => operations.valueOf({
type: operations.SCROLL_VERTICALLY,
})).to.throw(TypeError);
});
it('fills default valus of optional parameter', () => {
let op: operations.Operation = operations.valueOf({
type: operations.COMMAND_SHOW_OPEN,
});
expect(op.type).to.equal(operations.COMMAND_SHOW_OPEN)
expect(op.alter).to.be.false;
});
it('throws an Error on mismatch of parameter', () => {
expect(() => operations.valueOf({
type: operations.SCROLL_VERTICALLY,
count: '10',
})).to.throw(TypeError);
expect(() => valueOf({
type: operations.COMMAND_SHOW_OPEN,
alter: 'true',
})).to.throw(TypeError);
});
});
})

@ -2,11 +2,11 @@
"compilerOptions": { "compilerOptions": {
"target": "es2017", "target": "es2017",
"module": "commonjs", "module": "commonjs",
"lib": ["es6", "dom", "es2017"],
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"noEmit": true,
"jsx": "react", "jsx": "react",
"declaration": true,
"declarationMap": true,
"sourceMap": true, "sourceMap": true,
"outDir": "./build", "outDir": "./build",
"removeComments": true, "removeComments": true,
@ -29,5 +29,8 @@
"esModuleInterop": true, "esModuleInterop": true,
"typeRoots": ["node_modules/@types", "node_modules/web-ext-types"] "typeRoots": ["node_modules/@types", "node_modules/web-ext-types"]
} },
"include": [
"src"
]
} }