One of the promises of HTML5 is to enable cross-platform apps; meaning that you can write one app that runs on multiple platforms (using a single source code base). This is the ideal for HTML5 apps; the reality is that HTML5 web apps are mostly cross-platform and sometimes require conditional code to deal with platform-specific quirks and differences.
In addition, not all HTML5 web app platforms are Apache* Cordova* platforms that use Cordova APIs. By using feature detection it is possible to build a cross-platform web app that runs on Cordova platforms as well as those that support other JavaScript APIs. Such an app uses the appropriate API for each platform.
Building Packaged Mobile Web Apps
This discussion will focus on packaged web apps, meaning HTML5 web apps that have been packaged so they can be downloaded, installed and run (executed) on a portable or mobile platform. Examples of platforms that support packaged web apps include:
- Chrome* OS
- Firefox* OS
- Tizen*
- Ubuntu* Mobile
- Cordova* (aka PhoneGap*) on Android, iOS, Windows 8, et al
In its most common form an HTML5 packaged web app is a ZIP file (with an extension other than .zip
) containing a platform-specific manifest file that describes the application’s name, icon, system requirements, permissions and any other attributes relevant to the platform. Most importantly, this ZIP file contains the HTML5 source files (JavaScript, CSS and HTML) and assets (images, fonts, etc.) that constitute your web app.
A web app package that has been loaded and installed onto a device runs inside that device’s native WebView when it executes (when it is run). For a quick summary of what is meant by running an HTML5 web app in a WebView, please read When is an HTML5 Web App a WebView App?
Platform Detection and Feature Detection
There are two methods available to deal with detecting platform API differences that might affect the execution and functionality of your cross-platform web app: feature detection and platform detection (aka browser detection).
Feature detection is very useful for identifying at runtime the lack of, or availability of, key HTML5 functionality (usually JavaScript APIs). This technique can be used to conditionally implement backup features, or simply eliminate optional app features, when the necessary HTML5 APIs are not available. For example, detecting that a platform does not support WebRTC might mean that your app can only provide text-based chat instead of a video-chat feature.
Platform (or browser) detection is the easiest way to deal with Cordova API quirks and differences. Unfortunately, even within a collection of devices that are members of the same platform you may find differences that cause problems for your app. Usually this is due to manufacturer-specific differences or web runtime (aka WebView) version differences (especially on the Android platform). So relying only on platform detection is not a reliable solution, you need to use feature detection to be sure the API exists, even once you know the platform.
In the real world of HTML5 web app development you need to use a combination of feature and platform detection. Feature and platform detection is described quite well in these two articles:
In the articles linked above the discussion is about building cross-browser web apps. This technique can also be applied to building cross-platform mobile web apps. There are some additional considerations for Cordova mobile web apps that are worth learning about to make the most of these techniques.
Using Feature and Platform Detection with Cordova Apps
There are a variety of conditions where feature and platform detection can help you write better cross-platform code:
- detecting HTML5 features (as described in the linked articles above)
- dealing with Cordova API quirks (esp. those that are platform-specific)
- detecting platform-specific Cordova APIs (this is unique to the Intel XDK)
Detecting HTML5 Features
It is important to understand that many of the HTML5 features that exist in modern desktop and mobile browsers do not exist in the embedded WebViews in which your packaged web app executes (see When is an HTML5 Web App a WebView App? for more information).
For example, say you want to use the performance counter to accurately measure timing of events, you will find that many older devices do not support this feature. So detecting the platform is not very useful, you need to detect the feature and provide a backup plan if the feature is unavailable.
// Use perf counter if available, otherwise, msecs since 1970
if( window.performance && performance.now ) {
timeStamp = function() { return performance.now().toFixed(3) ; } ;
} else {
timeStart = Date.now() ; // ~zero ref for elapsed time
timeStamp = function() { return (Date.now() - timeStart) ; } ;
}
Dealing with Cordova API Quirks
When you review the documentation for many Cordova APIs you will find that some features of the API have quirks that are unique to the underlying platform. In this case, you may need to modify how you call the API methods or interpret returned results, as a function of the platform.
In the following example we are setting up the path to a WAV file that will be passed to the Cordova Media API for playback. In this case, the pathname is platform-specific. On a real iOS device, the relative name of the WAV file is simply prefixed with a '/' character. On all other platforms the fully unqualified path to the WAV file must be prefixed to the relative name of the file.
var x = navigator.userAgent ;
var z = getWebRoot() ;
var media = "audio/bark.wav" ;
if( window.tinyHippos ) {
// if in the Emulate tab
media = z + "/" + media ;
}
else if( x.match(/(ios)|(iphone)|(ipod)|(ipad)/ig) ) {
// if on a real iOS device
media = "/" + media ;
}
else {
// everything else...
media = z + "/" + media ;
}
where getWebRoot
is defined as:
// getWebRoot() returns URI pointing to index.html
function getWebRoot() {
"use strict" ;
var path = window.location.href ;
path = path.substring( 0, path.lastIndexOf('/') ) ;
return path ;
}
In the example above, the Emulate tab is being treated as a distinct “platform.” Since the Emulate tab does not emulate a real device (it is a desktop browser masquerading as a device), it will sometimes have unique API quirks of its own (see Emulate tab limitations). The Emulate platform is detected first because testing for an iOS device first will give a false positive when we are running our app in the Emulate tab browser and simulating an iOS device.
Detecting Platform-Specific Cordova APIs
Unfortunately, not all Cordova APIs are available on all platforms. The core Cordova APIs maintained by the Apache Cordova project team are generally supported on all Cordova platforms. Other third-party plugins usually support only a few platforms; typically Android and iOS.
When you include a third-party plugin the JavaScript layer of that plugin will usually be included on all platforms for which you build, even if that plugin does not support all the platforms you are targeting. This is due to the way the Cordova build system works (whether you are using Cordova CLI to build or the Intel XDK build system). This behavior makes it difficult to use feature detection (the preferred method for dealing with missing functionality) as a way of detecting an unsupported Cordova API on a specific platform.
Fortunately, there is a way to use feature detection with Cordova plugins when building your Cordova web app with the Intel XDK build system. This technique requires that you disable or remove the plugin from those platforms the plugin does not support. Disabling a plugin on a specific platform is done via the intelxdk.config.additions.xml
file (see the examples in Adding Intel® XDK Cordova Build Options Using the Additions File for details). Preventing a plugin from being added to a specific platform build, via the intelxdk.config.additions.xml
file, allows you to then use feature detection to test for the presence of or lack of a Cordova plugin’s API.
How does this work with the Intel XDK build system if that build system uses Cordova CLI?
Each time the Intel XDK build system performs a build, it creates a CLI project from scratch, and imports the plugins specified by your project settings (including any special directives in the intelxdk.config.additions.xml
file). Thus, each platform (Android, Crosswalk, iOS, etc.) is built from a freshly created project where plugins are added at build time. This differs from using CLI directly on your local machine, where the plugins are added once, for all platforms in your project.
When might one want to use this technique?
In addition to preventing plugins from being included as part of an unsupported platform, this technique can be very useful for situations where plugin A is more suited for use on platform A and plugin B is better suited to platform B. For example, using different ad network plugins as a function of the device OS; you might want to use the Google AdMob network on Android and Crosswalk platforms but use the Apple iAd service on iOS devices.
Distinguishing a Cordova Platform from other Platforms
Remember the point above that advised against relying solely on platform detection? There are a few cases where just using platform detection is useful. Specifically, if you need to distinguish between executing your web app on a Cordova platform versus a non-Cordova platform.
For example, assume you are building an app that you want to run on Chrome OS and on multiple Cordova platforms (e.g., Android, iOS and Windows 8). The Cordova platforms will have nearly identical APIs (based on standard HTML5 and Cordova), so the differences between them can be handled using feature detection. The Chrome OS platform, however, has a significantly different set of APIs. In this case, it probably makes sense to detect the presence of the Cordova platform to distinguish between “Cordova” and “non-Cordova” APIs and then use feature detection and device platform detection to deal with any subtle differences and quirks that may exist between the Cordova platforms.
How does one detect the presence of the Cordova platform?
if( typeof window.cordova !== "undefined" )
The above test detects the presence of a global cordova object that is created by cordova.js
in the JavaScript global name space. In the typical Cordova application you must include the cordova.js
script in your index.html
file (before you perform this test) to gain access to the Cordova APIs. Since the actual cordova.js
script is only included in your web app package during the build process, you can assume that the object will not exist if your web app is never packaged for Cordova. Thus, you can safely “include” the cordova.js
script in the index.html
file of non-Cordova platforms (you will see a harmless warning message for a cordova.js
file that cannot be found on non-Cordova platform consoles).
See the xdk/init-dev.js
file in the App Designer blank template for an example of how this detection mechanism is used to initialize and start a web app in a platform-independent way. In this example an app.Ready
event is generated that is then used to signal when it is “okay to initialize my app.”
A second global object named window.Cordova
can be used to detect the presence of the Cordova subsystem, but this object is sometimes detectable only after the underlying Cordova native container framework has initialized, which can cause problems for JavaScript that runs during the Cordova native code initialization (DOM initialization and the Cordova native subsystem initialization typically occur in parallel).
Likewise, it is not easy to use the Cordova deviceready
event to facilitate this platform detection, because you must wait an indeterminate time to decide if the deviceready
event will not fire (sometimes more than five seconds). Using this approach can also result in false positives and unnecessary delays to initialize and start your web app on non-Cordova platforms.
Additional Notes Regarding Web App Packages
Two types of web app packages exist: those that are directly supported by the target platform and those that require the inclusion of a native container.
For those platforms that directly support HTML5 web apps, as a native executable, the ZIP package contains your HTML5 application source files and associated manifest and asset files. Chrome OS CRX files and Tizen WGT files are examples of packaged web apps for platforms that directly support HTML5 packaged web apps. These packaged web apps run in a native WebView. The platform knows how to load and run your HTML5 code on the platform's native WebView. These platforms do not require the assistance of a native container.
Some platforms require a special native container that is included in the packaged web app's ZIP file. That native container enables your web app to be distributed via the platform’s store and installed and run on devices, just like a native app. Android and iOS are examples of platforms that require a native container be included in the web app package. In this case a standard native package (APK and IPA) is used to distribute your HTML5 packaged web app along with a native container, because those platforms do not support direct execution of HTML5 web apps. The special platform-specific native container loads and runs your HTML5 web app on the native WebView for that platform.
Some Cordova packaged apps require the inclusion of a special native container and some do not. Using Cordova APIs does not imply that your packaged web app also includes a native container. For example Tizen and Firefox OS platforms support Cordova APIs but do not require a native container as part of the package.