studio_title

We recently updated ShowCaster Studio to give users the ability to customise how they embed the ShowCaster player. This has become a common feature amongst some online services, however everyone seems to have their own interpretation of how to present this information to the user.

We approached this task as we do all of our user interface designs: the goal is to only present critical information to users and minimise their cognitive load. This combined with traditional UI design principles forced us to consider presenting the embed code alongside a responsive preview of the embed itself. The idea was to add a few options that would tweak the look and feel of the player and have the preview automatically update to reflect those settings. We also added tabs to differentiate between the major layouts, giving the user quick access to a variety of common embed types. The result of this design looks like this:

embedBuidler

A crucial part of the work was to make our embed player flexible in terms of its layout and presentation, however in this post I only focus on the builder and not the embed. A follow up post will touch on how we implemented a flexible embed player.

The main technical challenge of this implementation came from the interactive preview that we chose to implement as an iframe pointing to our embed player. In order to keep a consistent feel and not throw off the page layout, the iframe width should remain the same at all times but we also wanted the preview to be interactive in terms of size. This can be easily be achieved through dynamic scaling of the iframe to always fit the allocated space using CSS3.

.embedPreview iframe {
  transform-origin: top left;
  -moz-transform-origin: top left;
  -webkit-transform-origin: top left;
  -o-transform-origin: top left;
  -ms-transform-origin: top left;
}
var scale = (iframeContainer.width()) / iframe.width();
iframe.css("transform", "scale(" + scale + ")");
iframe.css("-webkit-transform", "scale(" + scale + ")");
...
iframe.css("-o-transform", data);
// Resize height of the container to fit the transform
iframeContainer.css("height", height * scale);

The main problem now came from detecting the new width and height of the iframe once its contents had been reloaded. This was slightly more complex since we cannot query the contents of an iframe on a different domain to adjust the scaling and height of the preview.

We used HTML5 messages to pass the size parameters of the iframe contents once it had finished loading.

// Code on the embed client page (embed player)
// Fire load event to parent once the document is loaded
$(window).load(function() {
  var data = new Object();
  data.type = "embedLoaded";
  data.eventId = ;
  data.contentWidth = $(document).width();
  data.contentHeight = $(document).height();
  data.layout = "";

  window.parent.postMessage(JSON.stringify(data), "*");
});

We can then listen for those messages on our Studio page and adjust the scaling and height accordingly.

// Code on the Studio page (embed buidler)
$(document).ready(function() {
 // IE8 handle
 if (typeof window.addEventListener != 'undefined') {
   // Add event listener for onLoad event of the embedded iframe
   window.addEventListener("message", onIframeLoad, false);
 } else if (typeof window.attachEvent != 'undefined') {
   window.attachEvent("onmessage", onIframeLoad);
 }
});

function onIframeLoad (pEvent) {
  var data = JSON.parse(pEvent.data);
  var width = data.contentWidth;
  var height = data.contentHeight;
  ...
}

It is important to note here that we are passing the object as a string and not simply as an extended JSON object. The reason for that being due to IE not having access to any other attributes than the event data, i.e. the string.

Hope you guys found this post useful and you now have a better idea of how to improve your embedding experience. Happy coding! :)

ShowCaster

If you are a developer dealing with iframes and cookies, then you would probably know this, but if not, then here is something to think about:

If website A (site-a.com) is hosting an iframe whose url is website b (web-b.com), and website B attempts to write a cookie – either via HTTP headers or javascript – it will work on Chrome and Firefox but not Safari (and IE actually).

*Note : The scenario described above is only true for the default settings of each browser. Unlike Chrome and Firefox, Safari by default sets its privacy setting to ‘Block cookies from third parties and advertisers. Obviously setting your privacy settings to ‘always accept cookies’ will mean that you won’t encounter the problem described above.

There are solutions / hacks that bypasses Safari’s privacy settings (like posting the iframe to itself) but sooner or later these loop holes will be fixed and closed by Apple. Perhaps this doesn’t affect you, since your application doesn’t rely on cookies (say for example a static html or image) however for the rest of us – this behaviour is bad news.

But what about HTML5′s localstorage? Why don’t we use that instead? Well, if the use of cookies is controlled by your application – i.e. you need to store “remember_me=true” locally on the client browser – then localstorage would be a good place to do this. However, that doesn’t solve all the issues as there are other types of cookies that are transparent to your application. Let’s look at 2 examples.

1) Session cookies – If you’re using a dynamic programming language that supports sessions (like jsp / php / asp / etc.) then the server will most likely insert a session cookie seamlessly on your behalf (i.e. a JSESSIONID or PHPSESSIONID).

2) Load Balancer cookies – Most load balancers (physical or cloud based) use cookies to track and manage a persistent connection. The load balacer will seamlessly add an extra cookie to the HTTP response (i.e. X-Mapping-abcdefg) which is used to determine which server to route all future requests to.

Putting the above into context, if your session-dependent web application is spread across multiple servers via a load balancer and is situated within an iframe in another domain, then any new Safari user visiting that site for the first time will have:

a) a different session id each time – since the session id cookie was blocked by Safari’s default privacy setting

b) a different server serving its requests each time – since the load balancer’s x-mapping cookie was blocked by Safari’s default privacy setting

This will then lead to unexpected behaviours like being logged out repeatedly or session data inconsistency (i.e. data across different servers is different). So how do we go about fixing this issue? Well, there are some work arounds available on the interweb. However, a workaround is only a short term solution and will work only until All Mighty Apple decides to shut it down. Therefore the best solution to this is to know the limitations of Safari (and its iOS webkit browser cousins) and then architect your applications to expect the behaviour where cookies can not be stored. For example, use localStorage where applicable, or don’t depend on the use of cookies, or don’t use iframes, or even better – inform users Safari’s not supported. :)

Let us know what you think of Safari and its default privacy settings.

@munwaikong

Development