Skip to content

Commit

Permalink
AG-34532 Improve 'trusted-click-element' scriptlet — fix click on pag…
Browse files Browse the repository at this point in the history
…e layout elements. #437

Squashed commit of the following:

commit 722b946
Merge: 46f05ef 2b397e1
Author: jellizaveta <[email protected]>
Date:   Thu Jul 25 13:23:31 2024 +0300

    Merge branch 'fix/AG-34532' of ssh://bit.int.agrd.dev:7999/adguard-filters/scriptlets into fix/AG-34532

commit 46f05ef
Merge: 7cabd76 3b500cf
Author: jellizaveta <[email protected]>
Date:   Thu Jul 25 13:23:07 2024 +0300

    Merge branch 'master' into fix/AG-34532

commit 2b397e1
Author: Slava Leleka <[email protected]>
Date:   Wed Jul 24 18:48:07 2024 +0300

    src/scriptlets/trusted-click-element.ts edited online with Bitbucket

commit ebaa27e
Author: Slava Leleka <[email protected]>
Date:   Wed Jul 24 18:48:00 2024 +0300

    CHANGELOG.md edited online with Bitbucket

commit d986149
Author: Slava Leleka <[email protected]>
Date:   Wed Jul 24 18:47:49 2024 +0300

    src/scriptlets/trusted-click-element.ts edited online with Bitbucket

commit 7cabd76
Author: jellizaveta <[email protected]>
Date:   Wed Jul 24 13:25:06 2024 +0300

    fix the click function of already existing elements in the DOM

commit 99eb37d
Author: jellizaveta <[email protected]>
Date:   Tue Jul 23 18:40:13 2024 +0300

    fix the case when one element is added before the scriptlet and the rest after

commit 62fe248
Author: jellizaveta <[email protected]>
Date:   Tue Jul 23 17:55:04 2024 +0300

    update tests and changelog

commit 6bc3ed5
Author: jellizaveta <[email protected]>
Date:   Tue Jul 23 15:30:21 2024 +0300

    AG-34532 Improve 'trusted-click-element' scriptlet — fix click on page layout elements. #437
  • Loading branch information
