We’ve all been there. Landed on a website only to be slapped with a modal that looked something like the one below:

Hello darkness, my old friend.

For me that triggers a knee-jerk reaction: curse for giving them a pageview, close the tab, and never return. But there’s also that off case when we might actually try to get to the info behind that modal. So the next step in this situation is to bring up DevTools in order to delete the modal and overlay, and maybe some other useless things that clutter up the page while we’re at it.

This is where that page starts to dabble in evil.

We may not be able to get to the DevTools via “Inspect Element” because the context menu might be disabled. That one is easy, it only takes them one line of code:

addEventListener('contextmenu', e => e.preventDefault(), false);

But hey, no problem, that’s what keyboard shortcuts are for, right? Right… unless there’s a bit of JavaScript in the vein of the one below running:

addEventListener('keydown', e => { if(e.keyCode === 123 /* F12: Chrome, Edge dev tools */ || (e.shiftKey && e.ctrlKey && ( e.keyCode === 73 /* + I: Chrome, FF dev tools */ || e.keyCode === 67 /* + C: Chrome, FF inspect el */ || e.keyCode === 74 /* + J: Chrome, FF console */ || e.keyCode === 75 /* + K: FF web console */ || e.keyCode === 83 /* + S: FF debugger */ || e.keyCode === 69 /* + E: FF network */ || e.keyCode === 77 /* + M: FF responsive design mode */)) || (e.shiftKey && ( e.keyCode === 118 /* + F5: Firefox style editor */ || e.keyCode === 116 /* + F5: Firefox performance */)) || (e.ctrlKey && e.keyCode === 85 /* + U: Chrome, FF view source */)) { e.preventDefault(); } }, false);

Still, nothing can be done about the browser menus. That will finally bring up DevTools for us! Hooray! Delete those awful nodes and… see how the page refreshes because there’s a mutation observer watching out for such actions. Something like the bit of code below:

addEventListener('DOMContentLoaded', e => { let observer = new MutationObserver((records) => { let reload = false; records.forEach((record) => { record.removedNodes.forEach((node) => { if(node.id === 'overlay' && !validCustomer()) reload = true; }); if(reload) window.location.reload(true); }); }); observer.observe( document.documentElement, { attributes: true, childList: true, subtree: true, attributeOldValue: true, characterData: true } ); });

“This is war!” mode activated! Alright, just change the modal and overlay styles then! Except, all the styles are inline, with !important on top of that, so there’s no way we can override it all without touching that style attribute.

Oh, the !importance of it all…

Some people might remember about how 256 classes override an id, but this has changed in WebKit browsers in the meanwhile (still happens in Edge and Firefox though) and 256 IDs never could override an inline style anyway.

Well, just change the style attribute then. However, another “surprise” awaits: there’s also a bit of JavaScript watching out for attribute changes:

if(record.type === 'attributes' && (record.target.matches && record.target.matches('body, #content, #overlay')) && record.attributeName === 'style' && !validCustomer()) reload = true;

So unless there are some styles that could help us get the overlay out of the way that the person coding this awful thing missed setting inline, we can’t get past this by modifying style attributes.

The first thing to look for is the display property as setting that to none on the overlay solves all problems. Then there’s the combo of position and z-index . Even if these are set inline on the actual overlay, if they’re not set inline on the actual content below the overlay, then we have the option of setting an even higher z-index value rule for the content and bring it above the overlay. However, it’s highly unlikely anyone would miss setting these.

If offsets ( top , right , bottom , left ) aren’t set inline, they could help us shift the overlay off the page and the same goes for margin or translate properties. In a similar fashion, even if they’re set inline on the overlay, but not on the actual content and on the parent of both the overlay and the content, then we can shift this parent laterally by something like 100vw and then shift just the content back into view.

At the end of the day, even a combination of opacity and pointer-events could work.

However, if the person coding the annoying overlay didn’t miss setting any of these inline and we have that bit of JS… we cannot mess with the inline styles without making the page reload.

But wait! There’s something that can override inline values with !important , something that’s likely to be missed by many… and that’s @keyframe animations! Adding the following bit of CSS in a new or already existing style element brings the overlay and modal behind the actual content:

#overlay { animation: a 1s infinite } @keyframes a { { 0%, to { z-index: -1 } } }

However, there might be a bit of JavaScript that prevents us from adding new style or link elements (to reference an external stylesheet) or modifying already existing ones to include the above bit of CSS, as it can be seen here.

if(record.type === 'characterData' && record.target.parentNode.tagName.toLowerCase() === 'style') reload = true; record.addedNodes.forEach((node) => { if(node.matches && node.matches('style:not([data-href]), link[rel="stylesheet"]')) reload = true; });

See the Pen How to be evil (but please don’t) by Ana Tudor (@thebabydino) on CodePen.

But if there already is an external stylesheet, we could add our bit of CSS there and, save for checking for changes in a requestAnimationFrame loop, there is no way of detecting such a change (there have been talks about a style mutation observer, but currently, we don’t yet have anything of the kind).

Of course, the animation property could also be set inline to none , which would prevent us from using this workaround.

But in that case, we could still disable JavaScript and remove the overlay and modal. Or just view the page source via the browser menu. The bottom line is: if the content is already there, behind the overlay, there is absolutely no sure way of preventing access to it. But trying to anyway is a sure way to get people to curse you.

I wrote this article because I actually experienced something similar recently. I eventually got to the content. It just took more time and it made me hate whoever coded that thing.

It could be argued that these overlays are pretty efficient when it comes to stopping non-technical people, which make up the vast majority of a website’s visitors, but when they go to the trouble of trying to prevent bringing up DevTools, node deletions, style changes and the like, and pack the whole JavaScript that does this into hex on top of it all, they’re not trying to stop just non-technical people.