diff options
| author | Alex Yatskov <alex@foosoft.net> | 2019-11-05 19:04:13 -0800 | 
|---|---|---|
| committer | Alex Yatskov <alex@foosoft.net> | 2019-11-05 19:04:13 -0800 | 
| commit | 08ad2779678cd447bd747c2b155ef9b5135fdf5d (patch) | |
| tree | faa54cbf9176989f9bd3c3b90ff3e032189adb20 /ext/fg/js | |
| parent | 438498435227cfa59cf9ed3430045b288cd2a7c0 (diff) | |
| parent | 91c01e0a7eeeb851344a22ace8a5fa0b873a3e57 (diff) | |
Merge branch 'master' into testing
Diffstat (limited to 'ext/fg/js')
| -rw-r--r-- | ext/fg/js/api.js | 8 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js | 411 | ||||
| -rw-r--r-- | ext/fg/js/popup.js | 2 | 
3 files changed, 195 insertions, 226 deletions
| diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js index b0746b85..54818702 100644 --- a/ext/fg/js/api.js +++ b/ext/fg/js/api.js @@ -21,6 +21,10 @@ function apiOptionsGet(optionsContext) {      return utilInvoke('optionsGet', {optionsContext});  } +function apiOptionsSet(changedOptions, optionsContext, source) { +    return utilInvoke('optionsSet', {changedOptions, optionsContext, source}); +} +  function apiTermsFind(text, optionsContext) {      return utilInvoke('termsFind', {text, optionsContext});  } @@ -72,3 +76,7 @@ function apiInjectStylesheet(css) {  function apiGetEnvironmentInfo() {      return utilInvoke('getEnvironmentInfo');  } + +function apiClipboardGet() { +    return utilInvoke('clipboardGet'); +} diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index e854f74e..e67008df 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -20,8 +20,8 @@  class Frontend {      constructor(popup, ignoreNodes) {          this.popup = popup; -        this.popupTimer = null; -        this.textSourceLast = null; +        this.popupTimerPromise = null; +        this.textSourceCurrent = null;          this.pendingLookup = false;          this.options = null;          this.ignoreNodes = (Array.isArray(ignoreNodes) && ignoreNodes.length > 0 ? ignoreNodes.join(',') : null); @@ -32,12 +32,10 @@ class Frontend {          };          this.primaryTouchIdentifier = null; -        this.contextMenuChecking = false; -        this.contextMenuPrevent = false; -        this.contextMenuPreviousRange = null; -        this.mouseDownPrevent = false; -        this.clickPrevent = false; -        this.scrollPrevent = false; +        this.preventNextContextMenu = false; +        this.preventNextMouseDown = false; +        this.preventNextClick = false; +        this.preventScroll = false;          this.enabled = false;          this.eventListeners = []; @@ -74,7 +72,7 @@ class Frontend {      }      onMouseOver(e) { -        if (e.target === this.popup.container && this.popupTimer !== null) { +        if (e.target === this.popup.container) {              this.popupTimerClear();          }      } @@ -82,10 +80,7 @@ class Frontend {      onMouseMove(e) {          this.popupTimerClear(); -        if ( -            this.pendingLookup || -            (e.buttons & 0x1) !== 0x0 // Left mouse button -        ) { +        if (this.pendingLookup || Frontend.isMouseButton('primary', e)) {              return;          } @@ -93,65 +88,60 @@ class Frontend {          const scanningModifier = scanningOptions.modifier;          if (!(              Frontend.isScanningModifierPressed(scanningModifier, e) || -            (scanningOptions.middleMouse && (e.buttons & 0x4) !== 0x0) // Middle mouse button +            (scanningOptions.middleMouse && Frontend.isMouseButton('auxiliary', e))          )) {              return;          }          const search = async () => { -            try { -                await this.searchAt(e.clientX, e.clientY, 'mouse'); -            } catch (e) { -                this.onError(e); +            if (scanningModifier === 'none') { +                if (!await this.popupTimerWait()) { +                    // Aborted +                    return; +                }              } + +            await this.searchAt(e.clientX, e.clientY, 'mouse');          }; -        if (scanningModifier === 'none') { -            this.popupTimerSet(search); -        } else { -            search(); -        } +        search();      }      onMouseDown(e) { -        if (this.mouseDownPrevent) { -            this.setMouseDownPrevent(false, false); -            this.setClickPrevent(true); +        if (this.preventNextMouseDown) { +            this.preventNextMouseDown = false; +            this.preventNextClick = true;              e.preventDefault();              e.stopPropagation();              return false;          } -        this.popupTimerClear(); -        this.searchClear(true); +        if (e.button === 0) { +            this.popupTimerClear(); +            this.searchClear(true); +        }      }      onMouseOut(e) {          this.popupTimerClear();      } -    onWindowMessage(e) { -        const action = e.data; -        const handlers = Frontend.windowMessageHandlers; -        if (handlers.hasOwnProperty(action)) { -            const handler = handlers[action]; -            handler(this); +    onClick(e) { +        if (this.preventNextClick) { +            this.preventNextClick = false; +            e.preventDefault(); +            e.stopPropagation(); +            return false;          }      } -    async onResize() { -        if (this.textSourceLast !== null && await this.popup.isVisibleAsync()) { -            const textSource = this.textSourceLast; -            this.lastShowPromise = this.popup.showContent( -                textSource.getRect(), -                textSource.getWritingMode() -            ); -        } +    onAuxClick(e) { +        this.preventNextContextMenu = false;      } -    onClick(e) { -        if (this.clickPrevent) { -            this.setClickPrevent(false); +    onContextMenu(e) { +        if (this.preventNextContextMenu) { +            this.preventNextContextMenu = false;              e.preventDefault();              e.stopPropagation();              return false; @@ -159,28 +149,58 @@ class Frontend {      }      onTouchStart(e) { -        if (this.primaryTouchIdentifier !== null && this.getIndexOfTouch(e.touches, this.primaryTouchIdentifier) >= 0) { +        if (this.primaryTouchIdentifier !== null || e.changedTouches.length === 0) {              return;          } -        let touch = this.getPrimaryTouch(e.changedTouches); -        if (this.selectionContainsPoint(window.getSelection(), touch.clientX, touch.clientY)) { -            touch = null; +        this.preventScroll = false; +        this.preventNextContextMenu = false; +        this.preventNextMouseDown = false; +        this.preventNextClick = false; + +        const primaryTouch = e.changedTouches[0]; +        if (Frontend.selectionContainsPoint(window.getSelection(), primaryTouch.clientX, primaryTouch.clientY)) { +            return;          } -        this.setPrimaryTouch(touch); -    } +        this.primaryTouchIdentifier = primaryTouch.identifier; -    onTouchEnd(e) { -        if (this.primaryTouchIdentifier === null) { +        if (this.pendingLookup) {              return;          } -        if (this.getIndexOfTouch(e.changedTouches, this.primaryTouchIdentifier) < 0) { +        const textSourceCurrentPrevious = this.textSourceCurrent !== null ? this.textSourceCurrent.clone() : null; + +        this.searchAt(primaryTouch.clientX, primaryTouch.clientY, 'touchStart') +        .then(() => { +            if ( +                this.textSourceCurrent === null || +                this.textSourceCurrent.equals(textSourceCurrentPrevious) +            ) { +                return; +            } + +            this.preventScroll = true; +            this.preventNextContextMenu = true; +            this.preventNextMouseDown = true; +        }); +    } + +    onTouchEnd(e) { +        if ( +            this.primaryTouchIdentifier === null || +            this.getIndexOfTouch(e.changedTouches, this.primaryTouchIdentifier) < 0 +        ) {              return;          } -        this.setPrimaryTouch(this.getPrimaryTouch(this.excludeTouches(e.touches, e.changedTouches))); +        this.primaryTouchIdentifier = null; +        this.preventScroll = false; +        this.preventNextClick = false; +        // Don't revert context menu and mouse down prevention, +        // since these events can occur after the touch has ended. +        // this.preventNextContextMenu = false; +        // this.preventNextMouseDown = false;      }      onTouchCancel(e) { @@ -188,7 +208,7 @@ class Frontend {      }      onTouchMove(e) { -        if (!this.scrollPrevent || !e.cancelable || this.primaryTouchIdentifier === null) { +        if (!this.preventScroll || !e.cancelable || this.primaryTouchIdentifier === null) {              return;          } @@ -198,39 +218,29 @@ class Frontend {              return;          } -        const touch = touches[index]; -        this.searchFromTouch(touch.clientX, touch.clientY, 'touchMove'); +        const primaryTouch = touches[index]; +        this.searchAt(primaryTouch.clientX, primaryTouch.clientY, 'touchMove');          e.preventDefault(); // Disable scroll      } -    onContextMenu(e) { -        if (this.contextMenuPrevent) { -            this.setContextMenuPrevent(false, false); -            e.preventDefault(); -            e.stopPropagation(); -            return false; +    async onResize() { +        if (this.textSourceCurrent !== null && await this.popup.isVisibleAsync()) { +            const textSource = this.textSourceCurrent; +            this.lastShowPromise = this.popup.showContent( +                textSource.getRect(), +                textSource.getWritingMode() +            );          }      } -    onAfterSearch(newRange, cause, searched, success) { -        if (cause === 'mouse') { -            return; -        } - -        if ( -            !this.contextMenuChecking || -            (this.contextMenuPreviousRange === null ? newRange === null : this.contextMenuPreviousRange.equals(newRange))) { -            return; -        } - -        if (cause === 'touchStart' && newRange !== null) { -            this.scrollPrevent = true; +    onWindowMessage(e) { +        const action = e.data; +        const handlers = Frontend.windowMessageHandlers; +        if (handlers.hasOwnProperty(action)) { +            const handler = handlers[action]; +            handler(this);          } - -        this.setContextMenuPrevent(true, false); -        this.setMouseDownPrevent(true, false); -        this.contextMenuChecking = false;      }      onRuntimeMessage({action, params}, sender, callback) { @@ -271,6 +281,7 @@ class Frontend {          if (this.options.scanning.touchInputEnabled) {              this.addEventListener(window, 'click', this.onClick.bind(this)); +            this.addEventListener(window, 'auxclick', this.onAuxClick.bind(this));              this.addEventListener(window, 'touchstart', this.onTouchStart.bind(this));              this.addEventListener(window, 'touchend', this.onTouchEnd.bind(this));              this.addEventListener(window, 'touchcancel', this.onTouchCancel.bind(this)); @@ -297,47 +308,69 @@ class Frontend {          await this.popup.setOptions(this.options);      } -    popupTimerSet(callback) { +    async popupTimerWait() {          const delay = this.options.scanning.delay; -        if (delay > 0) { -            this.popupTimer = window.setTimeout(callback, delay); -        } else { -            Promise.resolve().then(callback); +        const promise = promiseTimeout(delay, true); +        this.popupTimerPromise = promise; +        try { +            return await promise; +        } finally { +            if (this.popupTimerPromise === promise) { +                this.popupTimerPromise = null; +            }          }      }      popupTimerClear() { -        if (this.popupTimer !== null) { -            window.clearTimeout(this.popupTimer); -            this.popupTimer = null; +        if (this.popupTimerPromise !== null) { +            this.popupTimerPromise.resolve(false); +            this.popupTimerPromise = null;          }      }      async searchAt(x, y, cause) { -        if (this.pendingLookup || await this.popup.containsPoint(x, y)) { -            return; -        } +        try { +            this.popupTimerClear(); -        const textSource = docRangeFromPoint(x, y, this.options); -        return await this.searchSource(textSource, cause); +            if (this.pendingLookup || await this.popup.containsPoint(x, y)) { +                return; +            } + +            const textSource = docRangeFromPoint(x, y, this.options); +            if (this.textSourceCurrent !== null && this.textSourceCurrent.equals(textSource)) { +                return; +            } + +            try { +                await this.searchSource(textSource, cause); +            } finally { +                if (textSource !== null) { +                    textSource.cleanup(); +                } +            } +        } catch (e) { +            this.onError(e); +        }      }      async searchSource(textSource, cause) { -        let hideResults = textSource === null; -        let searched = false; -        let success = false; +        let results = null;          try { -            if (!hideResults && (!this.textSourceLast || !this.textSourceLast.equals(textSource))) { -                searched = true; -                this.pendingLookup = true; -                const focus = (cause === 'mouse'); -                hideResults = !await this.searchTerms(textSource, focus) && !await this.searchKanji(textSource, focus); -                success = true; +            this.pendingLookup = true; +            if (textSource !== null) { +                results = ( +                    await this.findTerms(textSource) || +                    await this.findKanji(textSource) +                ); +                if (results !== null) { +                    const focus = (cause === 'mouse'); +                    this.showContent(textSource, focus, results.definitions, results.type); +                }              }          } catch (e) {              if (window.yomichan_orphaned) { -                if (textSource && this.options.scanning.modifier !== 'none') { +                if (textSource !== null && this.options.scanning.modifier !== 'none') {                      this.lastShowPromise = this.popup.showContent(                          textSource.getRect(),                          textSource.getWritingMode(), @@ -348,93 +381,69 @@ class Frontend {                  this.onError(e);              }          } finally { -            if (textSource !== null) { -                textSource.cleanup(); -            } -            if (hideResults && this.options.scanning.autoHideResults) { +            if (results === null && this.options.scanning.autoHideResults) {                  this.searchClear(true);              }              this.pendingLookup = false; -            this.onAfterSearch(this.textSourceLast, cause, searched, success); -        } -    } - -    async searchTerms(textSource, focus) { -        this.setTextSourceScanLength(textSource, this.options.scanning.length); - -        const searchText = textSource.text(); -        if (searchText.length === 0) { -            return false;          } -        const {definitions, length} = await apiTermsFind(searchText, this.getOptionsContext()); -        if (definitions.length === 0) { -            return false; -        } - -        textSource.setEndOffset(length); +        return results; +    } +    showContent(textSource, focus, definitions, type) {          const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);          const url = window.location.href;          this.lastShowPromise = this.popup.showContent(              textSource.getRect(),              textSource.getWritingMode(), -            'terms', +            type,              {definitions, context: {sentence, url, focus}}          ); -        this.textSourceLast = textSource; +        this.textSourceCurrent = textSource;          if (this.options.scanning.selectText) {              textSource.select();          } +    } + +    async findTerms(textSource) { +        this.setTextSourceScanLength(textSource, this.options.scanning.length); + +        const searchText = textSource.text(); +        if (searchText.length === 0) { return null; } + +        const {definitions, length} = await apiTermsFind(searchText, this.getOptionsContext()); +        if (definitions.length === 0) { return null; } -        return true; +        textSource.setEndOffset(length); + +        return {definitions, type: 'terms'};      } -    async searchKanji(textSource, focus) { +    async findKanji(textSource) {          this.setTextSourceScanLength(textSource, 1);          const searchText = textSource.text(); -        if (searchText.length === 0) { -            return false; -        } +        if (searchText.length === 0) { return null; }          const definitions = await apiKanjiFind(searchText, this.getOptionsContext()); -        if (definitions.length === 0) { -            return false; -        } - -        const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); -        const url = window.location.href; -        this.lastShowPromise = this.popup.showContent( -            textSource.getRect(), -            textSource.getWritingMode(), -            'kanji', -            {definitions, context: {sentence, url, focus}} -        ); - -        this.textSourceLast = textSource; -        if (this.options.scanning.selectText) { -            textSource.select(); -        } +        if (definitions.length === 0) { return null; } -        return true; +        return {definitions, type: 'kanji'};      }      searchClear(changeFocus) {          this.popup.hide(changeFocus);          this.popup.clearAutoPlayTimer(); -        if (this.options.scanning.selectText && this.textSourceLast) { -            this.textSourceLast.deselect(); -        } - -        this.textSourceLast = null; -    } +        if (this.textSourceCurrent !== null) { +            if (this.options.scanning.selectText) { +                this.textSourceCurrent.deselect(); +            } -    getPrimaryTouch(touchList) { -        return touchList.length > 0 ? touchList[0] : null; +            this.textSourceCurrent = null; +        }      }      getIndexOfTouch(touchList, identifier) { @@ -447,74 +456,7 @@ class Frontend {          return -1;      } -    excludeTouches(touchList, excludeTouchList) { -        const result = []; -        for (let r of touchList) { -            if (this.getIndexOfTouch(excludeTouchList, r.identifier) < 0) { -                result.push(r); -            } -        } -        return result; -    } - -    setPrimaryTouch(touch) { -        if (touch === null) { -            this.primaryTouchIdentifier = null; -            this.contextMenuPreviousRange = null; -            this.contextMenuChecking = false; -            this.scrollPrevent = false; -            this.setContextMenuPrevent(false, true); -            this.setMouseDownPrevent(false, true); -            this.setClickPrevent(false); -        } -        else { -            this.primaryTouchIdentifier = touch.identifier; -            this.contextMenuPreviousRange = this.textSourceLast ? this.textSourceLast.clone() : null; -            this.contextMenuChecking = true; -            this.scrollPrevent = false; -            this.setContextMenuPrevent(false, false); -            this.setMouseDownPrevent(false, false); -            this.setClickPrevent(false); - -            this.searchFromTouch(touch.clientX, touch.clientY, 'touchStart'); -        } -    } - -    setContextMenuPrevent(value, delay) { -        if (!delay) { -            this.contextMenuPrevent = value; -        } -    } - -    setMouseDownPrevent(value, delay) { -        if (!delay) { -            this.mouseDownPrevent = value; -        } -    } - -    setClickPrevent(value) { -        this.clickPrevent = value; -    } - -    searchFromTouch(x, y, cause) { -        this.popupTimerClear(); - -        if (this.pendingLookup) { -            return; -        } - -        const search = async () => { -            try { -                await this.searchAt(x, y, cause); -            } catch (e) { -                this.onError(e); -            } -        }; - -        search(); -    } - -    selectionContainsPoint(selection, x, y) { +    static selectionContainsPoint(selection, x, y) {          for (let i = 0; i < selection.rangeCount; ++i) {              const range = selection.getRangeAt(i);              for (const rect of range.getClientRects()) { @@ -557,6 +499,25 @@ class Frontend {              default: return false;          }      } + +    static isMouseButton(button, mouseEvent) { +        switch (mouseEvent.type) { +            case 'mouseup': +            case 'mousedown': +            case 'click': switch (button) { +                case 'primary': return mouseEvent.button === 0; +                case 'secondary': return mouseEvent.button === 2; +                case 'auxiliary': return mouseEvent.button === 1; +                default: return false; +            } +            default: switch (button) { +                case 'primary': return (mouseEvent.buttons & 0x1) !== 0x0; +                case 'secondary': return (mouseEvent.buttons & 0x2) !== 0x0; +                case 'auxiliary': return (mouseEvent.buttons & 0x4) !== 0x0; +                default: return false; +            } +        } +    }  }  Frontend.windowMessageHandlers = { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index b5eb9fe2..1f9317e0 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -30,7 +30,7 @@ class Popup {          this.container.className = 'yomichan-float';          this.container.addEventListener('mousedown', e => e.stopPropagation());          this.container.addEventListener('scroll', e => e.stopPropagation()); -        this.container.setAttribute('src', chrome.extension.getURL('/fg/float.html')); +        this.container.setAttribute('src', chrome.runtime.getURL('/fg/float.html'));          this.container.style.width = '0px';          this.container.style.height = '0px';          this.injectPromise = null; |