document object has a property
domain that when accessed typically returns the full host name of the site you’re running in. Go ahead, try it. Go to http://www.flite.com in your browser and in the developer tools, execute
document.domain, and you’ll see that it returns
www.flite.com. This property is used by the browser to determine if Site A and Site B can communicate with each other.
All browsers respect a security specification known as a Same-origin Policy, which means that sites and even iFrames can only communicate with eachother if they share the same origin or host. The
document.domain property must match exactly in order for two frames to communicate with each other directly. I say “directly” because methods like using
window.postMessage allow for Cross-document messaging which I’ll cover later in the post.
What can you do with document.domain?
flite.com, but you cannot change
www.flite.com. There are some legitimate reasons why a site might make use of changing its
document.domain property. One typical use case is for supporting a plugin across a site that has many subdomains. Here’s a fictional example: let’s use flite.com. We have several subdomains where different information is kept for different audiences. There’s the main marketing site
www.flite.com, a site for developers at
developer.flite.com, and a site to get help at
support.flite.com. We also have a widget for serving videos that serves out of
video.flite.com which happens to require access to the top frame in order to expand a sharing menu. In order for the video player to work on all three sites, we will either need to inline the video player, enable Cross-Origin Resource Sharing (CORS) on our servers, or we could set the
document.domain property in all four of our sites using
document.domain = "flite.com". Magically, everything works and we can move along with our lives. This method is all well and good if you are the one controlling each of the sites and can orchestrate this sort of change, but it can wreak havoc on ads and plugins running on your site if they aren’t prepared to handle it.
So, what’s the problem?
Flite Ads are 3rd party and we don’t have control over how sites are implemented. In some implementations, our ads are served in iFrames that share the same domain as the top page where we need to expand into. Under normal circumstances the
document.domain property of the page. Sometimes it changes before the ad is loaded, sometimes shortly after the ad is loaded, or sometimes not at all. This makes it impossible to always change the
document.domain property by default. Instead, our code running inside of the iFrame must try and detect when the parent’s
document.domain property changes and update its own in lock-step so that during any future attempts to expand, our code will get the access it may need.
Flite’s secret sauce…
I mentioned 2 scenarios, the first being where
document.domain is changed before our ad is loaded. To overcome this, we simply try to determine if we can access the top window’s document object and if not, we change our local
document.domain property and try again.
Shifting document.domain using trial and error:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
The second case is what we’ve been encountering more of: the site loads a widget that is loaded after our ad is loaded which requires changing the
document.domain property. This bypasses our initial scenario leaving us with a mis-matched
document.domain property after we’ve initialized. As soon as this property changes, direct communication between frames in either direction is cut off. As I mentioned earlier, we are able to overcome this. On initialization we create a polling loop using
createInterval in the top window that waits until the
document.domain property changes from what it once was. Once the change is detected, it clears the interval and executes a call to
window.postMessage which sends a message to our frame. We send along the new value in the message and our frame is ready waiting for this message. Upon receiving it, the frame changes the local
document.domain property which restores the ability to communicate to the parent frame and vise versa!
Tracking when document.domain changes gracefully:
- JSFiddle showing how when
document.domainchanges, a child page can no longer access it. The top box is the current frame, the blue box is the child iFrame, both are loaded from the same subdomain:
- The child frame used in the previous example. It displays the current frame’s and the parent frame’s
- JSFiddle showing how a child frame can detect the change and handle it gracefully:
- The child frame with code that tracks
document.domainchanges in the parent frame:
If I could have my way, I would push everyone towards abandoning using this property. The reality is that all modern browsers support CORS and we as an industry should be embracing it. It’s one of those things that your developers want to use, but your Operations team likely needs to enable. The Web as a whole moves at glacial speeds when adopting new standards, so while it would be ideal to have everyone move to CORS, it’s not practical. I’ve shown that there are ways to work around it that make your plugin a little more resilient out in The Wild. However, if you develop a plugin or use a plugin on your site that requires changing
document.domain, please consider enabling CORS and please stop changing
document.domain! As a final note, on Flite.com and all of its subdomains, we do not change the
document.domain property, I just needed an example :-).