aboutsummaryrefslogtreecommitdiff
path: root/src/content/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/content/components')
-rw-r--r--src/content/components/common/follow.ts79
-rw-r--r--src/content/components/common/hint.ts33
-rw-r--r--src/content/components/common/index.ts43
-rw-r--r--src/content/components/common/input.ts46
-rw-r--r--src/content/components/common/keymapper.ts8
-rw-r--r--src/content/components/common/mark.ts2
-rw-r--r--src/content/components/top-content/find.ts25
-rw-r--r--src/content/components/top-content/follow-controller.ts65
-rw-r--r--src/content/components/top-content/index.ts28
9 files changed, 210 insertions, 119 deletions
diff --git a/src/content/components/common/follow.ts b/src/content/components/common/follow.ts
index 63ce603..67f2dd9 100644
--- a/src/content/components/common/follow.ts
+++ b/src/content/components/common/follow.ts
@@ -1,6 +1,8 @@
-import messages from 'shared/messages';
+import MessageListener from '../../MessageListener';
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 = [
'a', 'button', 'input', 'textarea', 'area',
@@ -8,8 +10,22 @@ const TARGET_SELECTOR = [
'[role="button"]', 'summary'
].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 {
top, left, bottom, right
} = dom.viewportRect(element);
@@ -30,34 +46,44 @@ const inViewport = (win, element, viewSize, framePosition) => {
return true;
};
-const isAriaHiddenOrAriaDisabled = (win, element) => {
+const isAriaHiddenOrAriaDisabled = (win: Window, element: Element): boolean => {
if (!element || win.document.documentElement === element) {
return false;
}
for (let attr of ['aria-hidden', 'aria-disabled']) {
- if (element.hasAttribute(attr)) {
- let hidden = element.getAttribute(attr).toLowerCase();
+ let value = element.getAttribute(attr);
+ if (value !== null) {
+ let hidden = value.toLowerCase();
if (hidden === '' || hidden === 'true') {
return true;
}
}
}
- return isAriaHiddenOrAriaDisabled(win, element.parentNode);
+ return isAriaHiddenOrAriaDisabled(win, element.parentElement as Element);
};
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.store = store;
this.newTab = false;
this.background = false;
this.hints = {};
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) {
return false;
}
@@ -69,7 +95,7 @@ export default class Follow {
return true;
}
- openLink(element) {
+ openLink(element: HTMLAreaElement|HTMLAnchorElement) {
// Browser prevent new tab by link with target='_blank'
if (!this.newTab && element.getAttribute('target') !== '_blank') {
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);
sender.postMessage(JSON.stringify({
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) {
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))
.forEach(key => this.hints[key].show());
Object.keys(this.hints).filter(key => !key.startsWith(keys))
@@ -128,18 +154,19 @@ export default class Follow {
this.targets = [];
}
- activateHints(keys) {
+ activateHints(keys: string) {
let hint = this.hints[keys];
if (!hint) {
return;
}
- let element = hint.target;
+ let element = hint.getTarget();
switch (element.tagName.toLowerCase()) {
case 'a':
+ return this.openLink(element as HTMLAnchorElement);
case 'area':
- return this.openLink(element);
+ return this.openLink(element as HTMLAreaElement);
case 'input':
- switch (element.type) {
+ switch ((element as HTMLInputElement).type) {
case 'file':
case 'checkbox':
case 'radio':
@@ -166,7 +193,7 @@ export default class Follow {
}
}
- onMessage(message, sender) {
+ onMessage(message: messages.Message, sender: any) {
switch (message.type) {
case messages.FOLLOW_REQUEST_COUNT_TARGETS:
return this.countHints(sender, message.viewSize, message.framePosition);
@@ -178,19 +205,23 @@ export default class Follow {
case messages.FOLLOW_ACTIVATE:
return this.activateHints(message.keys);
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 filtered = Array.prototype.filter.call(all, (element) => {
+ let filtered = Array.prototype.filter.call(all, (element: HTMLElement) => {
let style = win.getComputedStyle(element);
// AREA's 'display' in Browser style is 'none'
return (element.tagName === 'AREA' || style.display !== 'none') &&
style.visibility !== 'hidden' &&
- element.type !== 'hidden' &&
+ (element as HTMLInputElement).type !== 'hidden' &&
element.offsetHeight > 0 &&
!isAriaHiddenOrAriaDisabled(win, element) &&
inViewport(win, element, viewSize, framePosition);
diff --git a/src/content/components/common/hint.ts b/src/content/components/common/hint.ts
index 1472587..2fcbb0f 100644
--- a/src/content/components/common/hint.ts
+++ b/src/content/components/common/hint.ts
@@ -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);
if (element.tagName !== 'AREA') {
@@ -14,17 +19,21 @@ const hintPosition = (element) => {
};
export default class Hint {
- constructor(target, tag) {
- if (!(document.body instanceof HTMLElement)) {
- throw new TypeError('target is not an HTMLElement');
- }
+ private target: HTMLElement;
- this.target = target;
+ private element: HTMLElement;
+ constructor(target: HTMLElement, tag: string) {
let doc = target.ownerDocument;
+ if (doc === null) {
+ throw new TypeError('ownerDocument is null');
+ }
+
let { x, y } = hintPosition(target);
let { scrollX, scrollY } = window;
+ this.target = target;
+
this.element = doc.createElement('span');
this.element.className = 'vimvixen-hint';
this.element.textContent = tag;
@@ -35,15 +44,19 @@ export default class Hint {
doc.body.append(this.element);
}
- show() {
+ show(): void {
this.element.style.display = 'inline';
}
- hide() {
+ hide(): void {
this.element.style.display = 'none';
}
- remove() {
+ remove(): void {
this.element.remove();
}
+
+ getTarget(): HTMLElement {
+ return this.target;
+ }
}
diff --git a/src/content/components/common/index.ts b/src/content/components/common/index.ts
index bcab4fa..9b5164e 100644
--- a/src/content/components/common/index.ts
+++ b/src/content/components/common/index.ts
@@ -2,33 +2,37 @@ import InputComponent from './input';
import FollowComponent from './follow';
import MarkComponent from './mark';
import KeymapperComponent from './keymapper';
-import * as settingActions from 'content/actions/setting';
-import messages from 'shared/messages';
+import * as settingActions from '../../actions/setting';
+import * as messages from '../../../shared/messages';
+import MessageListener from '../../MessageListener';
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 {
- constructor(win, store) {
- const input = new InputComponent(win.document.body, store);
- const follow = new FollowComponent(win, store);
+ private win: Window;
+
+ 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 keymapper = new KeymapperComponent(store);
- input.onKey(key => follow.key(key));
- input.onKey(key => mark.key(key));
- input.onKey(key => keymapper.key(key));
+ input.onKey((key: keys.Key) => follow.key(key));
+ input.onKey((key: keys.Key) => mark.key(key));
+ input.onKey((key: keys.Key) => keymapper.key(key));
this.win = win;
this.store = store;
- this.prevEnabled = undefined;
- this.prevBlacklist = undefined;
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;
switch (message.type) {
case messages.SETTINGS_CHANGED:
@@ -40,12 +44,13 @@ export default class Common {
reloadSettings() {
try {
- this.store.dispatch(settingActions.load()).then(({ value: settings }) => {
- let enabled = !blacklists.includes(
- settings.blacklist, this.win.location.href
- );
- this.store.dispatch(addonActions.setEnabled(enabled));
- });
+ this.store.dispatch(settingActions.load())
+ .then(({ value: settings }: any) => {
+ let enabled = !blacklists.includes(
+ settings.blacklist, this.win.location.href
+ );
+ this.store.dispatch(addonActions.setEnabled(enabled));
+ });
} catch (e) {
// Sometime sendMessage fails when background script is not ready.
console.warn(e);
diff --git a/src/content/components/common/input.ts b/src/content/components/common/input.ts
index eefaf10..64eb5f3 100644
--- a/src/content/components/common/input.ts
+++ b/src/content/components/common/input.ts
@@ -1,12 +1,16 @@
-import * as dom from 'shared/utils/dom';
-import * as keys from 'shared/utils/keys';
+import * as dom from '../../../shared/utils/dom';
+import * as keys from '../../../shared/utils/keys';
-const cancelKey = (e) => {
+const cancelKey = (e: KeyboardEvent): boolean => {
return e.key === 'Escape' || e.key === '[' && e.ctrlKey;
};
export default class InputComponent {
- constructor(target) {
+ private pressed: {[key: string]: string} = {};
+
+ private onKeyListeners: ((key: keys.Key) => boolean)[] = [];
+
+ constructor(target: HTMLElement) {
this.pressed = {};
this.onKeyListeners = [];
@@ -15,11 +19,11 @@ export default class InputComponent {
target.addEventListener('keyup', this.onKeyUp.bind(this));
}
- onKey(cb) {
+ onKey(cb: (key: keys.Key) => boolean) {
this.onKeyListeners.push(cb);
}
- onKeyPress(e) {
+ onKeyPress(e: KeyboardEvent) {
if (this.pressed[e.key] && this.pressed[e.key] !== 'keypress') {
return;
}
@@ -27,7 +31,7 @@ export default class InputComponent {
this.capture(e);
}
- onKeyDown(e) {
+ onKeyDown(e: KeyboardEvent) {
if (this.pressed[e.key] && this.pressed[e.key] !== 'keydown') {
return;
}
@@ -35,14 +39,19 @@ export default class InputComponent {
this.capture(e);
}
- onKeyUp(e) {
+ onKeyUp(e: KeyboardEvent) {
delete this.pressed[e.key];
}
- capture(e) {
- if (this.fromInput(e)) {
- if (cancelKey(e) && e.target.blur) {
- e.target.blur();
+ // eslint-disable-next-line max-statements
+ capture(e: KeyboardEvent) {
+ let target = e.target;
+ if (!(target instanceof HTMLElement)) {
+ return;
+ }
+ if (this.fromInput(target)) {
+ if (cancelKey(e) && target.blur) {
+ target.blur();
}
return;
}
@@ -63,13 +72,10 @@ export default class InputComponent {
}
}
- fromInput(e) {
- if (!e.target) {
- return false;
- }
- return e.target instanceof HTMLInputElement ||
- e.target instanceof HTMLTextAreaElement ||
- e.target instanceof HTMLSelectElement ||
- dom.isContentEditable(e.target);
+ fromInput(e: Element) {
+ return e instanceof HTMLInputElement ||
+ e instanceof HTMLTextAreaElement ||
+ e instanceof HTMLSelectElement ||
+ dom.isContentEditable(e);
}
}
diff --git a/src/content/components/common/keymapper.ts b/src/content/components/common/keymapper.ts
index ec0d093..d9c9834 100644
--- a/src/content/components/common/keymapper.ts
+++ b/src/content/components/common/keymapper.ts
@@ -1,7 +1,7 @@
-import * as inputActions from 'content/actions/input';
-import * as operationActions from 'content/actions/operation';
-import operations from 'shared/operations';
-import * as keyUtils from 'shared/utils/keys';
+import * as inputActions from '../../actions/input';
+import * as operationActions from '../../actions/operation';
+import * as operations from '../../../shared/operations';
+import * as keyUtils from '../../../shared/utils/keys';
const mapStartsWith = (mapping, keys) => {
if (mapping.length < keys.length) {
diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts
index 0f838a9..500d03b 100644
--- a/src/content/components/common/mark.ts
+++ b/src/content/components/common/mark.ts
@@ -3,7 +3,7 @@ import * as scrolls from 'content/scrolls';
import * as consoleFrames from 'content/console-frames';
import * as properties from 'shared/settings/properties';
-const cancelKey = (key) => {
+const cancelKey = (key): boolean => {
return key.key === 'Esc' || key.key === '[' && key.ctrlKey;
};
diff --git a/src/content/components/top-content/find.ts b/src/content/components/top-content/find.ts
index 4d46d79..74b95bc 100644
--- a/src/content/components/top-content/find.ts
+++ b/src/content/components/top-content/find.ts
@@ -1,15 +1,17 @@
-import * as findActions from 'content/actions/find';
-import messages from 'shared/messages';
+import * as findActions from '../../actions/find';
+import * as messages from '../../../shared/messages';
+import MessageListener from '../../MessageListener';
export default class FindComponent {
- constructor(win, store) {
- this.win = win;
+ private store: any;
+
+ constructor(store: any) {
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) {
case messages.CONSOLE_ENTER_FIND:
return this.start(message.text);
@@ -20,22 +22,25 @@ export default class FindComponent {
}
}
- start(text) {
+ start(text: string) {
let state = this.store.getState().find;
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));
}
next() {
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() {
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));
}
}
diff --git a/src/content/components/top-content/follow-controller.ts b/src/content/components/top-content/follow-controller.ts
index 7f36604..be71f6e 100644
--- a/src/content/components/top-content/follow-controller.ts
+++ b/src/content/components/top-content/follow-controller.ts
@@ -1,30 +1,46 @@
-import * as followControllerActions from 'content/actions/follow-controller';
-import messages from 'shared/messages';
-import HintKeyProducer from 'content/hint-key-producer';
-import * as properties from 'shared/settings/properties';
+import * as followControllerActions from '../../actions/follow-controller';
+import * as messages from '../../../shared/messages';
+import MessageListener, { WebMessageSender } from '../../MessageListener';
+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 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, '*'));
};
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.store = store;
this.state = {};
this.keys = [];
this.producer = null;
- messages.onMessage(this.onMessage.bind(this));
+ new MessageListener().onWebMessage(this.onMessage.bind(this));
store.subscribe(() => {
this.update();
});
}
- onMessage(message, sender) {
+ onMessage(message: messages.Message, sender: WebMessageSender) {
switch (message.type) {
case messages.FOLLOW_START:
return this.store.dispatch(
@@ -36,7 +52,7 @@ export default class FollowController {
}
}
- update() {
+ update(): void {
let prevState = this.state;
this.state = this.store.getState().followController;
@@ -49,8 +65,10 @@ export default class FollowController {
}
}
- updateHints() {
- let shown = this.keys.filter(key => key.startsWith(this.state.keys));
+ updateHints(): void {
+ let shown = this.keys.filter((key) => {
+ return key.startsWith(this.state.keys as string);
+ });
if (shown.length === 1) {
this.activate();
this.store.dispatch(followControllerActions.disable());
@@ -58,18 +76,18 @@ export default class FollowController {
broadcastMessage(this.win, {
type: messages.FOLLOW_SHOW_HINTS,
- keys: this.state.keys,
+ keys: this.state.keys as string,
});
}
- activate() {
+ activate(): void {
broadcastMessage(this.win, {
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) {
this.store.dispatch(followControllerActions.disable());
return true;
@@ -107,25 +125,28 @@ export default class FollowController {
viewSize: { width: viewWidth, height: viewHeight },
framePosition: { x: 0, y: 0 },
}), '*');
- frameElements.forEach((element) => {
- let { left: frameX, top: frameY } = element.getBoundingClientRect();
+ frameElements.forEach((ele) => {
+ let { left: frameX, top: frameY } = ele.getBoundingClientRect();
let message = JSON.stringify({
type: messages.FOLLOW_REQUEST_COUNT_TARGETS,
viewSize: { width: viewWidth, height: viewHeight },
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 = [];
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);
- sender.postMessage(JSON.stringify({
+ (sender as Window).postMessage(JSON.stringify({
type: messages.FOLLOW_CREATE_HINTS,
keysArray: produced,
newTab: this.state.newTab,
diff --git a/src/content/components/top-content/index.ts b/src/content/components/top-content/index.ts
index 1aaef1b..ac95ea9 100644
--- a/src/content/components/top-content/index.ts
+++ b/src/content/components/top-content/index.ts
@@ -2,33 +2,43 @@ import CommonComponent from '../common';
import FollowController from './follow-controller';
import FindComponent from './find';
import * as consoleFrames from '../../console-frames';
-import messages from 'shared/messages';
-import * as scrolls from 'content/scrolls';
+import * as messages from '../../../shared/messages';
+import MessageListener from '../../MessageListener';
+import * as scrolls from '../../scrolls';
export default class TopContent {
+ private win: Window;
- constructor(win, store) {
+ private store: any;
+
+ constructor(win: Window, store: any) {
this.win = win;
this.store = store;
new CommonComponent(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
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) {
- let addonState = this.store.getState().addon;
-
+ onWebMessage(message: messages.Message) {
switch (message.type) {
case messages.CONSOLE_UNFOCUS:
this.win.focus();
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:
return Promise.resolve({
type: messages.ADDON_ENABLED_RESPONSE,