Don’t Be Afraid Of Your Own Shadow(Dom)
TLDR;
Slot element can be used as a placeholder for elements that are outside of the Shadow Dom. This can be a workaround for situations where a script can’t access Shadow Dom query methods.
Why we need Shadow DOM
Shadow DOM is a technique that came together with Web Component technologies.
Web Component is a relatively new suite of technologies that allows the creation of reusable custom elements for the browser.
One of the main benefits of Shadow DOM is Encapsulation 💊, the ability to separate HTML structure, CSS and logic from the main DOM without any clashing.
It means that an element won’t be affected by the style rules or logic that the DOM already has.
Encapsulation allows us to serve content recommendations and advertisements on a publisher site (like CNN, for example) without worrying that a predefined style or logic might change the content with unexpected behaviors. We want both our Publishers and Advertisers to find our platform reliable and efficient for their goals.
We use Shadow DOM to prevent external styling and logic from “leaking” into our recommendations. It helps us to encapsulate our content and present it exactly the way we want. That way, we can also assure the publisher that they needn’t worry about clashing with their site styling rules.
Display Advertising
In display advertising, in which inventory is bought and sold on a measurable metric basis, advertisers bid on impressions, and the advertiser who wins the auction is displayed as a recommendation on our network of publisher sites.
At Outbrain, we believe It’s crucial that when we serve recommendations, we don’t affect the ad appearance or behavior. After all, the bidder is paying for their content to appear exactly as it was meant to, and using a Shadow DOM lets us achieve exactly that.
JS Trackers
Impression & Viewability Trackers 👀 are JavaScript scripts that determine if a recommendation has been rendered anywhere on the page, and if it’s visible inside the users viewport — authentication for the winning bidder that their content was served and/or viewed.
Third-party tracker vendors like Moat, DoubleVerify, IAS, etc.. are very common in advertising, and we want to provide full support for our advertisers for this feature.
The mechanism behind each third party vendor is a bit different, but the common way is to inject a script into the content container. This triggers an external script that will query elements on the page. When found, it performs an assertion that determines which element should be measured. When it matches an element, it will calculate that element’s visibility on the viewport and continuously send the data to the vendor while the user is interacting with the site.
The Problem
Most third party vendors use Document Query Methods (querySelector, querySelectorAll…) to find their script in the DOM. The problem is that those methods can’t locate elements inside Shadow DOM by default, leaving the content element as not found. Some will send inaccurate information and some will send no information at all — the main purpose of the vendor is to confirm impression and viewability data, so it’s a pretty huge miss.
We need to have an element that is outside of the Shadow DOM but still inside the content container for integrity. We don’t want to use hacks; it should be as legit as possible. We are allowing advertisers to validate our data.
If we try to query an element inside a Shadow DOM:
It fails to retrieve the element.
Solution Requirements
- Content Integrity: we can’t represent recommendations outside of the real recommendation container.
- Can’t change the core HTML Structure.
- Can’t break anything 😳
Entering…The <Slot> 🎰
The Slot element is just a placeholder inside the Shadow DOM that we can use to reference other elements that don’t exist inside the Shadow DOM (among other things it can do).
Reference element, as the name applied, is an HTML element that references another HTML element.
Elements can be placed as a sibling for the Shadow DOM Root element.
Example:
Let’s create a Shadow DOM element with a 300x300 image:
Easy. Now let’s query the image element:
And this result is null… We can’t find this element easily on the document because it’s encapsulated inside the Shadow DOM.
So, let’s try to add a slot element to the script above:
Still nothing, it’s an empty slot.
Let’s make some magic happen, starting with the creation of an element that is sibling to the shadow-root, to be a placeholder as with the dimensions of our image:
It seems that something’s happened… Let’s now try to query the trackers element:
We can now query the placeholder element that we’ve created!
Let’s take it further and test these situations with an intersection observer:
Without Slot:
With Slot:
Gotcha!
COOL! Back To Our Problem
Now, all we need to do is create a container on the same hierarchy level as the shadowRoot that will contain the third party trackers scripts.
Inside our Shadow DOM, we now put the <slot> element where there was once the original third party tracker script.
Resulting with a <slot> that refers to an element outside the Shadow DOM but inside the recommendation container — No code needed!
This markup will keep the JS trackers outside the Shadow DOM queryable! Another cool behavior we’re getting is that our whole ShadowDOM is still encapsulated and can’t be mutated.
We Did It 🎉
Now the third party scripts can find themselves and still be inside the recommendation container for legit purposes.
Viewability data comparison
When we first started to address the issue, we decided that the best way was slowly but surely — data driven development.
We measured and compared every single change until we got satisfying results.
Our main KPIs were Impressions number & Viewability percentages.
Links and References
https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot
https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
https://developers.google.com/web/fundamentals/web-components/shadowdom#workwithslots
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API