rss

How to overcome the iframe zoom limitation in iOS

Friday, March 15, 2013

The growing demand for fast prototyping of new mobile web applications is raising new challenges to mobile developers. An example, is the security limitation in iOS's Safari: iframes aren't allowed to hide content or zoom. Although the main idea is to protect the users from hidden malicious elements, it also restricts their actions as they cannot see the content with the zoom they want. This raises usability problems.

Nevertheless, there is the possibly to simulate this feature by hacking the CSS property zoom. This property controls the magnification level for the current element. The rendering effect for the element is that of a "zoom" function on a camera. The possible values are:

normal No magnification is applied. The object is rendered as it normally would be.
[number] Positive floating point number indicating a zoom factor. Numbers smaller than 1.0 indicate a “zoom out” or size reduction effect, while numbers greater than 1.0 indicate a magnifying effect.
[percentage] Positive floating point number, followed by a percentage character (“%”) which indicates a zoom factor. Percentages smaller than 100% indicate a “zoom out” or size reduction effect, while numbers greater than 100% indicate a magnifying effect.

You might want to take a look at the following CSS3 Transforms, if you want to create a cross-browser zooming mechanism:

zoom: 0.85;
-moz-transform: scale(0.85);
-moz-transform-origin: 0 0;
-o-transform: scale(0.85);
-o-transform-origin: 0 0;
-webkit-transform: scale(0.85);
-webkit-transform-origin: 0 0;

Now, we shall see an example:

div { zoom: 150% }
<div style="zoom: 150%">Hello!</div>

Of course, I would try to avoid the use of the zoom property to increase the font size. I would probably use font-size: 200%, as it is standard and supported.

The next step is to use Javascript to dynamically modify these properties. Ideally, we want to use pinch zoom gesture. The easiest solution is to use TouchSwipe, a jQuery plugin for touch and gesture-based interaction.

First, we define a JavaScript function that updates the CSS zoom property. In this case, I set the zoom to all iframe elements. However you can do it for a specific element as well.

function updateZoomProperty( pinchZoom )
{
  $("iframe").css("zoom", pinchZoom);
  $("iframe").css("-webkit-transform", "scale(" + pinchZoom + ")");
  $("iframe").css("-webkit-transform-origin", "0 0");
  $("iframe").css("-moz-transform", "scale(" + pinchZoom + ")");
  $("iframe").css("-moz-transform-origin", "0 0");
  $("iframe").css("-o-transform", "scale(" + pinchZoom + ")");
  $("iframe").css("-o-transform-origin", "0 0"); 
}

Then, we define the function that recognizes pinch zoom gesture, using TouchSwipe.

function activateSwipe(itemId, touchItemId)
{
  $('#' + itemId).contents().find(touchItemId).swipe( {
    pinchIn:function(event, direction, distance, 
                     duration, fingerCount, pinchZoom)
    {
       updateZoomProperty(pinchZoom);
    },
    pinchOut:function(event, direction, distance, 
                      duration, fingerCount, pinchZoom)
    {
       updateZoomProperty(pinchZoom);
    },
    pinchStatus:function(event, phase, direction, 
                         distance, duration, fingerCount, 
                         pinchZoom) 
    {
       console.log(
             "Pinch zoom scale " + pinchZoom +
             " <br/>Distance pinched " + distance +
             " <br/>Direction " + direction
 );
    },
    fingers:2,
    pinchThreshold:0
  });
}

Now, we can define a function to dynamically inject an iframe.

function injectFrameElement( id, itemId, url )
{
  var h = $(document).height();
   $(itemId).html(
      '<iframe id="' + id + '" width="100%" height="' + h + 'px" ' +
        'frameborder="0" marginheight="0" marginwidth="0" src="' + url + '" ' +
        ' onload="activateSwipe(\'' + id + '\',\'body\')">' + 
          '<p>Your browser does not support tampering 
               with iframe content.</p>' +
        '</iframe>'+
        '<small>Loading page ...</small>'
   );
}

And voilà! We only have to use the function in the following way.

injectFrameElement('frameviewer', '.myframe');
In this case, it will add the iframe to the following HTML element:
  <div class="myframe"></div>
This is just a draft idea. Please let me know your opinion.

Enabling HierarchyViewer on any retail phone

Sunday, January 27, 2013

The HierarchyViewer is an Android SDK tool that gives developers the ability to introspect on all aspects of an application's layout at runtime. The tool can be extremely useful for developers when debugging the view state of an application across a realm of devices.

So how does it actually work? The HierarchyViewer utilizes a service running on the device called ViewServer. When launched, ViewServer opens up a socket on local port 4939 and receives commands from a client (usually HierarchyViewer) to dump the current view state of the device. The ViewServer dispatches these calls via binder to the ViewRoot class, which serializes the view state and transmits it to the client over the socket. The WindowManagerService manages the ViewServer and provides a hook (via binder service call) to spawn the service.

Unfortunately, due to security reasons HierarchyViewer does not work on retail phones. However, it is possible to overcome this restriction by using Romain Guy's ViewServer inside your application.

The recommended way to use this API is to register activities when they are created, and to unregister them when they get destroyed:
public class MyActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         ViewServer.get(this).addWindow(this);
     }
 
     public void onDestroy() {
         super.onDestroy();
         ViewServer.get(this).removeWindow(this);
    }
 
     public void onResume() {
         super.onResume();
         ViewServer.get(this).setFocusedWindow(this);
    }
}
Don't forget to modify AndroidManifest.xml file to request INTERNET permission, otherwise you will get nasty exceptions:

As a result you will be able to use HierarchyViewer with any device:



Final remarks: Sometimes it's useful to build ViewServer as an Android library project so it can is used by several Android projects.


References

[1] Using Hierarchy Viewer. http://developer.android.com/tools/debugging/debugging-ui.html

[2] Enabling HierarchyViewer on Rooted Android Devices. 2012. apkudo.com

[3] Android Tools: Using the Hierarchy Viewer. http://mobile.tutsplus.com