aboutsummaryrefslogtreecommitdiff
path: root/ext/fg/js
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2019-08-31 19:47:00 -0400
committertoasted-nutbread <toasted-nutbread@users.noreply.github.com>2019-09-02 11:43:33 -0400
commit737a5ee8a814bc89ac40f99264e8835c47f77387 (patch)
treecd4bdaca2468c784cee8817eabf395b465c5cec3 /ext/fg/js
parentd296ebd593d125d131b5bf9974e19d13ca3b3b3f (diff)
Allow elements behind other transparent elements to be scanned
Diffstat (limited to 'ext/fg/js')
-rw-r--r--ext/fg/js/document.js81
1 files changed, 78 insertions, 3 deletions
diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js
index a64b6c04..8bb857e7 100644
--- a/ext/fg/js/document.js
+++ b/ext/fg/js/document.js
@@ -17,6 +17,8 @@
*/
+const REGEX_TRANSPARENT_COLOR = /rgba\s*\([^\)]*,\s*0(?:\.0+)?\s*\)/;
+
function docSetImposterStyle(style, propertyName, value) {
style.setProperty(propertyName, value, 'important');
}
@@ -88,10 +90,11 @@ function docImposterCreate(element, isTextarea) {
}
function docRangeFromPoint({x, y}) {
- const element = document.elementFromPoint(x, y);
+ const elements = document.elementsFromPoint(x, y);
let imposter = null;
let imposterContainer = null;
- if (element) {
+ if (elements.length > 0) {
+ const element = elements[0];
switch (element.nodeName) {
case 'IMG':
case 'BUTTON':
@@ -105,7 +108,7 @@ function docRangeFromPoint({x, y}) {
}
}
- const range = caretRangeFromPoint(x, y);
+ const range = caretRangeFromPointExt(x, y, elements);
if (range !== null) {
if (imposter !== null) {
docSetImposterStyle(imposterContainer.style, 'z-index', '-2147483646');
@@ -261,3 +264,75 @@ const caretRangeFromPoint = (() => {
// No support
return () => null;
})();
+
+function caretRangeFromPointExt(x, y, elements) {
+ const modifications = [];
+ try {
+ let i = 0;
+ while (true) {
+ const range = caretRangeFromPoint(x, y);
+ if (range === null) {
+ return null;
+ }
+
+ const inRange = isPointInRange(x, y, range);
+ if (inRange) {
+ return range;
+ }
+
+ i = disableTransparentElement(elements, i, modifications);
+ if (i < 0) {
+ return null;
+ }
+ }
+ } finally {
+ if (modifications.length > 0) {
+ restoreElementStyleModifications(modifications);
+ }
+ }
+}
+
+function disableTransparentElement(elements, i, modifications) {
+ while (true) {
+ if (i >= elements.length) {
+ return -1;
+ }
+
+ const element = elements[i++];
+ if (isElementTransparent(element)) {
+ const style = element.hasAttribute('style') ? element.getAttribute('style') : null;
+ modifications.push({element, style});
+ element.style.pointerEvents = 'none';
+ return i;
+ }
+ }
+}
+
+function restoreElementStyleModifications(modifications) {
+ for (const {element, style} of modifications) {
+ if (style === null) {
+ element.removeAttribute('style');
+ } else {
+ element.setAttribute('style', style);
+ }
+ }
+}
+
+function isElementTransparent(element) {
+ if (
+ element === document.body ||
+ element === document.documentElement
+ ) {
+ return false;
+ }
+ const style = window.getComputedStyle(element);
+ return (
+ parseFloat(style.opacity) < 0 ||
+ style.visibility === 'hidden' ||
+ (style.backgroundImage === 'none' && isColorTransparent(style.backgroundColor))
+ );
+}
+
+function isColorTransparent(cssColor) {
+ return REGEX_TRANSPARENT_COLOR.test(cssColor);
+}