HTML5: crossing boundaries with iframes

The age old problem of cross-origin communication between iframes (or other browser windows) has been solved by HTML5. However, I had not seen it in practice until I needed to use it myself.

[Background]

Browsers don’t allow communication between windows or iframes that aren’t on the same origin for major security reasons. The protocol, domain, and port number of the two origins communicating have to be the same in order for it to be allowed (same origin policy).

I needed to circumvent this policy to allow for some functionality for one of my projects. HTML5 postMessage provided the solution. There were many examples that I found which showed communication from the outer window to an inner iframe; however, since I needed the communication to go the other way around, it was unclear to me whether postMessage would work for me. After a little bit of research, I learned that the communication does work in both directions.

[Examples]

If you want to see a document window communicate with an inner iframe (on a different domain), check out http://html5demos.com/postmessage2.

The following is an example that shows an inner iframe communicating with it’s parent window. 

The outer window HTML/JS:

<html>
<head>
<script type="text/javascript">
   window.onmessage = function(e){
     // You can specify which origins to accept
     // messages from by checking e.origin
     if (e.origin === "https://someurl.com") {
       $('#message').text(e.data);
     }
   };
</script>
</head>
<body>
  <div id="message"></div>
</body>
</html>

The following is the inner iframe’s HTML/JS. This code can be served from a different origin. In this case, it would be served from https://someurl.com:

<html>
<head>
  <script type="text/javascript">
    $('#sendToParent').on('click', function() {
      var text = $(input).val();
      // The second param is the target origin
      // for your message. "*" sends it to anyone
      // who is listening.
      window.parent.postMessage(text, '*');
    });
  </script>
</head>
<body>
  <input type="text" />
  <button id="sendToParent"> Send </button>
</body>
</html>

When the user types in some text in the input field inside the inner iframe and presses the “Send” button, the outer window’s onmessage listener will fire.  As long as the message is from the correct origin (the e.origin check above), the outer window will populate the “div” with the appropriate text. It is very important to validate the sender’s origin when communicating cross-origin. If you don’t, an attacker can compromise the intended behavior of your site.

HTML5 postMessage works between browser windows as well, not just iframes. To learn how to use it to communicate between browser windows, check out https://developer.mozilla.org/en-US/docs/DOM/window.postMessage.Note that in my example above, the communication is from the inner iframe to the outer window. Browser support Information.

Advertisements

Media Queries: Things I Wish I’d Known

Originally posted on Box’s tech blog – http://tech.blog.box.com/2013/05/media-queries-things-i-wish-id-known/

One of my recent projects, which involved consideration for mobile devices, needed a decent amount of exploration around CSS media queries. After a little bit of Google searching, I expected to easily utilize media queries to solve all my responsive and mobile design woes. However, the actual experience was far less pleasant than I had hoped. I hope that, by putting all my findings in one place, I can help other engineers find the right solution faster. May my media query woes be your media query treasure.

The Basics

This post is mainly for people who already have a basic understanding of media queries but here is a quick refresher.

You can apply specific CSS rules by using an “@media” tag in your CSS file such as:

@media only screen
{
  body { width: 100%; }
}
@media only print
{ 
   body { width: 50%; }
}

Any CSS rules you enclose between the braces above will only be applied when the specified media type is detected. For example, on a normal device with a screen your content body width would be 100%; however, when the user prints the content, the “@media print” styles will be applied and the document body width would shrink to 50%. You can also add other restrictions such as width and/or height to media type.

@media only screen and (max-width: 500px)
{
  body { width: 100%; }
}

This media query now triggers when it detects a device with a screen that is also being displayed in a container that is less than or equal to 500 pixels in width. For more information on the basics of media queries, check out the W3 spec here.

Use Cases

The most common use case for media queries is to create a responsive design that will render your website as intended in any environment, regardless of the screen real estate available (screen size or browser width). However, it is not uncommon to target mobile devices specifically in order to adjust elements that do not work well on certain devices (e.g.: tiny menus that you need to click on, or any other component that is not touch-friendly). Traditionally, media queries aren’t used for this purpose, but from all the hype, I decided to give it a try.

