aboutsummaryrefslogtreecommitdiff
path: root/src/content/components
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2019-05-18 13:06:37 +0900
committerShin'ya Ueoka <ueokande@i-beam.org>2019-05-18 16:23:00 +0900
commita88324acd9fe626b59637541975abe1ee6041aa7 (patch)
tree0c23a15e928977e79f18476b3a5119d0f750f9e9 /src/content/components
parent17dc2bb5ec6a53c67e1b6df2b82410239eee95fc (diff)
Define client and presenter for follow
Diffstat (limited to 'src/content/components')
-rw-r--r--src/content/components/common/follow.ts163
-rw-r--r--src/content/components/common/index.ts2
-rw-r--r--src/content/components/top-content/follow-controller.ts95
3 files changed, 90 insertions, 170 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;
- }
}
diff --git a/src/content/components/common/index.ts b/src/content/components/common/index.ts
index 899953d..b2f48a3 100644
--- a/src/content/components/common/index.ts
+++ b/src/content/components/common/index.ts
@@ -16,7 +16,7 @@ let settingUseCase = new SettingUseCase();
export default class Common {
constructor(win: Window, store: any) {
const input = new InputComponent(win.document.body);
- const follow = new FollowComponent(win);
+ const follow = new FollowComponent();
const mark = new MarkComponent(store);
const keymapper = new KeymapperComponent(store);
diff --git a/src/content/components/top-content/follow-controller.ts b/src/content/components/top-content/follow-controller.ts
index 2a242c2..43c917e 100644
--- a/src/content/components/top-content/follow-controller.ts
+++ b/src/content/components/top-content/follow-controller.ts
@@ -1,18 +1,14 @@
import * as followControllerActions from '../../actions/follow-controller';
import * as messages from '../../../shared/messages';
-import MessageListener, { WebMessageSender } from '../../MessageListener';
+import MessageListener from '../../MessageListener';
import HintKeyProducer from '../../hint-key-producer';
import { SettingRepositoryImpl } from '../../repositories/SettingRepository';
+import FollowSlaveClient, { FollowSlaveClientImpl }
+ from '../../client/FollowSlaveClient';
let settingRepository = new SettingRepositoryImpl();
-const broadcastMessage = (win: Window, message: messages.Message): void => {
- let json = JSON.stringify(message);
- let frames = [win.self].concat(Array.from(win.frames as any));
- frames.forEach(frame => frame.postMessage(json, '*'));
-};
-
export default class FollowController {
private win: Window;
@@ -43,7 +39,7 @@ export default class FollowController {
});
}
- onMessage(message: messages.Message, sender: WebMessageSender) {
+ onMessage(message: messages.Message, sender: Window) {
switch (message.type) {
case messages.FOLLOW_START:
return this.store.dispatch(
@@ -77,18 +73,17 @@ export default class FollowController {
this.store.dispatch(followControllerActions.disable());
}
- broadcastMessage(this.win, {
- type: messages.FOLLOW_SHOW_HINTS,
- keys: this.state.keys as string,
+ this.broadcastMessage((c: FollowSlaveClient) => {
+ c.filterHints(this.state.keys!!);
});
}
activate(): void {
- broadcastMessage(this.win, {
- type: messages.FOLLOW_ACTIVATE,
- keys: this.state.keys as string,
- newTab: this.state.newTab!!,
- background: this.state.background!!,
+ this.broadcastMessage((c: FollowSlaveClient) => {
+ c.activateIfExists(
+ this.state.keys!!,
+ this.state.newTab!!,
+ this.state.background!!);
});
}
@@ -123,50 +118,64 @@ export default class FollowController {
let doc = this.win.document;
let viewWidth = this.win.innerWidth || doc.documentElement.clientWidth;
let viewHeight = this.win.innerHeight || doc.documentElement.clientHeight;
- let frameElements = this.win.document.querySelectorAll('frame,iframe');
-
- this.win.postMessage(JSON.stringify({
- type: messages.FOLLOW_REQUEST_COUNT_TARGETS,
- viewSize: { width: viewWidth, height: viewHeight },
- framePosition: { x: 0, y: 0 },
- }), '*');
- frameElements.forEach((ele) => {
+ let frameElements = this.win.document.querySelectorAll('iframe');
+
+ new FollowSlaveClientImpl(this.win).requestHintCount(
+ { width: viewWidth, height: viewHeight },
+ { x: 0, y: 0 });
+
+ for (let ele of Array.from(frameElements)) {
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 },
- });
- if (ele instanceof HTMLFrameElement && ele.contentWindow ||
- ele instanceof HTMLIFrameElement && ele.contentWindow) {
- ele.contentWindow.postMessage(message, '*');
- }
- });
+ new FollowSlaveClientImpl(ele.contentWindow!!).requestHintCount(
+ { width: viewWidth, height: viewHeight },
+ { x: frameX, y: frameY },
+ );
+ }
}
- create(count: number, sender: WebMessageSender) {
+ create(count: number, sender: Window) {
let produced = [];
for (let i = 0; i < count; ++i) {
produced.push((this.producer as HintKeyProducer).produce());
}
this.keys = this.keys.concat(produced);
- (sender as Window).postMessage(JSON.stringify({
- type: messages.FOLLOW_CREATE_HINTS,
- keysArray: produced,
- newTab: this.state.newTab,
- background: this.state.background,
- }), '*');
+ let doc = this.win.document;
+ let viewWidth = this.win.innerWidth || doc.documentElement.clientWidth;
+ let viewHeight = this.win.innerHeight || doc.documentElement.clientHeight;
+ let pos = { x: 0, y: 0 };
+ if (sender !== window) {
+ let frameElements = this.win.document.querySelectorAll('iframe');
+ let ele = Array.from(frameElements).find(e => e.contentWindow === sender);
+ if (!ele) {
+ // elements of the sender is gone
+ return;
+ }
+ let { left: frameX, top: frameY } = ele.getBoundingClientRect();
+ pos = { x: frameX, y: frameY };
+ }
+ new FollowSlaveClientImpl(sender).createHints(
+ { width: viewWidth, height: viewHeight },
+ pos,
+ produced,
+ );
}
remove() {
this.keys = [];
- broadcastMessage(this.win, {
- type: messages.FOLLOW_REMOVE_HINTS,
+ this.broadcastMessage((c: FollowSlaveClient) => {
+ c.clearHints();
});
}
private hintchars() {
return settingRepository.get().properties.hintchars;
}
+
+ private broadcastMessage(f: (clinet: FollowSlaveClient) => void) {
+ let windows = [window.self].concat(Array.from(window.frames as any));
+ windows
+ .map(w => new FollowSlaveClientImpl(w))
+ .forEach(c => f(c));
+ }
}