aboutsummaryrefslogtreecommitdiff
path: root/ext/fg
diff options
context:
space:
mode:
authorAlex Yatskov <alex@foosoft.net>2019-09-23 17:35:36 -0700
committerAlex Yatskov <alex@foosoft.net>2019-09-23 17:35:36 -0700
commitf4b6527ed6ed1f0f4f5a63b94766b20f3b90e6ec (patch)
tree0d2f733c13597dd4067d3dc01e6da27f96bfe81b /ext/fg
parentcfc6363a01ee00e89866c54709006d6f55d093de (diff)
parentf5afe590ad0730a695614b32032b7ea70b46c7b0 (diff)
Merge branch 'master' into testing
Diffstat (limited to 'ext/fg')
-rw-r--r--ext/fg/js/api.js24
-rw-r--r--ext/fg/js/document.js21
-rw-r--r--ext/fg/js/float.js11
-rw-r--r--ext/fg/js/frontend-api-sender.js14
-rw-r--r--ext/fg/js/frontend.js126
-rw-r--r--ext/fg/js/popup-nested.js7
-rw-r--r--ext/fg/js/popup-proxy-host.js24
-rw-r--r--ext/fg/js/popup-proxy.js17
-rw-r--r--ext/fg/js/popup.js16
-rw-r--r--ext/fg/js/source.js4
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;