Skip to content

Commit

Permalink
feat(detection): Ignore invisible nodes when extracting text under mouse
Browse files Browse the repository at this point in the history
Specifically, ignore `display: none` and `visibility: hidden`.

Fixes #366
  • Loading branch information
melink14 committed Jul 2, 2021
1 parent 51a0bf1 commit 45f8fc4
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
13 changes: 13 additions & 0 deletions extension/rikaicontent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,12 @@ class RcxContent {
text.length < maxLength &&
(nextNode = result.iterateNext() as Text | null)
) {
// Since these nodes are text nodes we can assume parentElement exists.
if (!this.#isElementVisible(nextNode.parentElement!)) {
// If a node isn't visible it shouldn't be part of our text look up.
// See issue #366 for an example where it breaks look ups.
continue;
}
endIndex = Math.min(nextNode.data.length, maxLength - text.length);
text += nextNode.data.substring(0, endIndex);
selEndList.push({ node: nextNode, offset: endIndex });
Expand All @@ -679,6 +685,11 @@ class RcxContent {
return text;
}

#isElementVisible(element: HTMLElement) {
const style = window.getComputedStyle(element);
return style.visibility !== 'hidden' && style.display !== 'none';
}

// given a node which must not be null,
// returns either the next sibling or next sibling of the father or
// next sibling of the fathers father and so on or null
Expand Down Expand Up @@ -1286,3 +1297,5 @@ chrome.runtime.onMessage.addListener((request) => {

// When a page first loads, checks to see if it should enable script
chrome.runtime.sendMessage({ type: 'enable?' });

export { RcxContent as TestOnlyRcxContent };
32 changes: 32 additions & 0 deletions extension/test/chrome_stubs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};

const chromeSpy: DeepPartial<typeof chrome> & { reset: Function } = {
// Creates the partial structure of chrome web APIs rikaikun tests need.
runtime: {
sendMessage: () => {},
onMessage: {
addListener: () => {},
},
},

/**
* Set's named spies for each method defined above and allows state to be
* easily reset between test methods.
*/
reset() {
chromeSpy.runtime!.sendMessage = jasmine.createSpy(
'chrome.runtime.sendMessage'
);
chromeSpy.runtime!.onMessage!.addListener = jasmine.createSpy(
'chrome.runtime.onMessage.addListener'
);
},
};

chromeSpy.reset();
window.chrome = chromeSpy as unknown as typeof chrome;
const hardenedChrome = chromeSpy as Required<typeof chromeSpy>;

export { hardenedChrome as chrome };
64 changes: 64 additions & 0 deletions extension/test/rikaicontent_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// import 'jasmine-sinon';
// import * as sinonChrome from 'sinon-chrome';
import { TestOnlyRcxContent } from '../rikaicontent';
import { chrome } from './chrome_stubs';

// declare const chrome: Required<typeof chromeSpy>;

describe('RcxContent.show', () => {
beforeEach(() => {
chrome.reset();
});

describe('when given Japanese word interrupted with text wrapped by `display: none`', () => {
it('sends "xsearch" message with invisible text omitted', () => {
const rcxContent = new TestOnlyRcxContent();
const span = insertHtmlIntoDomAndReturnFirstTextNode(
'<span>試<span style="display:none">test</span>す</span>'
);

executeShowForGivenNode(rcxContent, span);

expect(chrome.runtime.sendMessage).toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'xsearch', text: '試す' }),
jasmine.any(Function)
);
});
});

describe('when given Japanese word interrupted with text wrapped by `visibility: hidden`', () => {
it('sends "xsearch" message with invisible text omitted', () => {
const rcxContent = new TestOnlyRcxContent();
const span = insertHtmlIntoDomAndReturnFirstTextNode(
'<span>試<span style="visibility: hidden">test</span>す</span>'
);

executeShowForGivenNode(rcxContent, span);

expect(chrome.runtime.sendMessage).toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'xsearch', text: '試す' }),
jasmine.any(Function)
);
});
});
});

function insertHtmlIntoDomAndReturnFirstTextNode(htmlString: string): Node {
const template = document.createElement('template');
template.innerHTML = htmlString;
return document.body.appendChild(template.content.firstChild!);
}

function executeShowForGivenNode(
rcxContent: TestOnlyRcxContent,
node: Node
): void {
rcxContent.show(
{
prevRangeNode: rcxContent.getFirstTextChild(node) as Text,
prevRangeOfs: 0,
uofs: 0,
},
0
);
}

0 comments on commit 45f8fc4

Please sign in to comment.