From c4c3292436a631f6d10cdd1d16bbdff4097f4fcb Mon Sep 17 00:00:00 2001 From: thc202 Date: Wed, 10 Jun 2026 14:05:51 +0100 Subject: [PATCH] Report interactable state Report if an element can be interacted with (e.g. visible/enabled) when first reported and of state changes to detect effects of actions. Signed-off-by: thc202 --- CHANGELOG.md | 3 +- source/ContentScript/index.ts | 88 +++++- source/ContentScript/util.ts | 54 +++- source/types/ReportedModel.ts | 15 +- test/ContentScript/integrationTests.test.ts | 266 +++++++++++++++++- test/ContentScript/unitTests.test.ts | 28 +- test/ContentScript/utils.ts | 14 +- .../webpages/interactableTest.html | 48 ++++ 8 files changed, 481 insertions(+), 35 deletions(-) create mode 100644 test/ContentScript/webpages/interactableTest.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 6998a3c..fb0a705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,10 @@ All notable changes to the full browser extension will be documented in this fil The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## Umreleased +## Unreleased ### Added - Report `textarea` and `select` elements. +- Report interactable state of elements. ## 0.1.9 - 2026-05-15 diff --git a/source/ContentScript/index.ts b/source/ContentScript/index.ts index c01a374..0162f6f 100644 --- a/source/ContentScript/index.ts +++ b/source/ContentScript/index.ts @@ -23,8 +23,10 @@ import { ReportedObject, ReportedStorage, ReportedEvent, + InteractableState, } from '../types/ReportedModel'; import Recorder from './recorder'; +import {hasPointerStyle, getInteractableState} from './util'; import { IS_FULL_EXTENSION, LOCAL_STORAGE, @@ -43,6 +45,10 @@ const reportedObjects = new Set(); const reportedEvents: {[key: string]: ReportedEvent} = {}; +const elementInteractableState = new WeakMap(); + +let trackedElementRefs: WeakRef[] = []; + const recorder = new Recorder(); function reportStorage( @@ -141,13 +147,25 @@ function reportEvent(event: ReportedEvent): void { } } +function trackInteractableElement(re: ReportedElement, element: Element): void { + const state = getInteractableState(element); + re.interactable = state; + + if (!elementInteractableState.has(element)) { + elementInteractableState.set(element, state); + trackedElementRefs.push(new WeakRef(element)); + } +} + function reportPageForms( doc: Document, fn: (re: ReportedObject) => void ): void { const url = window.location.href; Array.prototype.forEach.call(doc.forms, (form: HTMLFormElement) => { - fn(new ReportedElement(form, url)); + const re = new ReportedElement(form, url); + trackInteractableElement(re, form); + fn(re); }); } @@ -159,7 +177,9 @@ function reportPageLinks( Array.prototype.forEach.call( doc.links, (link: HTMLAnchorElement | HTMLAreaElement) => { - fn(new ReportedElement(link, url)); + const re = new ReportedElement(link, url); + trackInteractableElement(re, link); + fn(re); } ); } @@ -170,7 +190,9 @@ function reportElements( ): void { const url = window.location.href; Array.prototype.forEach.call(collection, (element: Element) => { - fn(new ReportedElement(element, url)); + const re = new ReportedElement(element, url); + trackInteractableElement(re, element); + fn(re); }); } @@ -199,9 +221,10 @@ function reportPointerElements( tagName !== 'a' && element instanceof Element ) { - const compStyles = window.getComputedStyle(element, 'hover'); - if (compStyles.getPropertyValue('cursor') === 'pointer') { - fn(new ReportedElement(element, url)); + if (hasPointerStyle(element)) { + const re = new ReportedElement(element, url); + trackInteractableElement(re, element); + fn(re); } } }); @@ -275,6 +298,59 @@ function enableExtension(): void { subtree: true, }); + const attributeObserver = new MutationObserver(() => { + withZapEnableSetting(async () => { + const pendingAnimations = document.getAnimations().map((a) => a.finished); + if (pendingAnimations.length > 0) { + await Promise.race([ + Promise.all(pendingAnimations), + new Promise((resolve) => { + setTimeout(resolve, 2000); + }), + ]); + } + + const alive: WeakRef[] = []; + for (const ref of trackedElementRefs) { + const el = ref.deref(); + if (el) { + alive.push(ref); + const newState = getInteractableState(el); + const prevState = elementInteractableState.get(el); + if ( + prevState !== undefined && + (prevState.visible !== newState.visible || + prevState.enabled !== newState.enabled || + prevState.pointer !== newState.pointer) + ) { + elementInteractableState.set(el, newState); + const re = new ReportedElement( + el, + window.location.href, + 'nodeChanged' + ); + re.interactable = newState; + sendObjectToZAP(re); + } + } + } + trackedElementRefs = alive; + }); + }); + attributeObserver.observe(document, { + attributes: true, + attributeFilter: [ + 'aria-disabled', + 'aria-hidden', + 'class', + 'disabled', + 'hidden', + 'href', + 'style', + ], + subtree: true, + }); + setInterval(() => { // Have to poll to pickup storage changes in a timely fashion reportAllStorage(); diff --git a/source/ContentScript/util.ts b/source/ContentScript/util.ts index 521ce39..f1099c2 100644 --- a/source/ContentScript/util.ts +++ b/source/ContentScript/util.ts @@ -19,6 +19,7 @@ */ import {ElementLocator} from '../types/zestScript/ZestStatement'; import {ZAP_FLOATING_DIV} from '../utils/constants'; +import {InteractableState} from '../types/ReportedModel'; const dynamicClassElements = new WeakSet(); const inputClassSnapshots = new WeakMap(); @@ -173,4 +174,55 @@ function getPath( return path; } -export {getPath, markClassAsDynamic, snapshotInputClass}; +function hasPointerStyle(el: Element): boolean { + const compStyles = window.getComputedStyle(el, 'hover'); + return compStyles.getPropertyValue('cursor') === 'pointer'; +} + +function isHiddenByParent(el: HTMLElement): boolean { + let node: HTMLElement | null = el; + while (node) { + const nodeStyle = node.ownerDocument.defaultView?.getComputedStyle(node); + if ( + nodeStyle?.display === 'none' || + nodeStyle?.opacity === '0' || + (nodeStyle as CSSStyleDeclaration & {contentVisibility?: string}) + ?.contentVisibility === 'hidden' + ) { + return true; + } + node = node.parentElement; + } + return false; +} + +function getInteractableState(el: Element): InteractableState { + if (!(el instanceof HTMLElement)) { + return {visible: false, enabled: false, pointer: false}; + } + + const enabled = + !(el as HTMLElement & {disabled?: boolean}).disabled && + el.getAttribute('aria-disabled') !== 'true'; + + const s = window.getComputedStyle(el); + const visible = + el.getAttribute('aria-hidden') !== 'true' && + s.display !== 'none' && + s.visibility !== 'hidden' && + s.visibility !== 'collapse' && + s.opacity !== '0' && + (el.offsetWidth > 0 || el.offsetHeight > 0) && + !isHiddenByParent(el); + const pointer = hasPointerStyle(el); + + return {visible, enabled, pointer}; +} + +export { + getPath, + hasPointerStyle, + getInteractableState, + markClassAsDynamic, + snapshotInputClass, +}; diff --git a/source/types/ReportedModel.ts b/source/types/ReportedModel.ts index ea25fb8..cbcbded 100644 --- a/source/types/ReportedModel.ts +++ b/source/types/ReportedModel.ts @@ -17,6 +17,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +interface InteractableState { + visible: boolean; + enabled: boolean; + pointer: boolean; +} + class ReportedObject { public timestamp: number; @@ -100,9 +106,11 @@ class ReportedElement extends ReportedObject { public formId: number | null; - public constructor(element: Element, url: string) { + public interactable: InteractableState | undefined; + + public constructor(element: Element, url: string, type = 'nodeAdded') { super( - 'nodeAdded', + type, element.tagName, element.id, element.nodeName, @@ -146,7 +154,7 @@ class ReportedElement extends ReportedObject { public toShortString(): string { return JSON.stringify(this, function replacer(k: string, v: string) { - if (k === 'timestamp') { + if (k === 'timestamp' || k === 'interactable') { // No point reporting the same element lots of times return undefined; } @@ -177,3 +185,4 @@ class ReportedEvent { } export {ReportedElement, ReportedObject, ReportedStorage, ReportedEvent}; +export type {InteractableState}; diff --git a/test/ContentScript/integrationTests.test.ts b/test/ContentScript/integrationTests.test.ts index 1b7c263..0e16642 100644 --- a/test/ContentScript/integrationTests.test.ts +++ b/test/ContentScript/integrationTests.test.ts @@ -114,7 +114,8 @@ function integrationTests( 'A', 'http://localhost:1801/webpages/integrationTest.html', 'http://localhost:1801/webpages/integrationTest.html#test', - 'Link' + 'Link', + {interactable: {visible: true, enabled: true, pointer: true}} ), reportObject( 'localStorage', @@ -1008,7 +1009,8 @@ function integrationTests( 'A', 'http://localhost:1801/webpages/integrationTest.html', 'http://localhost:1801/webpages/integrationTest.html#test', - 'Link' + 'Link', + {interactable: {visible: true, enabled: true, pointer: true}} ), reportEvent( 'domMutation', @@ -1021,7 +1023,8 @@ function integrationTests( 'A', 'http://localhost:1801/webpages/integrationTest.html', 'https://www.example.com/', - 'Test link' + 'Test link', + {interactable: {visible: true, enabled: true, pointer: true}} ), ]); }); @@ -1047,7 +1050,8 @@ function integrationTests( 'IMG', 'http://localhost:1801/webpages/pointerelements.html', undefined, - '' + '', + {interactable: {visible: true, enabled: true, pointer: true}} ), reportObject( 'nodeAdded', @@ -1056,7 +1060,8 @@ function integrationTests( 'DIV', 'http://localhost:1801/webpages/pointerelements.html', undefined, - 'Page 1' + 'Page 1', + {interactable: {visible: true, enabled: true, pointer: true}} ), reportObject( 'nodeAdded', @@ -1065,7 +1070,8 @@ function integrationTests( 'DIV', 'http://localhost:1801/webpages/pointerelements.html', undefined, - 'Page 2' + 'Page 2', + {interactable: {visible: true, enabled: true, pointer: true}} ), ]); }); @@ -1086,7 +1092,8 @@ function integrationTests( 'TEXTAREA', `http://localhost:${_HTTPPORT}/webpages/interactions.html`, undefined, - 'Existing text' + 'Existing text', + {interactable: {visible: true, enabled: true, pointer: false}} ) ); }); @@ -1107,7 +1114,8 @@ function integrationTests( 'SELECT', `http://localhost:${_HTTPPORT}/webpages/interactions.html`, undefined, - 'volvo' + 'volvo', + {interactable: {visible: true, enabled: true, pointer: false}} ) ); expect(actualData).toContainEqual( @@ -1118,7 +1126,8 @@ function integrationTests( 'SELECT', `http://localhost:${_HTTPPORT}/webpages/interactions.html`, undefined, - 'v01' + 'v01', + {interactable: {visible: true, enabled: true, pointer: false}} ) ); }); @@ -1306,6 +1315,245 @@ function integrationTests( expect(await wd.getPageSource()).toContain('Summary'); } ); + + test('Should report non interactable for disabled/hidden elements', async () => { + // Given + await enableZapEvents(server, driver); + const wd = await driver.getWebDriver(); + const url = `http://localhost:${_HTTPPORT}/webpages/interactableTest.html`; + // When + await wd.get(url); + await eventsProcessed(); + // Then + expect(actualData).toEqual( + expect.arrayContaining([ + reportObject( + 'nodeAdded', + 'BUTTON', + 'btn-aria-disabled', + 'BUTTON', + url, + undefined, + 'aria disabled', + {interactable: {visible: true, enabled: false, pointer: false}} + ), + reportObject( + 'nodeAdded', + 'INPUT', + 'input-disabled', + 'INPUT', + url, + undefined, + '', + { + interactable: {visible: true, enabled: false, pointer: false}, + tagType: 'text', + } + ), + reportObject( + 'nodeAdded', + 'BUTTON', + 'btn-aria-hidden', + 'BUTTON', + url, + undefined, + 'aria hidden', + {interactable: {visible: false, enabled: true, pointer: false}} + ), + reportObject( + 'nodeAdded', + 'BUTTON', + 'btn-css-display', + 'BUTTON', + url, + undefined, + 'css display', + {interactable: {visible: false, enabled: true, pointer: false}} + ), + reportObject( + 'nodeAdded', + 'BUTTON', + 'btn-css-opacity', + 'BUTTON', + url, + undefined, + 'css opacity', + {interactable: {visible: false, enabled: true, pointer: false}} + ), + reportObject( + 'nodeAdded', + 'BUTTON', + 'btn-css-visibility', + 'BUTTON', + url, + undefined, + 'css visibility', + {interactable: {visible: false, enabled: true, pointer: false}} + ), + reportObject( + 'nodeAdded', + 'BUTTON', + 'btn-css-offset', + 'BUTTON', + url, + undefined, + 'css offset', + {interactable: {visible: false, enabled: true, pointer: false}} + ), + reportObject( + 'nodeAdded', + 'BUTTON', + 'btn-css-transition', + 'BUTTON', + url, + undefined, + 'css transition', + {interactable: {visible: false, enabled: true, pointer: false}} + ), + reportObject( + 'nodeAdded', + 'BUTTON', + 'btn-pointer', + 'BUTTON', + url, + undefined, + 'pointer style', + {interactable: {visible: true, enabled: false, pointer: true}} + ), + reportObject( + 'nodeAdded', + 'BUTTON', + 'btn-in-transition-parent', + 'BUTTON', + url, + undefined, + 'child of transition', + {interactable: {visible: false, enabled: true, pointer: false}} + ), + ]) + ); + }); + + test('Should send nodeChanged when elements become interactable', async () => { + // Given + await enableZapEvents(server, driver); + const wd = await driver.getWebDriver(); + const url = `http://localhost:${_HTTPPORT}/webpages/interactableTest.html`; + await wd.get(url); + await eventsProcessed(); + actualData.length = 0; + // When + await wd.findElement(By.id('enable-btn')).click(); + await eventsProcessed(); + // Then + expect(actualData).toEqual( + expect.arrayContaining([ + reportObject( + 'nodeChanged', + 'BUTTON', + 'btn-aria-disabled', + 'BUTTON', + url, + undefined, + 'aria disabled', + {interactable: {visible: true, enabled: true, pointer: false}} + ), + reportObject( + 'nodeChanged', + 'INPUT', + 'input-disabled', + 'INPUT', + url, + undefined, + '', + { + interactable: {visible: true, enabled: true, pointer: false}, + tagType: 'text', + } + ), + reportObject( + 'nodeChanged', + 'BUTTON', + 'btn-aria-hidden', + 'BUTTON', + url, + undefined, + 'aria hidden', + {interactable: {visible: true, enabled: true, pointer: false}} + ), + reportObject( + 'nodeChanged', + 'BUTTON', + 'btn-css-display', + 'BUTTON', + url, + undefined, + 'css display', + {interactable: {visible: true, enabled: true, pointer: false}} + ), + reportObject( + 'nodeChanged', + 'BUTTON', + 'btn-css-opacity', + 'BUTTON', + url, + undefined, + 'css opacity', + {interactable: {visible: true, enabled: true, pointer: false}} + ), + reportObject( + 'nodeChanged', + 'BUTTON', + 'btn-css-visibility', + 'BUTTON', + url, + undefined, + 'css visibility', + {interactable: {visible: true, enabled: true, pointer: false}} + ), + reportObject( + 'nodeChanged', + 'BUTTON', + 'btn-css-offset', + 'BUTTON', + url, + undefined, + 'css offset', + {interactable: {visible: true, enabled: true, pointer: false}} + ), + reportObject( + 'nodeChanged', + 'BUTTON', + 'btn-css-transition', + 'BUTTON', + url, + undefined, + 'css transition', + {interactable: {visible: true, enabled: true, pointer: false}} + ), + reportObject( + 'nodeChanged', + 'BUTTON', + 'btn-pointer', + 'BUTTON', + url, + undefined, + 'pointer style', + {interactable: {visible: true, enabled: true, pointer: true}} + ), + reportObject( + 'nodeChanged', + 'BUTTON', + 'btn-in-transition-parent', + 'BUTTON', + url, + undefined, + 'child of transition', + {interactable: {visible: true, enabled: true, pointer: false}} + ), + ]) + ); + }); } describe('Chrome Integration Test', () => { diff --git a/test/ContentScript/unitTests.test.ts b/test/ContentScript/unitTests.test.ts index f384ba1..393dbad 100644 --- a/test/ContentScript/unitTests.test.ts +++ b/test/ContentScript/unitTests.test.ts @@ -115,10 +115,10 @@ test('Report standard page links', () => { // Then expect(mockFn.mock.calls.length).toBe(2); expect(mockFn.mock.calls[0][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"A","id":"","nodeName":"A","url":"http://localhost/","href":"https://www.example.com/1","text":"link1"}' + '{"type":"nodeAdded","tagName":"A","id":"","nodeName":"A","url":"http://localhost/","href":"https://www.example.com/1","text":"link1","interactable":{"visible":false,"enabled":false,"pointer":false}}' ); expect(mockFn.mock.calls[1][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"A","id":"","nodeName":"A","url":"http://localhost/","href":"https://www.example.com/2","text":"link2"}' + '{"type":"nodeAdded","tagName":"A","id":"","nodeName":"A","url":"http://localhost/","href":"https://www.example.com/2","text":"link2","interactable":{"visible":false,"enabled":false,"pointer":false}}' ); }); @@ -135,10 +135,10 @@ test('Report area page links', () => { // Then expect(mockFn.mock.calls.length).toBe(2); expect(mockFn.mock.calls[0][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"AREA","id":"","nodeName":"AREA","url":"http://localhost/","href":"https://www.example.com/1","text":""}' + '{"type":"nodeAdded","tagName":"AREA","id":"","nodeName":"AREA","url":"http://localhost/","href":"https://www.example.com/1","text":"","interactable":{"visible":false,"enabled":false,"pointer":false}}' ); expect(mockFn.mock.calls[1][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"AREA","id":"","nodeName":"AREA","url":"http://localhost/","href":"https://www.example.com/2","text":""}' + '{"type":"nodeAdded","tagName":"AREA","id":"","nodeName":"AREA","url":"http://localhost/","href":"https://www.example.com/2","text":"","interactable":{"visible":false,"enabled":false,"pointer":false}}' ); }); @@ -169,10 +169,10 @@ test('Report page forms', () => { // Then expect(mockFn.mock.calls.length).toBe(2); expect(mockFn.mock.calls[0][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"FORM","id":"form1","nodeName":"FORM","url":"http://localhost/","text":"Content1","formId":-1}' + '{"type":"nodeAdded","tagName":"FORM","id":"form1","nodeName":"FORM","url":"http://localhost/","text":"Content1","formId":-1,"interactable":{"visible":false,"enabled":false,"pointer":false}}' ); expect(mockFn.mock.calls[1][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"FORM","id":"form2","nodeName":"FORM","url":"http://localhost/","text":"Content2","formId":-1}' + '{"type":"nodeAdded","tagName":"FORM","id":"form2","nodeName":"FORM","url":"http://localhost/","text":"Content2","formId":-1,"interactable":{"visible":false,"enabled":false,"pointer":false}}' ); }); @@ -200,10 +200,10 @@ test('Report node elements', () => { // Then expect(mockFn.mock.calls.length).toBe(2); expect(mockFn.mock.calls[0][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"INPUT","id":"input1","nodeName":"INPUT","url":"http://localhost/","text":"","tagType":"text","formId":-1}' + '{"type":"nodeAdded","tagName":"INPUT","id":"input1","nodeName":"INPUT","url":"http://localhost/","text":"","tagType":"text","formId":-1,"interactable":{"visible":false,"enabled":true,"pointer":false}}' ); expect(mockFn.mock.calls[1][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"INPUT","id":"input2","nodeName":"INPUT","url":"http://localhost/","text":"","tagType":"text","formId":-1}' + '{"type":"nodeAdded","tagName":"INPUT","id":"input2","nodeName":"INPUT","url":"http://localhost/","text":"","tagType":"text","formId":-1,"interactable":{"visible":false,"enabled":true,"pointer":false}}' ); }); @@ -257,22 +257,22 @@ test('Reported page loaded', () => { // Then expect(mockFn.mock.calls.length).toBe(8); expect(mockFn.mock.calls[0][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"A","id":"","nodeName":"A","url":"http://localhost/","href":"https://www.example.com/1","text":"link1"}' + '{"type":"nodeAdded","tagName":"A","id":"","nodeName":"A","url":"http://localhost/","href":"https://www.example.com/1","text":"link1","interactable":{"visible":false,"enabled":false,"pointer":false}}' ); expect(mockFn.mock.calls[1][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"AREA","id":"","nodeName":"AREA","url":"http://localhost/","href":"https://www.example.com/1","text":""}' + '{"type":"nodeAdded","tagName":"AREA","id":"","nodeName":"AREA","url":"http://localhost/","href":"https://www.example.com/1","text":"","interactable":{"visible":false,"enabled":false,"pointer":false}}' ); expect(mockFn.mock.calls[2][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"FORM","id":"form1","nodeName":"FORM","url":"http://localhost/","text":"FormContent","formId":-1}' + '{"type":"nodeAdded","tagName":"FORM","id":"form1","nodeName":"FORM","url":"http://localhost/","text":"FormContent","formId":-1,"interactable":{"visible":false,"enabled":false,"pointer":false}}' ); expect(mockFn.mock.calls[3][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"INPUT","id":"input1","nodeName":"INPUT","url":"http://localhost/","text":"default","tagType":"text"}' + '{"type":"nodeAdded","tagName":"INPUT","id":"input1","nodeName":"INPUT","url":"http://localhost/","text":"default","tagType":"text","interactable":{"visible":false,"enabled":false,"pointer":false}}' ); expect(mockFn.mock.calls[4][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"INPUT","id":"submit","nodeName":"INPUT","url":"http://localhost/","text":"Submit","tagType":"submit"}' + '{"type":"nodeAdded","tagName":"INPUT","id":"submit","nodeName":"INPUT","url":"http://localhost/","text":"Submit","tagType":"submit","interactable":{"visible":false,"enabled":false,"pointer":false}}' ); expect(mockFn.mock.calls[5][0].toNonTimestampString()).toBe( - '{"type":"nodeAdded","tagName":"BUTTON","id":"button1","nodeName":"BUTTON","url":"http://localhost/","text":"Button"}' + '{"type":"nodeAdded","tagName":"BUTTON","id":"button1","nodeName":"BUTTON","url":"http://localhost/","text":"Button","interactable":{"visible":false,"enabled":false,"pointer":false}}' ); expect(mockFn.mock.calls[6][0].toNonTimestampString()).toBe( '{"type":"localStorage","tagName":"","id":"lsKey","nodeName":"","url":"http://localhost/","text":"value1"}' diff --git a/test/ContentScript/utils.ts b/test/ContentScript/utils.ts index 73bfe48..4326e08 100644 --- a/test/ContentScript/utils.ts +++ b/test/ContentScript/utils.ts @@ -89,7 +89,11 @@ export function reportObject( nodeName: string, url: string, href: string | undefined, - text: string + text: string, + extras?: { + tagType?: string; + interactable?: {visible: boolean; enabled: boolean; pointer: boolean}; + } ): object { const data = { action: {action: 'reportObject'}, @@ -103,6 +107,8 @@ export function reportObject( url, href, text, + tagType: extras?.tagType, + interactable: extras?.interactable, }, apikey: 'not set', }, @@ -110,6 +116,12 @@ export function reportObject( if (href === undefined) { delete data.body.objectJson.href; } + if (extras?.tagType === undefined) { + delete data.body.objectJson.tagType; + } + if (extras?.interactable === undefined) { + delete data.body.objectJson.interactable; + } return data; } diff --git a/test/ContentScript/webpages/interactableTest.html b/test/ContentScript/webpages/interactableTest.html new file mode 100644 index 0000000..67f86c9 --- /dev/null +++ b/test/ContentScript/webpages/interactableTest.html @@ -0,0 +1,48 @@ + + + + Interactable Test + + + + + + + + + + + +
+ +
+ + +
+ +
+ + + + +