diff options
Diffstat (limited to 'ext/fg/js')
| -rw-r--r-- | ext/fg/js/api.js | 24 | ||||
| -rw-r--r-- | ext/fg/js/document.js | 21 | ||||
| -rw-r--r-- | ext/fg/js/float.js | 11 | ||||
| -rw-r--r-- | ext/fg/js/frontend-api-sender.js | 14 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js | 126 | ||||
| -rw-r--r-- | ext/fg/js/popup-nested.js | 7 | ||||
| -rw-r--r-- | ext/fg/js/popup-proxy-host.js | 24 | ||||
| -rw-r--r-- | ext/fg/js/popup-proxy.js | 17 | ||||
| -rw-r--r-- | ext/fg/js/popup.js | 16 | ||||
| -rw-r--r-- | ext/fg/js/source.js | 4 | 
10 files changed, 154 insertions, 110 deletions
| diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js index 6bcb0dbb..d0ac649a 100644 --- a/ext/fg/js/api.js +++ b/ext/fg/js/api.js @@ -17,28 +17,24 @@   */ -function apiOptionsSet(options) { -    return utilInvoke('optionsSet', {options}); +function apiOptionsGet(optionsContext) { +    return utilInvoke('optionsGet', {optionsContext});  } -function apiOptionsGet() { -    return utilInvoke('optionsGet'); +function apiTermsFind(text, optionsContext) { +    return utilInvoke('termsFind', {text, optionsContext});  } -function apiTermsFind(text) { -    return utilInvoke('termsFind', {text}); +function apiKanjiFind(text, optionsContext) { +    return utilInvoke('kanjiFind', {text, optionsContext});  } -function apiKanjiFind(text) { -    return utilInvoke('kanjiFind', {text}); +function apiDefinitionAdd(definition, mode, context, optionsContext) { +    return utilInvoke('definitionAdd', {definition, mode, context, optionsContext});  } -function apiDefinitionAdd(definition, mode, context) { -    return utilInvoke('definitionAdd', {definition, mode, context}); -} - -function apiDefinitionsAddable(definitions, modes) { -    return utilInvoke('definitionsAddable', {definitions, modes}).catch(() => null); +function apiDefinitionsAddable(definitions, modes, optionsContext) { +    return utilInvoke('definitionsAddable', {definitions, modes, optionsContext}).catch(() => null);  }  function apiNoteView(noteId) { diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js index 60b1b9bd..94a68e6c 100644 --- a/ext/fg/js/document.js +++ b/ext/fg/js/document.js @@ -89,13 +89,23 @@ function docImposterCreate(element, isTextarea) {      return [imposter, container];  } -function docRangeFromPoint({x, y}, options) { -    const elements = document.elementsFromPoint(x, y); +function docElementsFromPoint(x, y, all) { +    if (all) { +        return document.elementsFromPoint(x, y); +    } + +    const e = document.elementFromPoint(x, y); +    return e !== null ? [e] : []; +} + +function docRangeFromPoint(x, y, options) { +    const deepDomScan = options.scanning.deepDomScan; +    const elements = docElementsFromPoint(x, y, deepDomScan);      let imposter = null;      let imposterContainer = null;      if (elements.length > 0) {          const element = elements[0]; -        switch (element.nodeName) { +        switch (element.nodeName.toUpperCase()) {              case 'IMG':              case 'BUTTON':                  return new TextSourceElement(element); @@ -108,7 +118,7 @@ function docRangeFromPoint({x, y}, options) {          }      } -    const range = caretRangeFromPointExt(x, y, options.scanning.deepDomScan ? elements : []); +    const range = caretRangeFromPointExt(x, y, deepDomScan ? elements : []);      if (range !== null) {          if (imposter !== null) {              docSetImposterStyle(imposterContainer.style, 'z-index', '-2147483646'); @@ -257,6 +267,9 @@ const caretRangeFromPoint = (() => {          // Firefox          return (x, y) => {              const position = document.caretPositionFromPoint(x, y); +            if (position === null) { +                return null; +            }              const node = position.offsetNode;              if (node === null) {                  return null; diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 3c521714..fd7986b8 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -23,6 +23,11 @@ class DisplayFloat extends Display {          this.autoPlayAudioTimer = null;          this.styleNode = null; +        this.optionsContext = { +            depth: 0, +            url: window.location.href +        }; +          this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract});          $(window).on('message', utilAsync(this.onMessage.bind(this))); @@ -74,8 +79,10 @@ class DisplayFloat extends Display {                  }              }, -            popupNestedInitialize: ({id, depth, parentFrameId}) => { -                popupNestedInitialize(id, depth, parentFrameId); +            popupNestedInitialize: ({id, depth, parentFrameId, url}) => { +                this.optionsContext.depth = depth; +                this.optionsContext.url = url; +                popupNestedInitialize(id, depth, parentFrameId, url);              }          }; diff --git a/ext/fg/js/frontend-api-sender.js b/ext/fg/js/frontend-api-sender.js index a1cb02c4..2e037e62 100644 --- a/ext/fg/js/frontend-api-sender.js +++ b/ext/fg/js/frontend-api-sender.js @@ -26,9 +26,7 @@ class FrontendApiSender {          this.disconnected = false;          this.nextId = 0; -        this.port = chrome.runtime.connect(null, {name: 'backend-api-forwarder'}); -        this.port.onDisconnect.addListener(this.onDisconnect.bind(this)); -        this.port.onMessage.addListener(this.onMessage.bind(this)); +        this.port = null;      }      invoke(action, params, target) { @@ -36,6 +34,10 @@ class FrontendApiSender {              return Promise.reject('Disconnected');          } +        if (this.port === null) { +            this.createPort(); +        } +          const id = `${this.nextId}`;          ++this.nextId; @@ -48,6 +50,12 @@ class FrontendApiSender {          });      } +    createPort() { +        this.port = chrome.runtime.connect(null, {name: 'backend-api-forwarder'}); +        this.port.onDisconnect.addListener(this.onDisconnect.bind(this)); +        this.port.onMessage.addListener(this.onMessage.bind(this)); +    } +      onMessage({type, id, data, senderId}) {          if (senderId !== this.senderId) { return; }          switch (type) { diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 079a7ef2..167e82c0 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -21,13 +21,16 @@ class Frontend {      constructor(popup, ignoreNodes) {          this.popup = popup;          this.popupTimer = null; -        this.mouseDownLeft = false; -        this.mouseDownMiddle = false;          this.textSourceLast = null;          this.pendingLookup = false;          this.options = null;          this.ignoreNodes = (Array.isArray(ignoreNodes) && ignoreNodes.length > 0 ? ignoreNodes.join(',') : null); +        this.optionsContext = { +            depth: popup.depth, +            url: popup.url +        }; +          this.primaryTouchIdentifier = null;          this.contextMenuChecking = false;          this.contextMenuPrevent = false; @@ -40,9 +43,9 @@ class Frontend {      static create() {          const initializationData = window.frontendInitializationData;          const isNested = (initializationData !== null && typeof initializationData === 'object'); -        const {id, parentFrameId, ignoreNodes} = isNested ? initializationData : {}; +        const {id, depth, parentFrameId, ignoreNodes, url} = isNested ? initializationData : {}; -        const popup = isNested ? new PopupProxy(id, parentFrameId) : PopupProxyHost.instance.createPopup(null); +        const popup = isNested ? new PopupProxy(depth + 1, id, parentFrameId, url) : PopupProxyHost.instance.createPopup(null);          const frontend = new Frontend(popup, ignoreNodes);          frontend.prepare();          return frontend; @@ -50,14 +53,13 @@ class Frontend {      async prepare() {          try { -            this.options = await apiOptionsGet(); +            this.options = await apiOptionsGet(this.getOptionsContext());              window.addEventListener('message', this.onFrameMessage.bind(this));              window.addEventListener('mousedown', this.onMouseDown.bind(this));              window.addEventListener('mousemove', this.onMouseMove.bind(this));              window.addEventListener('mouseover', this.onMouseOver.bind(this));              window.addEventListener('mouseout', this.onMouseOut.bind(this)); -            window.addEventListener('mouseup', this.onMouseUp.bind(this));              window.addEventListener('resize', this.onResize.bind(this));              if (this.options.scanning.touchInputEnabled) { @@ -76,7 +78,7 @@ class Frontend {      }      onMouseOver(e) { -        if (e.target === this.popup.container && this.popupTimer) { +        if (e.target === this.popup.container && this.popupTimer !== null) {              this.popupTimerClear();          }      } @@ -84,38 +86,32 @@ class Frontend {      onMouseMove(e) {          this.popupTimerClear(); -        if (!this.options.general.enable) { -            return; -        } - -        if (this.mouseDownLeft) { -            return; -        } - -        if (this.pendingLookup) { +        if ( +            this.pendingLookup || +            !this.options.general.enable || +            (e.buttons & 0x1) !== 0x0 // Left mouse button +        ) {              return;          } -        const mouseScan = this.mouseDownMiddle && this.options.scanning.middleMouse; -        const keyScan = -            this.options.scanning.modifier === 'alt' && e.altKey || -            this.options.scanning.modifier === 'ctrl' && e.ctrlKey || -            this.options.scanning.modifier === 'shift' && e.shiftKey || -            this.options.scanning.modifier === 'none'; - -        if (!keyScan && !mouseScan) { +        const scanningOptions = this.options.scanning; +        const scanningModifier = scanningOptions.modifier; +        if (!( +            Frontend.isScanningModifierPressed(scanningModifier, e) || +            (scanningOptions.middleMouse && (e.buttons & 0x4) !== 0x0) // Middle mouse button +        )) {              return;          }          const search = async () => {              try { -                await this.searchAt({x: e.clientX, y: e.clientY}, 'mouse'); +                await this.searchAt(e.clientX, e.clientY, 'mouse');              } catch (e) {                  this.onError(e);              }          }; -        if (this.options.scanning.modifier === 'none') { +        if (scanningModifier === 'none') {              this.popupTimerSet(search);          } else {              search(); @@ -131,23 +127,8 @@ class Frontend {              return false;          } -        this.mousePosLast = {x: e.clientX, y: e.clientY};          this.popupTimerClear();          this.searchClear(); - -        if (e.which === 1) { -            this.mouseDownLeft = true; -        } else if (e.which === 2) { -            this.mouseDownMiddle = true; -        } -    } - -    onMouseUp(e) { -        if (e.which === 1) { -            this.mouseDownLeft = false; -        } else if (e.which === 2) { -            this.mouseDownMiddle = false; -        }      }      onMouseOut(e) { @@ -239,8 +220,8 @@ class Frontend {          }      } -    onAfterSearch(newRange, type, searched, success) { -        if (type === 'mouse') { +    onAfterSearch(newRange, cause, searched, success) { +        if (cause === 'mouse') {              return;          } @@ -250,7 +231,7 @@ class Frontend {              return;          } -        if (type === 'touchStart' && newRange !== null) { +        if (cause === 'touchStart' && newRange !== null) {              this.scrollPrevent = true;          } @@ -261,11 +242,8 @@ class Frontend {      onBgMessage({action, params}, sender, callback) {          const handlers = { -            optionsSet: options => { -                this.options = options; -                if (!this.options.enable) { -                    this.searchClear(); -                } +            optionsUpdate: () => { +                this.updateOptions();              },              popupSetVisible: ({visible}) => { @@ -284,24 +262,35 @@ class Frontend {          console.log(error);      } +    async updateOptions() { +        this.options = await apiOptionsGet(this.getOptionsContext()); +        if (!this.options.enable) { +            this.searchClear(); +        } +    } +      popupTimerSet(callback) { -        this.popupTimerClear(); -        this.popupTimer = window.setTimeout(callback, this.options.scanning.delay); +        const delay = this.options.scanning.delay; +        if (delay > 0) { +            this.popupTimer = window.setTimeout(callback, delay); +        } else { +            Promise.resolve().then(callback); +        }      }      popupTimerClear() { -        if (this.popupTimer) { +        if (this.popupTimer !== null) {              window.clearTimeout(this.popupTimer);              this.popupTimer = null;          }      } -    async searchAt(point, type) { -        if (this.pendingLookup || await this.popup.containsPoint(point)) { +    async searchAt(x, y, cause) { +        if (this.pendingLookup || await this.popup.containsPoint(x, y)) {              return;          } -        const textSource = docRangeFromPoint(point, this.options); +        const textSource = docRangeFromPoint(x, y, this.options);          let hideResults = textSource === null;          let searched = false;          let success = false; @@ -310,7 +299,7 @@ class Frontend {              if (!hideResults && (!this.textSourceLast || !this.textSourceLast.equals(textSource))) {                  searched = true;                  this.pendingLookup = true; -                const focus = (type === 'mouse'); +                const focus = (cause === 'mouse');                  hideResults = !await this.searchTerms(textSource, focus) && !await this.searchKanji(textSource, focus);                  success = true;              } @@ -335,7 +324,7 @@ class Frontend {              }              this.pendingLookup = false; -            this.onAfterSearch(this.textSourceLast, type, searched, success); +            this.onAfterSearch(this.textSourceLast, cause, searched, success);          }      } @@ -347,7 +336,7 @@ class Frontend {              return;          } -        const {definitions, length} = await apiTermsFind(searchText); +        const {definitions, length} = await apiTermsFind(searchText, this.getOptionsContext());          if (definitions.length === 0) {              return false;          } @@ -380,7 +369,7 @@ class Frontend {              return;          } -        const definitions = await apiKanjiFind(searchText); +        const definitions = await apiKanjiFind(searchText, this.getOptionsContext());          if (definitions.length === 0) {              return false;          } @@ -477,7 +466,7 @@ class Frontend {          this.clickPrevent = value;      } -    searchFromTouch(x, y, type) { +    searchFromTouch(x, y, cause) {          this.popupTimerClear();          if (!this.options.general.enable || this.pendingLookup) { @@ -486,7 +475,7 @@ class Frontend {          const search = async () => {              try { -                await this.searchAt({x, y}, type); +                await this.searchAt(x, y, cause);              } catch (e) {                  this.onError(e);              } @@ -523,6 +512,21 @@ class Frontend {              textSource.setEndOffset(length);          }      } + +    getOptionsContext() { +        this.optionsContext.url = this.popup.url; +        return this.optionsContext; +    } + +    static isScanningModifierPressed(scanningModifier, mouseEvent) { +        switch (scanningModifier) { +            case 'alt': return mouseEvent.altKey; +            case 'ctrl': return mouseEvent.ctrlKey; +            case 'shift': return mouseEvent.shiftKey; +            case 'none': return true; +            default: return false; +        } +    }  }  window.yomichan_frontend = Frontend.create(); diff --git a/ext/fg/js/popup-nested.js b/ext/fg/js/popup-nested.js index e0376bb2..b36de2ec 100644 --- a/ext/fg/js/popup-nested.js +++ b/ext/fg/js/popup-nested.js @@ -19,13 +19,14 @@  let popupNestedInitialized = false; -async function popupNestedInitialize(id, depth, parentFrameId) { +async function popupNestedInitialize(id, depth, parentFrameId, url) {      if (popupNestedInitialized) {          return;      }      popupNestedInitialized = true; -    const options = await apiOptionsGet(); +    const optionsContext = {depth, url}; +    const options = await apiOptionsGet(optionsContext);      const popupNestingMaxDepth = options.scanning.popupNestingMaxDepth;      if (!(typeof popupNestingMaxDepth === 'number' && typeof depth === 'number' && depth < popupNestingMaxDepth)) { @@ -34,7 +35,7 @@ async function popupNestedInitialize(id, depth, parentFrameId) {      const ignoreNodes = options.scanning.enableOnPopupExpressions ? [] : [ '.expression', '.expression *' ]; -    window.frontendInitializationData = {id, depth, parentFrameId, ignoreNodes}; +    window.frontendInitializationData = {id, depth, parentFrameId, ignoreNodes, url};      const scriptSrcs = [          '/fg/js/frontend-api-sender.js', diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index fa61aeb4..396f7556 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -42,9 +42,9 @@ class PopupProxyHost {              showOrphaned: ({id, elementRect, options}) => this.show(id, elementRect, options),              hide: ({id}) => this.hide(id),              setVisible: ({id, visible}) => this.setVisible(id, visible), -            containsPoint: ({id, point}) => this.containsPoint(id, point), -            termsShow: ({id, elementRect, definitions, options, context}) => this.termsShow(id, elementRect, definitions, options, context), -            kanjiShow: ({id, elementRect, definitions, options, context}) => this.kanjiShow(id, elementRect, definitions, options, context), +            containsPoint: ({id, x, y}) => this.containsPoint(id, x, y), +            termsShow: ({id, elementRect, writingMode, definitions, options, context}) => this.termsShow(id, elementRect, writingMode, definitions, options, context), +            kanjiShow: ({id, elementRect, writingMode, definitions, options, context}) => this.kanjiShow(id, elementRect, writingMode, definitions, options, context),              clearAutoPlayTimer: ({id}) => this.clearAutoPlayTimer(id)          });      } @@ -108,27 +108,33 @@ class PopupProxyHost {          return popup.setVisible(visible);      } -    async containsPoint(id, point) { +    async containsPoint(id, x, y) {          const popup = this.getPopup(id); -        return await popup.containsPoint(point); +        return await popup.containsPoint(x, y);      } -    async termsShow(id, elementRect, definitions, options, context) { +    async termsShow(id, elementRect, writingMode, definitions, options, context) {          const popup = this.getPopup(id);          elementRect = this.jsonRectToDOMRect(popup, elementRect); -        return await popup.termsShow(elementRect, definitions, options, context); +        if (!PopupProxyHost.popupCanShow(popup)) { return false; } +        return await popup.termsShow(elementRect, writingMode, definitions, options, context);      } -    async kanjiShow(id, elementRect, definitions, options, context) { +    async kanjiShow(id, elementRect, writingMode, definitions, options, context) {          const popup = this.getPopup(id);          elementRect = this.jsonRectToDOMRect(popup, elementRect); -        return await popup.kanjiShow(elementRect, definitions, options, context); +        if (!PopupProxyHost.popupCanShow(popup)) { return false; } +        return await popup.kanjiShow(elementRect, writingMode, definitions, options, context);      }      async clearAutoPlayTimer(id) {          const popup = this.getPopup(id);          return popup.clearAutoPlayTimer();      } + +    static popupCanShow(popup) { +        return popup.parent === null || popup.parent.isVisible(); +    }  }  PopupProxyHost.instance = PopupProxyHost.create(); diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index f6295079..235e1730 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -18,14 +18,15 @@  class PopupProxy { -    constructor(parentId, parentFrameId) { +    constructor(depth, parentId, parentFrameId, url) {          this.parentId = parentId;          this.parentFrameId = parentFrameId;          this.id = null;          this.idPromise = null;          this.parent = null;          this.child = null; -        this.depth = 0; +        this.depth = depth; +        this.url = url;          this.container = null; @@ -69,23 +70,23 @@ class PopupProxy {          return await this.invokeHostApi('setVisible', {id, visible});      } -    async containsPoint(point) { +    async containsPoint(x, y) {          if (this.id === null) {              return false;          } -        return await this.invokeHostApi('containsPoint', {id: this.id, point}); +        return await this.invokeHostApi('containsPoint', {id: this.id, x, y});      } -    async termsShow(elementRect, definitions, options, context) { +    async termsShow(elementRect, writingMode, definitions, options, context) {          const id = await this.getPopupId();          elementRect = PopupProxy.DOMRectToJson(elementRect); -        return await this.invokeHostApi('termsShow', {id, elementRect, definitions, options, context}); +        return await this.invokeHostApi('termsShow', {id, elementRect, writingMode, definitions, options, context});      } -    async kanjiShow(elementRect, definitions, options, context) { +    async kanjiShow(elementRect, writingMode, definitions, options, context) {          const id = await this.getPopupId();          elementRect = PopupProxy.DOMRectToJson(elementRect); -        return await this.invokeHostApi('kanjiShow', {id, elementRect, definitions, options, context}); +        return await this.invokeHostApi('kanjiShow', {id, elementRect, writingMode, definitions, options, context});      }      async clearAutoPlayTimer() { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 1b15977b..08965084 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -59,7 +59,8 @@ class Popup {                  this.invokeApi('popupNestedInitialize', {                      id: this.id,                      depth: this.depth, -                    parentFrameId +                    parentFrameId, +                    url: this.url                  });                  this.invokeApi('setOptions', {                      general: { @@ -239,9 +240,12 @@ class Popup {      }      focusParent() { -        if (this.parent && this.parent.container) { +        if (this.parent !== null) {              // Chrome doesn't like focusing iframe without contentWindow. -            this.parent.container.contentWindow.focus(); +            const contentWindow = this.parent.container.contentWindow; +            if (contentWindow !== null) { +                contentWindow.focus(); +            }          } else {              // Firefox doesn't like focusing window without first blurring the iframe.              // this.container.contentWindow.blur() doesn't work on Firefox for some reason. @@ -251,7 +255,7 @@ class Popup {          }      } -    async containsPoint({x, y}) { +    async containsPoint(x, y) {          for (let popup = this; popup !== null && popup.isVisible(); popup = popup.child) {              const rect = popup.container.getBoundingClientRect();              if (x >= rect.left && y >= rect.top && x < rect.right && y < rect.bottom) { @@ -308,4 +312,8 @@ class Popup {              parent.appendChild(this.container);          }      } + +    get url() { +        return window.location.href; +    }  } diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js index 18a1a976..4642de50 100644 --- a/ext/fg/js/source.js +++ b/ext/fg/js/source.js @@ -88,7 +88,7 @@ class TextSourceRange {          }          const skip = ['RT', 'SCRIPT', 'STYLE']; -        if (skip.includes(node.nodeName)) { +        if (skip.includes(node.nodeName.toUpperCase())) {              return false;          } @@ -285,7 +285,7 @@ class TextSourceElement {      }      setEndOffset(length) { -        switch (this.element.nodeName) { +        switch (this.element.nodeName.toUpperCase()) {              case 'BUTTON':                  this.content = this.element.innerHTML;                  break; |