jellizaveta committed Jul 25, 2024
1 parent 3b500cf commit 50cabb2
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 16 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
- new values to `set-cookie` and `set-cookie-reload` scriptlets: `essential`, `nonessential` [#436]
- `trusted-set-session-storage-item` scriptlet [#426]

### Fixed

- `trusted-click-element` scriptlet does not click on an element that is already in the DOM [#437]

[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v1.11.6...HEAD
[#435]: https://github.com/AdguardTeam/Scriptlets/issues/435
[#436]: https://github.com/AdguardTeam/Scriptlets/issues/436
[#426]: https://github.com/AdguardTeam/Scriptlets/issues/426
[#437]: https://github.com/AdguardTeam/Scriptlets/issues/437

## [v1.11.6] - 2024-07-08

Expand Down
74 changes: 60 additions & 14 deletions src/scriptlets/trusted-click-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,14 +347,12 @@ export function trustedClickElement(
};

/**
* Query all selectors from queue on each mutation
* Each selector is swapped to null in selectorsSequence on founding corresponding element
*
* We start looking for elements before possible delay is over, to avoid cases
* when delay is getting off after the last mutation took place.
* Processes a sequence of selectors, handling elements found in DOM (and shadow DOM),
* and updates the sequence.
*
* @returns {string[]} The updated selectors sequence, with fulfilled selectors set to null.
*/
const findElements = (mutations: MutationRecord[], observer: MutationObserver) => {
const fulfillAndHandleSelectors = () => {
const fulfilledSelectors: string[] = [];
selectorsSequence.forEach((selector, i) => {
if (!selector) {
Expand All @@ -376,29 +374,77 @@ export function trustedClickElement(
: selector;
});

return selectorsSequence;
};

/**
* Queries all selectors from queue on each mutation
*
* We start looking for elements before possible delay is over, to avoid cases
* when delay is getting off after the last mutation took place.
*
*/
const findElements = (mutations: MutationRecord[], observer: MutationObserver) => {
// TODO: try to make the function cleaner — avoid usage of selectorsSequence from the outer scope
selectorsSequence = fulfillAndHandleSelectors();

// Disconnect observer after finding all elements
const allSelectorsFulfilled = selectorsSequence.every((selector) => selector === null);
if (allSelectorsFulfilled) {
observer.disconnect();
}
};

const observer = new MutationObserver(throttle(findElements, THROTTLE_DELAY_MS));
observer.observe(document.documentElement, {
attributes: true,
childList: true,
subtree: true,
});
/**
* Initializes a `MutationObserver` to watch for changes in the DOM.
* The observer is set up to monitor changes in attributes, child nodes, and subtree.
* A timeout is set to disconnect the observer if no elements are found within the specified time.
*/
const initializeMutationObserver = () => {
const observer = new MutationObserver(throttle(findElements, THROTTLE_DELAY_MS));
observer.observe(document.documentElement, {
attributes: true,
childList: true,
subtree: true,
});

// Set timeout to disconnect observer if elements are not found within the specified time
setTimeout(() => observer.disconnect(), OBSERVER_TIMEOUT_MS);
};

/**
* Checks if elements are already present in the DOM.
* If elements are found, they are clicked.
* If elements are not found, the observer is initialized.
*/
const checkInitialElements = () => {
const foundElements = selectorsSequence.every((selector) => {
if (!selector) {
return false;
}
const element = queryShadowSelector(selector);
return !!element;
});
if (foundElements) {
// Click previously collected elements
fulfillAndHandleSelectors();
} else {
// Initialize MutationObserver if elements were not found initially
initializeMutationObserver();
}
};

// Run the initial check
checkInitialElements();

// If there's a delay before clicking elements, use a timeout
if (parsedDelay) {
setTimeout(() => {
// Click previously collected elements
clickElementsBySequence();
canClick = true;
}, parsedDelay);
}

setTimeout(() => observer.disconnect(), OBSERVER_TIMEOUT_MS);
}

trustedClickElement.names = [
Expand Down
60 changes: 58 additions & 2 deletions tests/scriptlets/trusted-click-element.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,37 @@ const afterEach = () => {

module(name, { beforeEach, afterEach });

test('Single element clicked', (assert) => {
test('Element already in DOM is clicked', (assert) => {
const ELEM_COUNT = 1;
// Check elements for being clicked and hit func execution
const ASSERTIONS = ELEM_COUNT + 1;
assert.expect(ASSERTIONS);
const done = assert.async();

const selectorsString = `#${PANEL_ID} > #${CLICKABLE_NAME}${ELEM_COUNT}`;
const panel = createPanel();
const clickable = createClickable(1);
panel.appendChild(clickable);

runScriptlet(name, [selectorsString]);

setTimeout(() => {
assert.ok(clickable.getAttribute('clicked'), 'Element should be clicked');
assert.strictEqual(window.hit, 'FIRED', 'hit func executed');
done();
}, 150);
});

test('Element added to DOM is clicked', (assert) => {
const ELEM_COUNT = 1;
const ASSERTIONS = ELEM_COUNT + 1;
assert.expect(ASSERTIONS);

const done = assert.async();
const selectorsString = `#${PANEL_ID} > #${CLICKABLE_NAME}${ELEM_COUNT}`;
const panel = createPanel();

runScriptlet(name, [selectorsString]);

const clickable = createClickable(1);
panel.appendChild(clickable);

Expand All @@ -78,6 +98,42 @@ test('Single element clicked', (assert) => {
}, 150);
});

test('Multiple elements clicked - one element loaded before scriptlet, rest added later', (assert) => {
const CLICK_ORDER = [1, 2, 3];
// Assert elements for being clicked, hit func execution & click order
const ASSERTIONS = CLICK_ORDER.length + 2;
assert.expect(ASSERTIONS);
const done = assert.async();

const selectorsString = createSelectorsString(CLICK_ORDER);

const panel = createPanel();

const clickables = [];
const clickable1 = createClickable(1);
panel.appendChild(clickable1);
clickables.push(clickable1);

runScriptlet(name, [selectorsString]);

const clickable2 = createClickable(2);
panel.appendChild(clickable2);
clickables.push(clickable2);

const clickable3 = createClickable(3);
panel.appendChild(clickable3);
clickables.push(clickable3);

setTimeout(() => {
clickables.forEach((clickable) => {
assert.ok(clickable.getAttribute('clicked'), 'Element should be clicked');
});
assert.strictEqual(CLICK_ORDER.join(), window.clickOrder.join(), 'Elements were clicked in a given order');
assert.strictEqual(window.hit, 'FIRED', 'hit func executed');
done();
}, 400);
});

test('Single element clicked, delay is set', (assert) => {
const ELEM_COUNT = 1;
const DELAY = 300;
Expand Down

0 comments on commit 50cabb2

Please sign in to comment.