Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explore ways to identify changes in hidden elements, like menus #128

Open
Mr0grog opened this issue Sep 26, 2017 · 7 comments
Open

Explore ways to identify changes in hidden elements, like menus #128

Mr0grog opened this issue Sep 26, 2017 · 7 comments

Comments

@Mr0grog
Copy link
Member

Mr0grog commented Sep 26, 2017

A common problem analysts run into is changes to parts of the page that are not visible by default, such as expanding/popup menus. What are some ways to we can aid analysts in discovering those without sending them to a diff of HTML source code they may not be able to read?

Some example changes, with hidden changes in flyout menus:

If we can identify a change in the DOM, we can roughly determine whether it’s displayed based on whether it has a width and/or height (similar to jQuery’s :visible selector). One approach might be walking up the tree from any invisible changes to find the deepest one that is visible, then highlighting that with something indicating that it contains a hidden change. It’s not perfect—we can’t say where to click (or whatever) to display the changed content—but it might still be a useful hint.

Any other brilliant ideas for this?


Edited 2019-09-16 to update example links.

@Mr0grog Mr0grog added the ui label Sep 26, 2017
@lightandluck lightandluck added this to the Backlog milestone Oct 9, 2017
@stale stale bot added the stale label Jan 10, 2019
@Mr0grog
Copy link
Member Author

Mr0grog commented Jan 10, 2019

Definitely still highly relevant.

@Mr0grog Mr0grog removed this from the Backlog milestone Jan 10, 2019
@stale stale bot removed the stale label Jan 10, 2019
@Mr0grog Mr0grog added this to the Backlog milestone Jan 10, 2019
@stale stale bot added the stale label Jul 9, 2019
@stale stale bot removed the stale label Jul 9, 2019
@lightandluck
Copy link
Collaborator

Added never-stale label

@Mr0grog
Copy link
Member Author

Mr0grog commented Sep 16, 2019

While working on a different problem this afternoon, I hacked together a quick script for identifying elements with hidden content that could be used for this. It just draws a little orange dot on the top right corner of any elements with hidden children:

// Yield elements inside `root` (and including `root`) that are invisible
// (offscreen, transparent, etc.). It doesn't descend *into* the invisible
// elements, though.
function *findInvisibles (root, context = null) {
    const computed = getComputedStyle(root);
    if (computed.opacity === '0' || computed.visibility === 'hidden' || computed.display === 'none') {
        yield root;
        return;
    }
    if (!context) {
        context = {
            bodyTop: document.body.getBoundingClientRect().top
        };
    }
    const bounds = root.getBoundingClientRect();
    if (bounds.right < 0 || bounds.bottom < context.bodyTop || (bounds.height === 0 && computed.overflow === 'hidden')) {
        yield root;
        return;
    }

    const children = root.childNodes;
    const childCount = children.length;
    let hasChildren = false;
    let hasVisibleChildren = false;
    let invisibleChildren = []
    for (let i = 0; i < childCount; i++) {
        if (children[i].nodeType === Node.ELEMENT_NODE) {
            hasChildren = true;
            let innerInvisibles = findInvisibles(children[i], context);
            if (!hasVisibleChildren) {
                innerInvisibles = [...innerInvisibles];
                if (innerInvisibles[0] !== children[i]) {
                    hasVisibleChildren = true;
                    for (let invisible of invisibleChildren) yield invisible;
                }
                else {
                    invisibleChildren = invisibleChildren.concat(innerInvisibles);
                }
            }
            if (hasVisibleChildren) {
                for (let invisible of innerInvisibles) yield invisible;
            }
        }
        else if (children[i].nodeType === Node.TEXT_NODE || children[i].nodeType === Node.CDATA_SECTION_NODE) {
            if (children[i].textContent.trim()) hasVisibleChildren = true;
        }
    }
    if (hasChildren && !hasVisibleChildren) yield root;
}

// Draw an orange dot on the top-right corner of an element. The dot is
// a DOM element that lives in a body-level container (so you can easily
// manipulate all the dots together). Dots aren't hosted inside the
// element they mark so we don't have to worry about whether the element
// is a containing block (or changes between being a containing block
// and not being one).
function makeDot(element) {
    var pageBounds = document.body.getBoundingClientRect();
    var elementBounds = element.getBoundingClientRect();

    var dot = document.createElement('div');
    dot.className = 'invisble-change-indicator';
    Object.assign(dot.style, {
        boxSizing: 'border-box',
        backgroundColor: 'orange',
        borderRadius: '7px',
        border: '2px solid white',
        width: '14px',
        height: '14px',
        position: 'absolute',
        left: `${elementBounds.right - 19 - pageBounds.left}px`,
        top: `${elementBounds.top + 5 - pageBounds.top}px`,
        zIndex: '9999999999'
    });

    var dotBox = document.getElementById('wm-diff-dot-box');
    if (!dotBox) {
        dotBox = document.createElement('div');
        dotBox.id = 'wm-diff-dot-box';
        document.body.appendChild(dotBox);
    }
    dotBox.appendChild(dot);
}

// Find all the elements that have invisible elements inside them
invisibleParents = new Set();
for (let invisible of findInvisibles(document.body)) {
    // We only care if the invisible area is or contains our
    // diff insertions/deletions
    if (invisible.querySelector('.wm-diff') && !invisibleParents.has(invisible.parentNode)) {
        invisibleParents.add(invisible.parentNode);
        makeDot(invisible.parentNode);
    }
}

@edgi-govdata-archiving edgi-govdata-archiving deleted a comment from stale bot Sep 16, 2019
@edgi-govdata-archiving edgi-govdata-archiving deleted a comment from stale bot Sep 16, 2019
@Mr0grog
Copy link
Member Author

Mr0grog commented Sep 16, 2019

What probably should happen from here:

@SYU15
Copy link
Contributor

SYU15 commented Sep 17, 2019

This sounds super useful. Mind adding a screenshot to show what it looks like now?

@Mr0grog
Copy link
Member Author

Mr0grog commented Sep 17, 2019

Quick screencap from throwing the above script in the web inspector:

invisibles

@Mr0grog
Copy link
Member Author

Mr0grog commented Sep 17, 2019

Another note on the above as to why this isn’t perfect: there is an element that changed in the container that holds the whole navigation bar, and it looks like no interactions on the page will ever make that element visible. So the orange dot over “Mission” in the menu is actually for the whole menu, and indicates a change you can never actually see no matter what you do. I’m not sure there’s any feasible way to determine whether an invisible change might or might not be revealable.

For reference, the change above is: https://monitoring.envirodatagov.org/page/9328fdfb-f8ad-4212-a638-b0782cf83bfb/53ac6d02-917d-499d-82fb-7b2e643a7866..d4c76163-3cf5-4efd-a16d-bb7b27eebb28

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants