diff options
author | Shin'ya Ueoka <ueokande@i-beam.org> | 2019-05-18 13:06:37 +0900 |
---|---|---|
committer | Shin'ya Ueoka <ueokande@i-beam.org> | 2019-05-18 16:23:00 +0900 |
commit | a88324acd9fe626b59637541975abe1ee6041aa7 (patch) | |
tree | 0c23a15e928977e79f18476b3a5119d0f750f9e9 /src/content/components | |
parent | 17dc2bb5ec6a53c67e1b6df2b82410239eee95fc (diff) |
Define client and presenter for follow
Diffstat (limited to 'src/content/components')
-rw-r--r-- | src/content/components/common/follow.ts | 163 | ||||
-rw-r--r-- | src/content/components/common/index.ts | 2 | ||||
-rw-r--r-- | src/content/components/top-content/follow-controller.ts | 95 |
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)); + } } |