iCab Mobile/UIWebView and implementing support for file uploads

Many web pages provide a form where users can upload photos, videos and other files, like for example on the photo community flickr.com, on social networks like facebook.com or video platforms like DailyMotion.com, on email services where you upload files to use them as attachments for emails, etc. But when visiting these sites on the iOS in Safari or in other iOS browsers, you won’t be able to upload any files. The button to select the file is disabled and not working. The reason is that the web-engine of the iOS (and Apple does not allow to use another web engine than the one that is built-in in the iOS) does not support file uploads, and the API of the web engine (UIWebView) is also extremely limited and has nothing that can be used to add support for file uploads (it seems 😉 ).

I found this situation not very satisfying. So I tried to find a way to add support for file uploads in iCab Mobile, and had some success. So iCab Mobile seems to be the only iOS Browser for iPad and iPhone which supports file uploads at the moment.

Note: With the upcoming iOS 6 release later this year, the web engine of the iOS will also add support for file uploads, though only for photos or videos. But of course if you need to upload other files than photos or videos from the photo album, or if iOS 6 cannot be installed on your device (the most important device which won’t get iOS 6 will be the 1st iPad model), you still need iCab Mobile to be able to upload files.

This blog post will explain how iCab Mobile implements the file upload support, lists all the limitations resulting from this implementation and which are caused by the iOS itself. This post will also give web authors all the information they need to make sure that file upload forms on their web pages do work with iCab Mobile and what they can do to make the user experience even better.

HTML and file uploads

In HTML, file uploads are done using standard HTML forms. These forms must use a certain encoding method for the form data (“multipart/form-data” instead of the usual default “application/x-www-form-urlencoded“), and the INPUT element with the TYPE attribute of “file” must be used to add a button to let the user select the file for the upload.

A simple form with file upload capability would look like this:

<form action="url" method="post" enctype="multipart/form-data">
	<input type="file" name="file"> 
	<input type="submit" value="Upload">
</form>

The user can click on the “file” button of the form and the browser presents a file selector where the user can select the file for the upload. When submitting the form, the browser would include the data of the file into the HTTP request and post it to the server.

The iOS web engine

In the web engine of the iOS there are several things which do not work. First of all the “file” button will be inactive and greyed out. So there’s no way to select a file in the first place. The web engine also isn’t able to add the file data to the HTTP request. This means iCab Mobile has to find a way to enable the “file” button so the user can select files or photos for the upload, and it has to include the data of the selected file into the HTTP request of the form.

iCab Mobile implementation of file uploads

You can’t get the “file” button working in the web engine of the iOS, the OS is not prepared for this. So the only option is to replace the “file” buttons by normal push buttons. In HTML these are defined this way:

<input type="button">

which means the type is “button” instead of “file”. iCab is using the method stringByEvaluatingJavaScriptFromString: of UIWebView to execute JavaScript code, which searches for “file” buttons and converts these into push buttons by changing the “type” attribute, and enables these buttons. For these push buttons iCab Mobile will also define an “onclick” handler, so whenever the button is clicked, the “onclick” handler will assign a special custom URL to location.href. In the delegate method webView:shouldStartLoadWithRequest:navigationType: of UIWebView iCab checks for this special URL and can then present the window to select a file or photo for the upload. After a file is selected, iCab executes some JavaScript code which adds a new hidden “input” element into the form whose “name” and “value” attributes will be set to some meta information about the selected file. This additional input element is required to store the information about the selected file in the form. It is necessary to create additional “input” elements in the form, because this meta information of the file must be submitted with the rest of the form, and because  “push” buttons won’t be included in the form data  this meta data can not be added to these “push” (former “file”) buttons (you’ll find more about this below).

When the form is submitted, it is impossible to directly add the file data that needs to be uploaded to HTTP request that is sent to the server. The web engine does not support file uploads and therefore is not prepared to process any files and their data. So what iCab Mobile needs to do is to intercept the HTTP request which the web engine is building when submitting the form and to modify the request by adding the file data somehow. Intercepting the HTTP requests from UIWebView can be done by implementing your own HTTP protocol handler using the NSURLProtocol class. All HTTP requests which are created using the standard Cocoa network layer (NSURLConnection) will be then re-routed through this HTTP protocol handler.

What iCab is doing in the HTTP protocol handler is to first check if the method for the HTTP request is “POST” and the encoding type is “multipart/form-data”. Only when this is the case, this request could be a file upload and iCab Mobile needs to do something more. Now the additional hidden “input” elements which iCab has inserted in the form for the selected files before will be used to find out if the request should include any files for the upload. These input elements do have a unique identifier within the name/value attributes, so by searching for these identifiers iCab Mobile can find out if  and which files needs to be uploaded. The information of these special hidden “input” fields is then removed from the request and replaced by the data of the selected files. This modified request is then send to the server. The server will therefore receive a properly formatted HTTP request which includes the data of the selected file and which no longer includes the data of the additional “input” elements. The server gets the same request from iCab Mobile it would have received from a desktop browser which built-in file upload support.

Limitations (how web authors can make sure the upload works in iCab Mobile)

This implementation works fine for all plain-vanilla HTML forms with one or more “file” buttons.

But submitting plain-vanilla HTML forms and uploading larger files will block the user interaction with the web page  until the upload is finished. The page can only update itself when the upload is finished.

This is not very user-friendly, especially when uploading larger files, like videos, where the web page can be blocked for a long time (this is not an issue of iCab Mobile or the iOS platform, this is a general issue of pure HTML forms and affects all browsers on all platforms in the same way).