The first thing I had to learn (the hard way) was that, depending on what you are trying to achieve, there IS a difference between just creating a responsive design and creating a specific mobile experience. For my project, I had to build a responsive design while handling mobile devices separately. I needed to remove some elements from the page that weren’t mobile friendly, not just because of the size of the screen real estate, but because the elements had poor user interaction on mobile devices (rather than being touch friendly). These elements could remain visible on small browser windows on a laptop because a user is still able to interact with them using a mouse; however, the interaction greatly suffered when the user tried using their fingers to do the same on a mobile device.

Responsive Design Tips

Creating a responsive design was simple enough — I picked a few different places (widths) where the design needed to shift and I added CSS rules to account for it. For example:

/* Default Style */
body { display: inline-block; }

/* First Design Shift */
@media only screen and (min-width: 401px) and (max-width: 700px)
{
    body { display: block; }
}

/* Second Design Shift */
@media only screen and (max-width: 400px)
{
    body { display: inline; }
}

The queries above target two specific cases. If your website is being rendered in a container such as a browser, iframe, mobile browser, or webview that is greater than 700 pixels in width, neither of the above media queries would be triggered and your normal styles would be applied. But if your site is being rendered in a smaller container, the media queries are triggered and your design would shift. In particular, if the container is anywhere between 401-700 pixels in width, only the first media query is triggered. If the container is under 400 pixels in width, only the second one is triggered. In this case, the media queries are exclusive and do not overlap, but there are cases where they might. For example:

/* Non-exclusive media queries (order DOES matters) */

@media only screen and (max-width: 400px)
{
    body { width: 200px; }
}
@media only screen and (max-width: 800px)
{
    body { width: 600px; }
}

If you wrote the above media queries in this particular order and the container rendering your content happened to be under 400 pixels in width, both of the media queries would be triggered. Additionally, since both queries are targeting the same selector (with the same hierarchy), the second rule would override the first. This is not ideal, as the CSS rule inside the first query will always be overridden.  A way to make sure you do not get trapped by the order of your queries is to keep them exclusive. By modifying the second query above and making it more restrictive, you can avoid this unexpected behavior.

/* Exclusive media queries (order does NOT matter) */

@media only screen and (max-width: 400px)
{
    body { width: 200px; }
}
@media only screen and (min-width: 401px) and (max-width: 800px)
{
    body { width: 600px; }
}

The order will no longer matter since the media queries are targeting completely exclusive widths.

Targeting mobile devices

So that was the easy part, right? The more annoying/unknown part is figuring out how to handle mobile devices differently, without affecting the beautiful responsive design you have already created. If you’ve done your research by now, you might say, “Hey, there is a media type for handheld devices, why not just use that?” Yes, you could write a media query such as “@media only handheld { }”. However, it isn’t widely supported. In fact, mobile Safari flat-out ignores it. So how do we go about targeting only mobile devices?

An acceptable approach is to make use of device-width and min-/max-device-width, rather than the typical width, min-/max-width. While the various “width” tags refer to the rendering surface width of the application running on your device, such as your browser window, the “device-width” tags refer to the width of the device itself. For example, take the following media query:

@media only screen and (max-width: 400px)
{
    body { display: none; }
}

If you were to resize your browser window to 400 pixels or less, this query would be triggered and your document body would be hidden. But if you modified that same media query to the following:

@media only screen and (max-device-width: 400px)
{
    body { display: none; }
}

When you resize your browser to 400 pixels or less, the media query would no longer be triggered and your document body would stay alive and well. This is because “device-width” does not consider the rendering area of the application displaying your content but rather the width of your device.

This helps target mobile devices as they typically have lower screen resolutions than larger devices (though that may not always be true, given the increasing real estate on mobile displays). For example, every generation of the iPhone has a device-width of 320 pixels. The above media query would trigger on your iPhone, including the iPhone 5 and its longer display, because the width of the device matches the query. For further information on “width” vs “device-width”, including why even the retina iOS devices adhere to the same device-width as their older counter-parts, here is a decent explanation. Also, check out Peter-Paul Koch’s post about the mobile viewport.

So, how do you target ALL mobile devices?

An effective approach is to sniff the user agent string of the user’s browser and add a CSS class to the content body (or load an extra style sheet). The reported user agent is not always accurate since it can be faked by a browser easily. However, this is still not very concerning since such spoofing is intentional on the user’s part and you can’t be held accountable for not handling it appropriately.

If you still want to use media queries to target mobile devices, there is a semi-solution. You can target “all” devices using the following media query:

