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)); +  }  } | 