Therefore many web pages try to improve the user experience for file uploads. The upload process itself is still blocked until it is finished, but it is moved to something that is invisible on the page (like an iframe) or which can run independently in the background (AJAX), so as long as the user does not leave the page, the upload can be done without blocking the interaction with the main page.

Also many web pages try to check if the form is filled out properly before it submits the form data to the server. This helps to avoid resubmitting forms because certain fields were filled out out wrong.

In principle, this all can still work in iCab Mobile, and in many cases it also does work fine. But this can also fail, depending of how the web pages do the redirection of the form submission, or how the page is checking the form.

Here are the known limitations

Some web pages are using the “file reader” API to check meta data for the file (file sizes, file types etc), or read the file data. This won’t work on the iOS platform at the moment. Also the “file” input element has attributes like “file” or “files” to access meta data of the selected files, these attributes are also not accessible (their value is “null”). All this is related to the fact the the web engine does not support file uploads and therefore won’t need these file related features. So when a web page detects that it runs on the iOS platform, it should not rely on the “file reader” API or the “file”/”files” attributes. But is still possible to check the “value” of the (former “file”) push button to get the file name of the selected file, for example to check if the file has the expected file extension.

Another issue is that iCab has to convert the “file” buttons into push buttons to make them work. So web pages which look for “input” element of type “file” to check if a file is selected will fail. But if they look for the “name” or “id” of these former “file” input elements, the input elements can be found and everything can work just fine. This is because these input elements keep their name and id and also all other attributes, only their “type” attribute has changed.

If a web page accesses certain “input” elements by static index, it can fail. This is because iCab has to add additional “input” elements for selected files.

Also invalid HTML code can cause problems, especially when nesting form/input elements within table/tr/td elements where these elements are not allowed. This is because the browser has to correct this invalid nesting when it renders the code and creates a valid HTML/DOM tree. And for wrong nesting within tables, almost all browsers just move the wrongly placed element in front of the table. So for the invalid code like this…

<table>
  <form>
    <tr>
      <td>
        <input>	
      </td>
    </tr>
  </form>
</table>

the browser would create this valid HTML/DOM tree…

<form>
</form>
<table>
  <tr>
    <td>
      <input>	
    </td>
  </tr>
</table>

As you can see, the form element is now no longer a (grand-)parent of the input element, which means the relationship between input and form element is gone. Normally this is not a big deal, because while rendering the code, the browser keeps a reference to the last form element and therefore can link the input elements to its form element. But if iCab needs to add these special hidden input elements for the selected files,  the rendering is already finished, and so the web engine can’t link the new input element to the right form element anymore. This means when submitting the form, these special hidden input elements won’t be included. iCab tries to find the correct form element for these input elements manually, which works fine in many cases, especially if there’s only one form on the page, but it can fail if there are multiple forms. So as a web author please make sure that you write valid HTML code to avoid problems.

Also if the upload form is loaded in a frame or iframe that is coming from a different domain than the main document of the web page, iCab can not modify the form to enable the file buttons. This is because UIWebView only supports access to the web page via JavaScript through the main document, and the “same-origin” policy prevents access to documents coming from a different domain.

iCab Mobile will modify the upload forms to make them work when the web page has finished loading, after the “onload” handler has fired. But some web pages do create “file” input elements or whole forms only when needed and the user has clicked on a certain button. So the upload form is not yet available when iCab searches for these forms. This means the “file” buttons of the forms which are created later remain disabled. This problem could be solved by checking for newly created “file” input elements every second or so, but this could also be a big performance issue because searching the whole code of the web page can be expensive, especially on larger web pages. And most web pages do not even have file upload forms at all. So searching the page for “file” buttons all the time is not an option.

iCab will workaround this issue by letting the user hold down a finger for a longer time (about a second) on a disabled “file” button. This will force iCab to search and replace all “file” buttons again, so the user can then select files for the upload. This is not the best user experience, because the requirement to activate the “file” button first is nothing a user is used to do. But without the help of the web page, there’s no better way to solve this.

Web sites can give iCab hints about dynamically created INPUT elements

Update May 2013
This paragraph is still valid, but it is usually no longer necessary that web sites need to help iCab. iCab Mobile will now monitor the creation of new HTML elements and if new INPUT elements are created it will automatically check if these are file elements and therefore can convert these automatically. There might be still some rare cases where iCab fails to recognize new elements, in these cases the site can still help out like suggested in the following paragraph.

Since iCab Mobile 6.0.1 there is a way web pages can help iCab Mobile to automatically activate dynamically created “file” input elements, so the user does no longer to do this manually.

This works in the following way:

The web page needs to set a global JavaScript variable named iCabMobile_FileFieldActive and set it to 1 if (and only if) the web page can create and insert a “file” input element into a form via JavaScript after the “onload” handler has fired. If the “file” buttons are all created and inserted into the forms before the “onload” handler fires or created and added within the “onload” handler or created statically in the HTML code, this variable should not be defined at all.

  iCabMobile_FileFieldActive = 1;

If this variable is 1 when iCab first checks the web page (after the “onload” handler was executed), iCab will set this variable to 0 and will start observing the value of this variable until the user loads a new page. The web page should now set the variable to 1 each time it has created a “file” input element and added it to a form. iCab Mobile will search for all newly created “file” buttons as soon as the value of this variable was set to 1 again (and only if the value is 1). This way it is possible to activate all the “file” buttons automatically, without any performance issues. It is super-easy for web developers to add this variable and this should not have any side effects at all.

There’s even the first add-on available for the concrete 5 CMS which provides an file uploader and which supports this global variable to improve the user experience in iCab Mobile.