@media only screen and (min-device-width: 320px) and (max-device-width: 768px)
{
    #header, #footer { display: none; }
}

This is essentially CSS duck typing (kind of) since this media query targets all devices that have a screen width ranging from 320 pixels to 768 pixels (encompasses iPhones, many Androids, all iPads and some other tablets). It assumes that given a certain size, the user is on a mobile device. Your laptop’s screen width is most likely greater than 768 pixels, so unless you are still using a machine from ancient times (and, at that point, seriously, get a retina display already), this media query will not be triggered on your browser. That said, if you do have 500-pixel width screen on your laptop, this media query will trigger for you.

This approach clearly has its shortcomings, as there is no guarantee that mobile displays will continue to have consistently lower resolution displays than other devices. While the above approach works well for iOS devices, which generally don’t vary in size from one and other, it will not hold up on recent Android devices with larger displays and different manufacturers.

If you want to target a specific iOS device, here are a few media queries I’ve written (slash Googled for) that work:

/* WARNING: These only work on 'most' current iOS devices but NEED to be maintained */

@media only screen and (device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 1) /* iPhone 4s and before */
,only screen and (device-width: 320px) and (device-height: 568px) and (device-aspect-ratio: 40/71) /* iPhone 5 */
,only screen and (device-width: 768px) and (device-height: 1024px) and (orientation: landscape) /* all iPads in landscape */
,only screen and (device-width: 768px) and (device-height: 1024px) and (orientation: portrait) /* all iPads in portrait */
{
    #header, #footer, #comments { display: none; }
}

If you need media queries for a specific Android device, good luck and godspeed.

General Tips

You likely noticed the use of “orientation” as part of the rules in the above media queries. For iOS and Android you can specify which orientation you are targeting. If you only want your media query to apply in portrait mode (holding your device as you would if you were calling someone) you can specify “(orientation: portrait)”. On iOS, as far as media queries are concerned, the “device-width” and “device-height” will remain the same whether you are in “portrait” or “landscape” mode, as shown in the media query above that targets all iPads. However, for Android devices, when the orientation changes, the “device-height” and “device-width” swap. For example:

@media only screen and (device-width: 320px) and (device-height: 480px)
{
    body { width: 100%; }
}

This media query would trigger on an iPhone in both orientations. However, on an Android device with the above screen resolution, this query would only trigger in portrait mode. This is because in landscape mode, the device-width changes to 480 pixels and the device-height becomes 320 pixels. Disclaimer: this was only tested on the Android native browser with a few of the Android phones I had available. It might not work the same way on Chrome or various other devices.

“Does orientation apply to my computer? How do I turn my computer into portrait mode? Turn it sideways?” Okay, okay. Maybe you weren’t asking these questions, but I sure was. On your computer’s browser, the orientation depends on your browser’s width and height. If your browser’s width is smaller than its height, it is considered to be in portrait mode. Otherwise it is in landscape mode. Orientation is not supported by all browsers, here is the full list of supported browsers.

Another thing I was perplexed by was the “only” keyword in media queries. Since there are many different types of “media” you can target, such as TVs, projectors, screen, print, etc., I thought “only” in the above cases meant to only target screens! But, NO, it is used for more than that. The “only” keyword is primarily used to handle older browsers. The “media” tag was previously used for media types rather than media queries. For example:

@media screen and (max-width: 500px)
{
  body { width: 100%; }
}

An older browser would interpret “screen and (max-width: 500px)” as just “screen”, because it understands media types but not media queries. You need to prefix the “only” keyword to avoid this behavior. For example:

@media only screen and (max-width: 500px)
{
  body { width: 100%; }
}

In this example, an older browsers will ignore your media query rather than mistake it for a media type because the “only” keyword is not part of media types (most browsers generally ignore things they don’t understand). Think of it as a <noscript> tag, if you will… kind of. A further explanation can be found here.

Conclusion

Media queries can be very useful for responsive layouts and are supported by most browsers (full list of supported browsers). But be mindful of their shortcomings while using them to target specific mobile devices. If your goal is to target mobile devices separately from your normal responsive layout, consider using user agent sniffing (especially if you need to support Android). Hopefully, we will see better support for media queries and an easier way to target mobile devices in the future. But in the interim, as new devices hit the market, you may have to update your media queries. I hope this brain dump of my experience with media queries has been helpful. I wish you the best of luck using them!