aboutsummaryrefslogtreecommitdiff
path: root/src/content/components/common/follow.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/content/components/common/follow.ts')
-rw-r--r--src/content/components/common/follow.ts163
1 files changed, 37 insertions, 126 deletions
diff --git a/src/content/components/common/follow.ts b/src/content/components/common/follow.ts
index 9a62613..e0003e3 100644
--- a/src/content/components/common/follow.ts
+++ b/src/content/components/common/follow.ts
@@ -1,17 +1,18 @@
import MessageListener from '../../MessageListener';
-import Hint, { LinkHint, InputHint } from '../../presenters/Hint';
-import * as dom from '../../../shared/utils/dom';
+import { LinkHint, InputHint } from '../../presenters/Hint';
import * as messages from '../../../shared/messages';
-import * as keyUtils from '../../../shared/utils/keys';
+import { Key } from '../../../shared/utils/keys';
import TabsClient, { TabsClientImpl } from '../../client/TabsClient';
+import FollowMasterClient, { FollowMasterClientImpl }
+ from '../../client/FollowMasterClient';
+import FollowPresenter, { FollowPresenterImpl }
+ from '../../presenters/FollowPresenter';
let tabsClient: TabsClient = new TabsClientImpl();
-
-const TARGET_SELECTOR = [
- 'a', 'button', 'input', 'textarea', 'area',
- '[contenteditable=true]', '[contenteditable=""]', '[tabindex]',
- '[role="button"]', 'summary'
-].join(',');
+let followMasterClient: FollowMasterClient =
+ new FollowMasterClientImpl(window.top);
+let followPresenter: FollowPresenter =
+ new FollowPresenterImpl();
interface Size {
width: number;
@@ -23,118 +24,46 @@ interface Point {
y: number;
}
-const inViewport = (
- win: Window,
- element: Element,
- viewSize: Size,
- framePosition: Point,
-): boolean => {
- let {
- top, left, bottom, right
- } = dom.viewportRect(element);
- let doc = win.document;
- let frameWidth = doc.documentElement.clientWidth;
- let frameHeight = doc.documentElement.clientHeight;
-
- if (right < 0 || bottom < 0 || top > frameHeight || left > frameWidth) {
- // out of frame
- return false;
- }
- if (right + framePosition.x < 0 || bottom + framePosition.y < 0 ||
- left + framePosition.x > viewSize.width ||
- top + framePosition.y > viewSize.height) {
- // out of viewport
- return false;
- }
- return true;
-};
-
-const isAriaHiddenOrAriaDisabled = (win: Window, element: Element): boolean => {
- if (!element || win.document.documentElement === element) {
- return false;
- }
- for (let attr of ['aria-hidden', 'aria-disabled']) {
- let value = element.getAttribute(attr);
- if (value !== null) {
- let hidden = value.toLowerCase();
- if (hidden === '' || hidden === 'true') {
- return true;
- }
- }
- }
- return isAriaHiddenOrAriaDisabled(win, element.parentElement as Element);
-};
-
export default class Follow {
- private win: Window;
+ private enabled: boolean;
- private hints: {[key: string]: Hint };
-
- private targets: HTMLElement[] = [];
-
- constructor(win: Window) {
- this.win = win;
- this.hints = {};
- this.targets = [];
+ constructor() {
+ this.enabled = false;
new MessageListener().onWebMessage(this.onMessage.bind(this));
}
- key(key: keyUtils.Key): boolean {
- if (Object.keys(this.hints).length === 0) {
+ key(key: Key): boolean {
+ if (!this.enabled) {
return false;
}
- this.win.parent.postMessage(JSON.stringify({
- type: messages.FOLLOW_KEY_PRESS,
- key: key.key,
- ctrlKey: key.ctrlKey,
- }), '*');
+ followMasterClient.sendKey(key);
return true;
}
- 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,
- count: this.targets.length,
- }), '*');
+ countHints(viewSize: Size, framePosition: Point) {
+ let count = followPresenter.getTargetCount(viewSize, framePosition);
+ followMasterClient.responseHintCount(count);
}
- createHints(keysArray: string[]) {
- if (keysArray.length !== this.targets.length) {
- throw new Error('illegal hint count');
- }
-
- this.hints = {};
- for (let i = 0; i < keysArray.length; ++i) {
- let keys = keysArray[i];
- let target = this.targets[i];
- if (target instanceof HTMLAnchorElement ||
- target instanceof HTMLAreaElement) {
- this.hints[keys] = new LinkHint(target, keys);
- } else {
- this.hints[keys] = new InputHint(target, keys);
- }
- }
+ createHints(viewSize: Size, framePosition: Point, tags: string[]) {
+ this.enabled = true;
+ followPresenter.createHints(viewSize, framePosition, tags);
}
- 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))
- .forEach(key => this.hints[key].hide());
+ showHints(prefix: string) {
+ followPresenter.filterHints(prefix);
}
removeHints() {
- Object.keys(this.hints).forEach((key) => {
- this.hints[key].remove();
- });
- this.hints = {};
- this.targets = [];
+ followPresenter.clearHints();
+ this.enabled = false;
}
- async activateHints(keys: string, newTab: boolean, background: boolean): Promise<void> {
- let hint = this.hints[keys];
+ async activateHints(
+ tag: string, newTab: boolean, background: boolean,
+ ): Promise<void> {
+ let hint = followPresenter.getHint(tag);
if (!hint) {
return;
}
@@ -156,38 +85,20 @@ export default class Follow {
}
}
- onMessage(message: messages.Message, sender: any) {
+ onMessage(message: messages.Message, _sender: Window) {
switch (message.type) {
case messages.FOLLOW_REQUEST_COUNT_TARGETS:
- return this.countHints(sender, message.viewSize, message.framePosition);
+ return this.countHints(message.viewSize, message.framePosition);
case messages.FOLLOW_CREATE_HINTS:
- return this.createHints(message.keysArray);
+ return this.createHints(
+ message.viewSize, message.framePosition, message.tags);
case messages.FOLLOW_SHOW_HINTS:
- return this.showHints(message.keys);
+ return this.showHints(message.prefix);
case messages.FOLLOW_ACTIVATE:
- return this.activateHints(message.keys, message.newTab, message.background);
+ return this.activateHints(
+ message.tag, message.newTab, message.background);
case messages.FOLLOW_REMOVE_HINTS:
return this.removeHints();
}
}
-
- static getTargetElements(
- win: Window,
- viewSize:
- Size, framePosition: Point,
- ): HTMLElement[] {
- let all = win.document.querySelectorAll(TARGET_SELECTOR);
- 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 as HTMLInputElement).type !== 'hidden' &&
- element.offsetHeight > 0 &&
- !isAriaHiddenOrAriaDisabled(win, element) &&
- inViewport(win, element, viewSize, framePosition);
- });
- return filtered;
- }
}