Tuesday, March 4, 2014

Google Maps Engine Lite 'X-Frame-Options' 'Deny' Fix

Update: Apparently something has changed in Google maps api since this original post and now it appears that the content window of the iframe has to explicitly be checked in the onload function, otherwise the function will incorrectly load the map when it has not in fact properly loaded. This has been updated in the code example below.

During a diversion with some website work, I came across an interesting issue with using Google's Map Engine Lite to embed a custom map on a website. For those of you who are unfamiliar with the Map Engine Lite, as I was, it provides a nice graphical front-end to create a Google map with custom markers and features and then easily share the map with others by just embedding the link to the map in an iframe on a website. All went as expected; create a nice custom map, embed the map on the website and done, simple and straight forward with basically no coding required.

So now the moment has come to show others the great map that has been created and BOOM! Nothing! There is just a big blank, empty space where the map should be. With a perplexed look you initially decide this must just be an isolated incident and investigate the browser console to discover that an error of "'X-Frame-Options' 'DENY'" is what is being reported as the reason the map is not being rendered. This really doesn't give any reassurance of what is going on to cause this big blank space on the website. With such a nondescript message you just defer investigating this issue, as it must just be just this person and go to show someone else the map and  BOOM! Nothing!


Now you realize, there is a major issue going on here that is causing the map to not render and leave a big blank space smack dab in the middle of the website. Of course the blame, right or wrong, will fall directly on the website owner/builder and they will either think your incompetent or just plain inept to allow such a bad design to be on your website. Something must be done to fix this as it is not just simply an isolated incident and apparently is going to happen and needs to "just work" all the time regardless!

Ironically, the same place that is discovered to be causing the root of the problem provide access to the answer of what is causing this problem. A little Googling around and it is discovered that this issue is apparently cropping up on other peoples websites, so some relief that it's not just your website but that still doesn't make this problem any less irritating. But at the very least it might just have a solution floating around since this is not as isolated as first thought.


Some deep digging reveals that the core of the issue is that the user is "partially" logged into Google and before being allowed to see the map, Google is requiring the user to log in again, even though the map is specified as public without a log in required. Furthermore, the reason we have a blank map is because of the iframe not allowing redirection to another server; the map is hosted at 'mapsengine.google.com' and the required log on is being redirected to the server 'accounts.google.com', so it just refuses to render anything because of the redirection.


The interesting thing about this problem is that no one seems to have an answer about how to fix this. Actually this was really surprising since the problem seems to happen quite a bit, there seems to be reporting of this issue and the issue appears to have even been reported to Google. Regardless, it is completely unacceptable to have a blank space on a website with no explanation of what is going on, so a fix needed to be created since it could not be found.


Oddly enough the solution to this problem is actually quite easy and straight forward with a little use of javascript. The iframe prevents any fallback behavior if the content does not render but it does generate an 'onload' event when the frame loads. Hopefully you will see that now fixing this issue is trivial. All that is needed is to use an image as a placeholder that looks like the map (a screen shot works nicely) with a link to the map on Google initially and when the iframe is loaded in the background, swap it with the map placeholder image. Amazingly straight forward but apparently not a solution that anyone has posted that I could find.


Now onto some code to demonstrate how to fix this issue. The javascript is pretty straight forward, this can simply be placed in the head section like below:


<html>

<head>
<script type="text/javascript">
/* Create a function to create the maps iframe */
function setupIframe() {
var iframe = document.createElement("iframe");
iframe.setAttribute("src", "https://mapsengine.google.com/map/embed?mid=(your_id_here)");
iframe.setAttribute("id", "_map_iframe");
iframe.style.width = 800+"px";
iframe.style.height = 375+"px";
iframe.style.visibility="hidden";
iframe.style.boxShadow="10px 10px 5px #888888";
document.getElementById("map_div").appendChild(iframe);
iframe.onload=function() {
                /* UPDATE: Apparently we need to check and make sure that we have a valid content window */
                if(this.contentWindow != null) {
                    /* Access the placeholder image, set it hidden and remove it from the DOM */
                   var placeholder = document.getElementById("_maps_placeholder");
                   placeholder.style.visibility="hidden";
                   placeholder.parentNode.removeChild(placeholder);
                  /* Set the iframe to visible */
                  this.style.visibility="visible";
        }
        };
}
/* Make sure to run the setup code when the window loads */
window.onload=setupIframe;
/* Set a timeout function to remove the iframe if it does not load (if desired) */
window.setTimeout(function () {
      var mapsFrame = document.getElementById("_map_iframe");
      if(mapsFrame.style.visibility != "visible")
              mapsFrame.parentNode.removeChild(mapsFrame);
}, 10000);

</script>


</head>

<body>
…………...
</body>
</html>

Hopefully this should be straight forward in what is happening. We create an iframe element in javascript with our map source, set its visibility to hidden, tweak a few of the style settings and add it to the document where it should inevitably be placed. Then in the iframe "onload" function, hide our map placeholder image, remove the placeholder and show the actual Google map, thus swapping out the image placeholder when/if the map loads. Next we need to add the setup function to the window to run when it loads the document content and here also setup a timeout function that can be added, if desired, to remove the iframe after a predetermined amount of time, 10 seconds in this example, to give up trying to load the map.


The html code for the actual place holder in the document would look something like this:


<div id="map_div">

<a id="_maps_placeholder" href="https://mapsengine.google.com/map/embed?mid=(your_id_here)">
<img class="BorderedImg" src="images/Map_Placeholder.jpg" alt="Google Map" width="800" height="375" />
</a>
</div>

All we have here is a div that holds a map placeholder image with a link to the Google map and where the Google map is being place via the Javascript function. Now if you noticed the actual anchor tag is what we are removing in the javascript; it can be the anchor tag or the image, it doesn't really matter which you remove, I just wanted all of the code removed. So no matter what we have "something" occupying the possibly blank space if the Google requires a log in, which the user will have an opportunity to do if the user follows the link to Google and everything will "just work" as it should.


The css used for the image, which most closely matches the styling that the final map uses is:


.BorderedImg {

  border:2px solid #999;
  border-bottom-color:#EEE;
  border-right-color:#EEE;
  box-shadow: 10px 10px 5px #888888;
}

That's it! A fairly simple solution that will provide some robust fallback behavior for the embedded map. I feel that this better matches the all around experience desired as there will always be something occupying the space for the map, even if the user has disabled javascript in their browser. It would have been much nicer, in my opinion, to be able place the anchor tag and image content inside of the <iframe></iframe> tags and have the iframe render that content if it would not render its own source. Short of that, here is a solution that accomplishes the same goal.


No comments:

Post a Comment