You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devicemap.apache.org by wk...@apache.org on 2014/07/15 09:51:41 UTC

svn commit: r1610605 [4/7] - in /incubator/devicemap/trunk/browsermap: ./ .settings/ branches/ tags/ tags/browsermap-1.3.0/ tags/browsermap-1.3.0/ci/ tags/browsermap-1.3.0/src/ tags/browsermap-1.3.0/src/main/ tags/browsermap-1.3.0/src/main/js/ tags/bro...

Added: incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/bmap.js
URL: http://svn.apache.org/viewvc/incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/bmap.js?rev=1610605&view=auto
==============================================================================
--- incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/bmap.js (added)
+++ incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/bmap.js Tue Jul 15 07:51:39 2014
@@ -0,0 +1,626 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*global BrowserMapUtil:false, Cookie:false */
+/**
+ * The BrowserMap object is used to identify the client's device group, based on JavaScript detection tests ("probes") that find out
+ * which features the client supports.
+ *
+ * @class BrowserMap
+ */
+(function(BrowserMap) {
+    'use strict';
+
+    var cookiePrefix = 'BMAP_',
+        deviceGroupCookieName = 'device',
+        deviceOverrideParameter = 'device',
+        languageOverrideParameter = 'language',
+        enableForwardingWhenCookiesDisabled = false,
+        matchRun = false,
+        languageOverride = null,
+        matchedDeviceGroups = {},
+        probes = {},
+        probeCache = {},
+        deviceGroups = {};
+    // Android 4.x phones in landscape view use 42 pixels for displaying the "soft buttons"
+    BrowserMap.THE_ANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING = 42;
+
+    BrowserMap.VERSION = '<%= pkg.version %>'; // replaced at build time by Grunt
+
+    var linkDataDevgroups = 'data-bmap-devgroups';
+    var linkcurrentVariant = 'data-bmap-currentvar';
+
+    /**
+     * Retrieves the probes Map - useful for outputting debugging information.
+     *
+     * @return {Object} an Object holding the probes and their results
+     */
+    BrowserMap.getProbingResults = function () {
+        var probingResults = {},
+            probe;
+        for (probe in probes) {
+            if (probes.hasOwnProperty(probe)) {
+                probingResults[probe] = BrowserMap.probe(probe);
+            }
+        }
+        return probingResults;
+    };
+
+    /**
+     * Initialises BrowserMap with a configuration object.
+     *
+     * @param {Object} config - a hash object with various properties that can be used to configure BrowserMap
+     * <p>
+     * The following properties can be be used:
+     *      <ol>
+     *          <li><code>config.cookiePrefix</code> - the prefix used to name cookies used throughout the detection</li>
+     *          <li><code>config.deviceGroupCookieName</code> - the name of the device group cookie (the final name will be of the form
+     *             <code>config.cookiePrefix + config.deviceGroupCookieName</code>)</li>
+     *          <li><code>config.deviceOverrideParameter</code> - the name of the GET parameter that triggers a device override</li>
+     *          <li><code>config.languageOverrideParameter</code> - the name of the GET parameter that triggers a language override</li>
+     *          <li><code>config.enableForwardingWhenCookiesDisabled</code> - if true, it will allow for all the URLs pointing to resources
+     *              from the current domain to be modified in order to include the deviceOverrideParameter; this is useful if the client
+     *              does not support cookies</li>
+     *      </ol>
+     * </p>
+     */
+    BrowserMap.config = function (config) {
+        if (config.cookiePrefix !== null) {
+            cookiePrefix = config.cookiePrefix;
+        }
+        if (config.deviceGroupCookieName !== null) {
+            deviceGroupCookieName = config.deviceGroupCookieName;
+        }
+        if (config.deviceOverrideParameter !== null) {
+            deviceOverrideParameter = config.deviceOverrideParameter;
+        }
+        if (config.languageOverrideParameter !== null) {
+            languageOverrideParameter = config.languageOverrideParameter;
+        }
+        if (config.enableForwardingWhenCookiesDisabled !== null) {
+            enableForwardingWhenCookiesDisabled = config.enableForwardingWhenCookiesDisabled;
+        }
+    };
+
+    /**
+     * Returns an Array of the alternate sites by analysing the link elements with rel='alternate' and the data-bmap-devgroups attribute
+     * not null or empty.
+     *
+     * @return {Array} an array of alternate sites defined as objects with the <code>id, href, hreflang, devgroups</code> set of
+     *                 attributes; an empty array if no alternate site is found
+     */
+    BrowserMap.getAllAlternateSites = function () {
+        var alternateSites = [],
+            links,
+            i,
+            link,
+            headElement,
+            onIE7,
+            linkHref,
+            devgroups;
+        onIE7 = navigator.appVersion.indexOf('MSIE 7') !== -1;
+        headElement = document.getElementsByTagName('head')[0];
+        if (headElement) {
+            links = headElement.getElementsByTagName('link');
+            for (i = 0; i < links.length; i++) {
+                link = links[i];
+                devgroups = link.getAttribute(linkDataDevgroups);
+                if (link.rel == 'alternate' && devgroups && devgroups !== '') {
+                    if (onIE7) {
+                        linkHref = BrowserMapUtil.Url.qualifyURL(link.href);
+                    } else {
+                        linkHref = link.href;
+                    }
+                    alternateSites.push(
+                        {'id' : link.id, 'href' : linkHref, 'hreflang' : link.hreflang, 'devgroups' : devgroups}
+                    );
+                }
+            }
+        }
+        return alternateSites;
+    };
+
+    /**
+     * <p>
+     * Looks for the best matching alternate site. The primary criterion is the number of matched device groups which also provides the
+     * score of the alternate site. More criteria can be added by providing a filtering function.
+     * </p>
+     * <p>
+     * The filtering function receives an alternate site as a parameter and it must return a boolean value if the filter matches or not. The
+     * filter is applied to alternate sites that have matched at least one device group. If the alternate site matches the filter, the total
+     * score of the alternate site will increase by 1. The alternate site's object attributes are id, href, hreflang and media.
+     * </p>
+     *
+     * @param {Array} deviceGroups - an array containing the names of the device groups for which to get the best alternate link
+     * @param {Function} filter - a callback function that acts as a filter and which must return a boolean; the callback will receive a
+     *      hash object representing an alternate site with the following attributes: "id", "href", "hreflang", "devgroups"
+     * @return {String} the alternate link that matches the most device groups matched by the client
+     */
+    BrowserMap.getAlternateSite = function (deviceGroups, filter) {
+        var alternateSites = BrowserMap.getAllAlternateSites(),
+            maxLinkScore = 0,
+            alternateSite = null,
+            i,
+            j,
+            linkScore,
+            devices;
+        for (i = 0; i < alternateSites.length; i++) {
+            linkScore = 0;
+            devices = alternateSites[i].devgroups.split(',');
+            for (j = 0; j < devices.length; j++) {
+                if (deviceGroups.indexOf(devices[j].trim()) !== -1) {
+                    linkScore++;
+                }
+            }
+            if (typeof filter == 'function' && linkScore > 0) {
+                if(filter(alternateSites[i])) {
+                    linkScore++;
+                }
+            }
+            if (linkScore > maxLinkScore) {
+                alternateSite = alternateSites[i];
+                maxLinkScore = linkScore;
+            }
+        }
+        return alternateSite;
+    };
+
+    /**
+     * Returns the current variant, if one is found.
+     *
+     * @return {Object} an object with the <code>id, href, hreflang, devgroups</code> set of attributes; <code>null</code> if the current
+     *                  variant cannot be determined
+     */
+    BrowserMap.getCurrentVariant = function () {
+        var headElement = document.getElementsByTagName('head')[0],
+            i = 0,
+            currentVariant = null,
+            currentVariantAttribute,
+            links,
+            link,
+            onIE7,
+            linkHref,
+            devgroups;
+        onIE7 = navigator.appVersion.indexOf('MSIE 7') !== -1;
+        if (headElement) {
+            links = headElement.getElementsByTagName('link');
+            for (i = 0; i < links.length; i++) {
+                link = links[i];
+                if (link.rel == 'alternate') {
+                    if (onIE7) {
+                        linkHref = BrowserMapUtil.Url.qualifyURL(link.href);
+                    } else {
+                        linkHref = link.href;
+                    }
+                    devgroups = link.getAttribute(linkDataDevgroups);
+                    currentVariantAttribute = link.getAttribute(linkcurrentVariant);
+                    if (currentVariantAttribute && currentVariantAttribute === 'true') {
+                        currentVariant = {'id' : link.id, 'href' : linkHref, 'hreflang' : link.hreflang, 'devgroups' : devgroups};
+                        break;
+                    }
+                }
+            }
+        }
+        return currentVariant;
+    };
+
+    /**
+     * Returns the defined DeviceGroups for this BrowserMap as an array in which the elements are ordered by their ranking property.
+     *
+     * @return {Array}
+     */
+    BrowserMap.getDeviceGroupsInRankingOrder = function () {
+        var dgs = [],
+            dg;
+        for (dg in deviceGroups) {
+            if (deviceGroups.hasOwnProperty(dg)) {
+                dgs.push(deviceGroups[dg]);
+            }
+        }
+        dgs.sort(function(a, b) {
+            return a.ranking - b.ranking;
+        });
+        return dgs;
+    };
+
+    /**
+     * Executes a probe that was previously added via <code>addProbe</code>. The result of the probe is cached so a second call
+     * with the same probeName will not run the probe again. You can use <code>BrowserMap.clearProbeCache()</code> to avoid that.
+     *
+     * @param {String} probeName - the name of the requested probe
+     * @return {Object} the result of the probe, or null if the probe has not been defined
+     */
+    BrowserMap.probe = function (probeName) {
+        if (!probes[probeName]) {
+            return null;
+        }
+        if (!probeCache.hasOwnProperty(probeName)) {
+            probeCache[probeName] = probes[probeName]();
+        }
+        return probeCache[probeName];
+    };
+
+    /**
+     * Starting from a currentURL, an array of device groups and an array of url selectors returns the alternate URL for the current URL.
+     *
+     * @param {String} currentURL - the current URL
+     * @param {Array} detectedDeviceGroups - the Array of detected device groups
+     * @param {Array} urlSelectors - the Array of URL selectors, in the order of their device group ranking
+     * @return {String} the specific URL for the identified device groups
+     */
+    BrowserMap.getNewURL = function (currentURL, detectedDeviceGroups, urlSelectors) {
+        var newURL = null,
+            currentVariant = BrowserMap.getCurrentVariant(),
+            alternateSite = BrowserMap.getAlternateSite(detectedDeviceGroups, function(alternateLink) {
+                if (languageOverride && alternateLink.hreflang && alternateLink.hreflang.lastIndexOf(languageOverride) === 0) {
+                    return true;
+                } else if (currentVariant && currentVariant.hreflang === alternateLink.hreflang) {
+                    return true;
+                }
+                return false;
+            }),
+            i,
+            dg,
+            parameters = BrowserMapUtil.Url.getURLParametersString(currentURL),
+            urlNoParams = currentURL.replace(parameters, '');
+        if (alternateSite) {
+            newURL = alternateSite.href;
+        }
+        if (!newURL) {
+            for (i = 0; i < detectedDeviceGroups.length; i++) {
+                dg = BrowserMap.getDeviceGroupByName(detectedDeviceGroups[i]);
+                if (dg) {
+                    newURL = dg.url;
+                    if (newURL) {
+                        break;
+                    }
+                }
+            }
+        }
+        if (!newURL) {
+            newURL = BrowserMapUtil.Url.addSelectorsToURL(urlNoParams, urlSelectors);
+        }
+        if (parameters) {
+            newURL += parameters;
+        }
+        return newURL;
+    };
+
+    /**
+     * Removes the device group override, whether it was set up by using the override cookie or just by using the specific device group
+     * override parameter.
+     */
+    BrowserMap.removeOverride = function () {
+        var oCookie = BrowserMapUtil.CookieManager.getCookie('o_' + cookiePrefix + deviceGroupCookieName),
+            currentURL = window.location.href,
+            parameters = BrowserMapUtil.Url.getURLParametersString(currentURL),
+            overrideParameter,
+            indexOfOverride;
+        if (oCookie) {
+            BrowserMapUtil.CookieManager.removeCookie(cookiePrefix + deviceGroupCookieName);
+            BrowserMapUtil.CookieManager.removeCookie(oCookie.name);
+            oCookie.name = cookiePrefix + deviceGroupCookieName;
+            oCookie.path = '/';
+            BrowserMapUtil.CookieManager.setCookie(oCookie);
+        }
+        if (parameters) {
+            overrideParameter = deviceOverrideParameter + '=' +
+                BrowserMapUtil.Url.getValueForParameter(currentURL, deviceOverrideParameter);
+            currentURL = currentURL.replace(parameters, '');
+            indexOfOverride = parameters.indexOf(overrideParameter);
+            if (indexOfOverride !== -1) {
+                if (parameters.length > indexOfOverride + overrideParameter.length) {
+                    if (parameters[indexOfOverride - 1] == '?') {
+                        parameters = parameters.replace(overrideParameter + '&', '');
+                    }
+                    else {
+                        parameters = parameters.replace('&' + overrideParameter, '');
+                    }
+                }
+                else {
+                    parameters = parameters.replace('?' + overrideParameter, '');
+                }
+            }
+            currentURL += parameters;
+        }
+        window.location = currentURL;
+    };
+
+    /**
+     * <p>Decides if the client should be forwarded to the best matching alternate link, depending on the detected device group.</p>
+     * <p>
+     * Three options are available for determining the correct representation of a page depending on the detected device group, listed in
+     * the order of their importance:
+     *      <ol>
+     *          <li>alternate links: <code>&lt;link rel="alternate" href="..." hreflang="..." media="device_groups" &gt;</code></li>
+     *          <li><code>DeviceGroup</code> level URLs (check the <code>DeviceGroup</code> objects description)</li>
+     *          <li>selector-based URLs (the device group names will be appended to the current URL: <code>index.html ->
+     *              index.tablet.html</code>)</li>
+     *      </ol>
+     * In either case <code>GET</code> parameters will be maintained.
+     */
+    BrowserMap.forwardRequest = function () {
+        var currentURL = window.location.href,
+            deviceOverride = BrowserMapUtil.Url.getValueForParameter(currentURL, deviceOverrideParameter),
+            detectedDeviceGroups = [],
+            urlSelectors = [],
+            oCookie = BrowserMapUtil.CookieManager.getCookie('o_' + cookiePrefix + deviceGroupCookieName),
+            cookie = BrowserMapUtil.CookieManager.getCookie(cookiePrefix + deviceGroupCookieName),
+            dgs = [],
+            i,
+            g,
+            registeredDeviceGroups,
+            dgName,
+            domain,
+            aTags,
+            url,
+            parameters,
+            newURL,
+            canonicalURL = BrowserMapUtil.Url.getCanonicalURL();
+        if (BrowserMap.isEnabled()) {
+            languageOverride = BrowserMapUtil.Url.getValueForParameter(currentURL, languageOverrideParameter);
+            if (deviceOverride) {
+                // override detected
+                detectedDeviceGroups = deviceOverride.split(',');
+                if (detectedDeviceGroups.length > 0) {
+                    if (BrowserMapUtil.CookieManager.cookiesEnabled()) {
+                        if (!oCookie && !cookie) {
+                            // tried to access resource directly with override parameter without passing through detection
+                            // run detection code to detect the original device groups
+                            oCookie = new Cookie();
+                            oCookie.name = 'o_' + cookiePrefix + deviceGroupCookieName;
+                            oCookie.path = '/';
+                            BrowserMap.matchDeviceGroups();
+                            for (g in matchedDeviceGroups) {
+                                if (matchedDeviceGroups.hasOwnProperty(g)) {
+                                    dgs.push(matchedDeviceGroups[g].name);
+                                }
+                            }
+                            if (deviceOverride !== dgs.join(',')) {
+                                oCookie.value = dgs.join(',');
+                                BrowserMapUtil.CookieManager.setCookie(oCookie);
+                            }
+                        }
+                        else if (!oCookie) {
+                            // detection has been performed; override detected; store original values
+                            if (cookie.value !== detectedDeviceGroups.join(',')) {
+                                cookie.name = 'o_' + cookie.name;
+                                cookie.path = '/';
+                                BrowserMapUtil.CookieManager.setCookie(cookie);
+                            }
+                        }
+                        // store the override
+                        cookie = new Cookie();
+                        cookie.name = cookiePrefix + deviceGroupCookieName;
+                        cookie.value = detectedDeviceGroups.join(',');
+                        cookie.path = '/';
+                        BrowserMapUtil.CookieManager.setCookie(cookie);
+                        if (oCookie) {
+                            if (oCookie.value == cookie.value) {
+                                BrowserMapUtil.CookieManager.removeCookie(oCookie.name);
+                            }
+                        }
+                    }
+                }
+            }
+            if (cookie !== null || deviceOverride) {
+                /**
+                 * cookie was either set by the detection code before, or we have an override;
+                 *
+                 * in either case, the matchDeviceGroups must match the detectedDeviceGroups which can come from the cookie or from the
+                 * override parameter
+                 */
+                registeredDeviceGroups = BrowserMap.getDeviceGroups();
+                if (detectedDeviceGroups.length === 0) {
+                    detectedDeviceGroups = cookie.value.split(',');
+                }
+                matchedDeviceGroups = { };
+                for (i = 0 ; i < detectedDeviceGroups.length; i++) {
+                    dgName = detectedDeviceGroups[i].trim();
+                    if (registeredDeviceGroups.hasOwnProperty(dgName)) {
+                        if (registeredDeviceGroups[dgName].isSelector) {
+                            urlSelectors.push(dgName);
+                        }
+                        matchedDeviceGroups[dgName] = registeredDeviceGroups[dgName];
+                    }
+                }
+                // add the device override parameter to links using the same domain if a device override was detected
+                if (deviceOverride && cookie === null && enableForwardingWhenCookiesDisabled) {
+                    domain = BrowserMapUtil.Url.getDomainFromURL(window.location.href);
+                    aTags = document.getElementsByTagName('a');
+                    for (i = 0; i < aTags.length; i++) {
+                        url = aTags[i].href;
+                        if (url && url.indexOf(domain) !== -1) {
+                            parameters = BrowserMapUtil.Url.getURLParametersString(url);
+                            if (parameters) {
+                                if (parameters.indexOf(languageOverrideParameter + '=' + deviceOverride) == -1) {
+                                    aTags[i].href = url + '&' + deviceOverrideParameter + '=' + deviceOverride;
+                                }
+                            }
+                            else {
+                                aTags[i].href = url + '?' + deviceOverrideParameter + '=' + deviceOverride;
+                            }
+                        }
+                    }
+                }
+            }
+            else {
+                // no override has been detected, nor a cookie has been set previous to this call
+                // perform the match and then set the cookie
+                BrowserMap.matchDeviceGroups();
+                for (g in matchedDeviceGroups) {
+                    if (matchedDeviceGroups.hasOwnProperty(g)) {
+                        if (matchedDeviceGroups[g].isSelector) {
+                            urlSelectors.push(matchedDeviceGroups[g].name);
+                        }
+                        detectedDeviceGroups.push(matchedDeviceGroups[g].name);
+                    }
+                }
+                cookie = new Cookie();
+                cookie.name = cookiePrefix + deviceGroupCookieName;
+                cookie.value = detectedDeviceGroups.join(',');
+                cookie.path = '/';
+                BrowserMapUtil.CookieManager.setCookie(cookie);
+            }
+            newURL = BrowserMap.getNewURL(currentURL, detectedDeviceGroups, urlSelectors);
+            if (currentURL !== newURL && canonicalURL !== newURL) {
+                window.location = newURL;
+            }
+        }
+    };
+
+    /**
+     * Clears the probe result cache.
+     */
+    BrowserMap.clearProbeCache = function () {
+        probeCache = { };
+    };
+
+    /**
+     * Adds a <code>DeviceGroup</code> to the <code>BrowserMap</code> object. The key which is used to store the <code>DeviceGroup</code> is
+     * represented by its name. The last <code>DeviceGroup</code> added to <code>BrowserMap</code> with the same name as a previously
+     * existing <code>DeviceGroup</code> will be the one which will be stored.
+     *
+     * @param {Object} deviceGroup - the DeviceGroup to be added to the list
+     * <p>
+     * A DeviceGroup is represented by a hash object with the following attributes:
+     *      <ol>
+     *          <li><code>Number</code> <code>ranking</code> - the order number of the DeviceGroup (when it comes to matching the
+     *              <code>DeviceGroups</code> to the client's capabilites, the defined <code>DeviceGroups</code> will be evaluated in order)
+     *          </li>
+     *          <li><code>String</code> <code>name</code> - the name of the <code>DeviceGroup</code> as one word (use camelCase if you need
+     *              more words)</li>
+     *          <li><code>Function</code> <code>testFunction</code> - the function that is to be evaluated to check if the client matches
+     *              the <code>DeviceGroup</code>; this function <strong>must</strong> return a boolean value</li>
+     *          <li><code>String</code> <code>url</code> (optional) - the URL to which a client will be forwarded in case the
+     *              <code>DeviceGroup</code> matches and the current page does not contain an alternate link to which the client can be
+     *              forwarded</li>
+     *          <li><code>String</code> <code>description</code> (optional) - the description of the <code>DeviceGroup</code></li>
+     *          <li><code>Boolean</code> <code>isSelector</code> (optional) - if present and set to <code>true</code>, the name of the
+     *              <code>DeviceGroup</code> will be used to create a URL with a selector to which BrowserMap can forward the client
+     *              (e.g. index.selector.html)</li>
+     *      </ol>
+     * </p>
+     */
+    BrowserMap.addDeviceGroup = function (deviceGroup) {
+        // validate the deviceGroup object
+        if (typeof deviceGroup.ranking !== 'number') {
+            throw new TypeError('Expected a Number for device group ' + deviceGroup.name + ' ranking');
+        }
+        if (typeof deviceGroup.testFunction !== 'function') {
+            throw new TypeError('Expected a Function for device group ' + deviceGroup.name + ' testFunction');
+        }
+        deviceGroups[deviceGroup.name] = deviceGroup;
+    };
+
+    /**
+     * Adds a probe to BrowserMap and returns the BrowserMap object (useful for chaining). The probe name must be unique. If one tries to
+     * overwrite an existing probe nothing will happen and the BrowserMap object will be returned as it was before the method was called.
+     *
+     * @param name a String containing the name of the probe
+     * @param probe a Function that returns the result of the probe
+     *
+     * @return the BrowserMap object
+     */
+    BrowserMap.addProbe = function (name, probe) {
+        if (typeof name !== 'string' || name.length < 1) {
+            throw new TypeError('invalid probe name');
+        }
+        if (typeof probe !== 'function') {
+            throw new TypeError('invalid probe function');
+        }
+        if (!probes.hasOwnProperty(name)) {
+            probes[name] = probe;
+        }
+        return BrowserMap;
+    };
+
+    /**
+     * Returns the DeviceGroups that a client has matched.
+     *
+     * @return {Object} a hash object containing the matched device groups
+     */
+    BrowserMap.getMatchedDeviceGroups = function () {
+        return matchedDeviceGroups;
+    };
+
+    /**
+     * Returns all the DeviceGroups defined for the BrowserMap object.
+     *
+     * @return {Object} a hash object containing the defined device groups for this BrowserMap instance
+     */
+    BrowserMap.getDeviceGroups = function () {
+        return deviceGroups;
+    };
+
+    /**
+     * Matches the DeviceGroups to the client's capabilities by evaluating the DeviceGroup's test function.
+     */
+    BrowserMap.matchDeviceGroups = function () {
+        var deviceGroupsArray = BrowserMap.getDeviceGroupsInRankingOrder(),
+            i,
+            deviceGroup;
+        for (i = 0; i < deviceGroupsArray.length; i++) {
+            deviceGroup = deviceGroupsArray[i];
+            if (!!deviceGroup.testFunction.call()) {
+                matchedDeviceGroups[deviceGroup.name] = deviceGroup;
+            }
+        }
+        matchRun = true;
+    };
+
+    /**
+     * Queries the list of DeviceGroups associated to this BrowserMap object using a DeviceGroup name and returns it if found.
+     *
+     * @param {String} groupName - the name of the DeviceGroup
+     * @return {Object} the DeviceGroup with the respective name, <code>null</code> otherwise
+     * @see BrowserMap.addDeviceGroup
+     */
+    BrowserMap.getDeviceGroupByName = function (groupName) {
+        return deviceGroups[groupName];
+    };
+
+    /**
+     * Checks if BrowserMap should be enabled by searching the current document for tags like <code>&lt;meta name="browsermap.enabled"
+     *  content="false"&gt;</code> in the <head> section. If such a tag exists, then this method returns <code>false</code>.
+     *
+     * @return {Boolean} false if the previously mentioned tag exists, true otherwise
+     */
+    BrowserMap.isEnabled = function () {
+        var headElement = document.getElementsByTagName('head')[0],
+            metaTags,
+            i,
+            name,
+            tag;
+        if (headElement) {
+            metaTags = headElement.getElementsByTagName('meta');
+            for (i = 0; i < metaTags.length; i++) {
+                if ((tag = metaTags[i]) && (name = tag.getAttribute('name'))) {
+                    if (name === 'browsermap.enabled' && tag.getAttribute('content') === 'false') {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    };
+
+    return BrowserMap;
+
+})(window.BrowserMap = window.BrowserMap || {});

Added: incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/bmaputil.js
URL: http://svn.apache.org/viewvc/incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/bmaputil.js?rev=1610605&view=auto
==============================================================================
--- incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/bmaputil.js (added)
+++ incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/bmaputil.js Tue Jul 15 07:51:39 2014
@@ -0,0 +1,489 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * BrowserMapUtil contains various utility static functions used by BrowserMap-related code.
+ *
+ * @class BrowserMapUtil
+ */
+ (function(BrowserMapUtil) {
+    'use strict';
+
+        /**
+     * Merge two objects as hashes. Entries with duplicate keys are overwritten with values from the second object.
+     *
+     * @param {Object} hsh1 - the first hash object
+     * @param {Object} hsh2 - the second hash object
+     * @return {Object} a hash object obtained by merging the two parameter hash objects
+     */
+    BrowserMapUtil.merge = function(hsh1, hsh2) {
+        var hsh = { },
+            prop;
+        for (prop in hsh1) {
+            if (hsh1.hasOwnProperty(prop)) {
+                hsh[prop] = hsh1[prop];
+            }
+        }
+        for (prop in hsh2) {
+            if (hsh2.hasOwnProperty(prop)) {
+                hsh[prop] = hsh2[prop];
+            }
+        }
+        return hsh;
+    };
+
+    /**
+     * Returns the set difference between Array a and Array b (a \ b).
+     *
+     * @param {Array} a - the first Array
+     * @param {Array} b - the second Array
+     * @return {Array} an Array containing the set difference
+     * @throws TypeError if either a or b are not of type Array
+     */
+    BrowserMapUtil.getArrayDifference = function (a, b) {
+        if (!a instanceof Array) {
+            throw new TypeError('Expected Array for a');
+        }
+        if (!b instanceof Array) {
+            throw new TypeError('Expected Array for b');
+        }
+        var i,
+            seen = [],
+            diff = [];
+        for (i = 0; i < b.length; i++) {
+            seen[b[i]] = true;
+        }
+        for (i = 0; i < a.length; i++) {
+            if (!seen[a[i]]) {
+                diff.push(a[i]);
+            }
+        }
+        return diff;
+    };
+
+    /**
+     * The <code>cookieManager</code> is used to manage cookies client-side (see
+     * <a href="https://developer.mozilla.org/en/DOM/document.cookie">https://developer.mozilla.org/en/DOM/document.cookie</a>).
+     *
+     * @class BrowserMapUtil.CookieManager
+     */
+    BrowserMapUtil.CookieManager = {
+        /**
+         * Returns a <code>Cookie</code> set on the client.
+         *
+         * @param {String} name - the cookie's name
+         * @return {Cookie} the cookie; <code>null</code> if the specified cookie cannot be found
+         */
+        getCookie : function (name) {
+            if (!name || !this.cookieExists(name)) { return null; }
+            var cookieValue = decodeURIComponent(document.cookie.replace(new RegExp('(?:^|.*;\\s*)' +
+                encodeURIComponent(name).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*'), '$1'));
+            var cookie = new Cookie(name, cookieValue);
+            return cookie;
+        },
+
+        /**
+         * Sets a <code>Cookie</code> on the client.
+         *
+         * @param {Cookie} cookie - the cookie
+         */
+        setCookie : function (cookie) {
+            if (!cookie.name || /^(?:expires|max\-age|path|domain|secure)$/.test(cookie.name)) { return; }
+            var sExpires = '';
+            if (cookie.expires) {
+                switch (typeof cookie.expires) {
+                    case 'number':
+                        sExpires = '; max-age=' + cookie.expires; break;
+                    case 'String':
+                        sExpires = '; expires=' + cookie.expires; break;
+                    case 'object':
+                        if (cookie.expires.hasOwnProperty('toGMTString')) {
+                            sExpires = '; expires=' + cookie.expires.toGMTString();
+                        }
+                    break;
+                }
+            }
+            document.cookie = encodeURIComponent(cookie.name) + '=' + encodeURIComponent(cookie.value) + sExpires +
+                (cookie.domain ? '; domain=' + cookie.domain : '') + (cookie.path ? '; path=' + cookie.path : '') +
+                    (cookie.secure ? '; secure' : '');
+        },
+
+        /**
+         * Removes a cookie from the client, if one exists.
+         *
+         * @param {String} name - the <code>Cookie</code>'s name
+         */
+        removeCookie : function (name) {
+            if (!name || !this.cookieExists(name)) { return; }
+            var oExpDate = new Date();
+            oExpDate.setDate(oExpDate.getDate() - 1);
+            document.cookie = encodeURIComponent(name) + '=; expires=' + oExpDate.toGMTString() + ';';
+        },
+
+        /**
+         * Tests if a cookie exists on the client.
+         *
+         * @param {String} name - the cookie's name
+         * @return {Boolean} <code>true</code> if the cookie exists, <code>false</code> otherwise
+         */
+        cookieExists : function (name) {
+            return (new RegExp('(?:^|;\\s*)' + encodeURIComponent(name).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=')).test(document.cookie);
+        },
+
+        cookiesEnabled : function () {
+            var cookie = new Cookie('browsermap_test_cookie', 'browsermap_test_cookie', 10, '/');
+            this.setCookie(cookie);
+            var testCookie = this.getCookie('browsermap_test_cookie');
+            if (testCookie !== null) {
+                this.removeCookie('browsermap_test_cookie');
+                return true;
+            }
+            return false;
+        }
+    };
+
+    /**
+     * The <code>file</code> object provides various file-related static utility methods.
+     *
+     * @class BrowserMapUtil.File
+     */
+    BrowserMapUtil.File = {
+        /**
+         * Returns the extension of a file based on the file name.
+         *
+         * @param {String} file - the file's name
+         * @return {String} a String containing the file's extension, empty String if the file does not have an extension
+         */
+        getFileExtension : function (file) {
+            var extension = '';
+            if (file && file !== '' && file.indexOf('.') != -1) {
+                extension = file.substring(file.lastIndexOf('.') + 1, file.length);
+            }
+            return extension;
+        },
+
+        /**
+         * Analyses if a file has selectors in its file name and returns the file name (file part + extension) without the selectors.
+         *
+         * @param {String} file - the file from which to remove the selectors
+         * @return {String} a String containing the file with the removed selectors
+         */
+        removeSelectorsFromFile : function(file) {
+            if (file && file !== '') {
+                var tokens = file.split('.');
+                if (tokens.length > 2) {
+                    return tokens[0] + '.' + tokens[tokens.length - 1];
+                }
+            }
+            return file;
+        }
+    };
+
+    /**
+     * The <code>url</code> object provides various URL-related static utility methods.
+     *
+     * @class BrowserMapUtil.Url
+     */
+    BrowserMapUtil.Url = {
+        /**
+         * Analyses a URL an returns the domain part from it.
+         *
+         * @param {String} url - the URL from which to extract the domain part
+         * @return {String} the detected domain
+         */
+        getDomainFromURL : function (url) {
+            var domain = '';
+            url = url.replace(/http:\/\/|https:\/\//, '');
+            var slashIndex = url.indexOf('/');
+            if (slashIndex == -1) {
+                domain = url;
+            } else {
+                domain = url.substring(0, slashIndex);
+            }
+            return domain;
+        },
+
+        /**
+         * Decodes the value of a <code>GET</code> request URL parameter.
+         *
+         * @param {String} value - the encoded value of the parameter
+         * @return {String} the decoded value of the parameter
+         */
+        decodeURLParameterValue : function (value) {
+            return decodeURIComponent(value.replace(/\+/g, ' '));
+        },
+
+        /**
+         * Returns a map with the <code>GET</code> paramters of a URL.
+         *
+         * @param {String} url - the URL from which the parameters need to be extracted
+         * @return {Object} the map with the parameters and their values
+         */
+        getURLParameters : function (url) {
+            var map = {}, self = this;
+            var f = function(m,key,value) { map[key] = self.decodeURLParameterValue(value); };
+            url.replace(/[?&]+([^=&]+)=([^&]*)/gi, f);
+            return map;
+        },
+
+        /**
+         * Returns the value of a specified <code>GET</code> parameter from a URL if the parameter exists. Otherwise it will return
+         * <code>null</code>.
+         *
+         * @param {String} url - the URL from which the parameter value needs to be extracted
+         * @param {String} parameter - the name of the <code>GET</code> parameter whose value needs to be returned
+         * @return {String} the value of the parameter, <code>null</code> if the parameter does not exist
+         */
+        getValueForParameter : function (url, parameter) {
+            return this.getURLParameters(url)[parameter];
+        },
+
+        /**
+         * Returns the <code>GET</code> parameters String from a URL.
+         *
+         * @param {String} url - the URL form which the parameters String should be extracted
+         * @return {String} the parameters String; empty String if the URL is <code>null</code> / empty
+         */
+        getURLParametersString : function (url) {
+            var urlParametersString = '';
+            if (url && url !== '' && url.lastIndexOf('?') != -1) {
+                urlParametersString = url.substring(url.lastIndexOf('?'), url.length);
+            }
+            return urlParametersString;
+        },
+
+        /**
+         * Returns the file part of a URL If the URL sent as a parameter
+         * is empty or null, the returned value will be an empty String.
+         *
+         * @param {String} url - the URL from which the file part should be extracted
+         * @return {String} a String containing the file part; empty String if the URL is null or empty or points to a folder instead of
+         *      a file
+         */
+        getFileFromURL : function (url) {
+            var file = '';
+            if (url && url !== '') {
+                url = url.replace('https://', '');
+                url = url.replace('http://', '');
+                url = url.replace(BrowserMapUtil.Url.getURLParametersString(url), '');
+                if (url.lastIndexOf('/') != -1 && url[url.lastIndexOf('/') + 1] != '?') {
+                    file = url.substring(url.lastIndexOf('/') + 1, url.length);
+                }
+            }
+            return file;
+        },
+
+        /**
+         * Retrieves the folder path from a URL.
+         *
+         * @param {String} url - the URL from which the path is extracted
+         * @return {String} a String containing the folder path; empty String if the URL is <code>null</code> or empty or it does not end
+         *  with "/"
+         */
+        getFolderPathFromURL : function (url) {
+            var folderPath = '';
+            var tmpURL = url;
+            tmpURL = tmpURL.replace('https://', '');
+            tmpURL = tmpURL.replace('http://', '');
+            if (tmpURL && tmpURL !== '' && tmpURL.lastIndexOf('/') != -1) {
+                folderPath = tmpURL.substring(0, tmpURL.lastIndexOf('/') + 1);
+                folderPath = url.substring(0, url.indexOf(folderPath)) + folderPath;
+            }
+            return folderPath;
+        },
+
+        /**
+         * Analyses a resource (the file part from a URL) and retrieves its selectors. The selectors will be returned in an Array. An empty
+         * Array will be returned if no selectors have been found.
+         *
+         * @param {String} url - the URL from which the selectors have to be extracted
+         * @return {Array} an Array with the selectors; the Array will be empty if no selectors have been found
+         */
+        getSelectorsFromURL : function(url) {
+            var selectors = [];
+            if (url && url !== '') {
+                url = url.replace('https://', '');
+                url = url.replace('http://', '');
+                // ditch the parameters when retrieving selectors
+                if (url.lastIndexOf('?') != -1) {
+                    url = url.substring(0, url.lastIndexOf('?'));
+                }
+                if (url.lastIndexOf('/') != -1 ) {
+                    url = url.substring(url.lastIndexOf('/') + 1, url.length);
+                    var selectorCandidates = url.split('.');
+                    if (selectorCandidates.length > 2) {
+                        for (var i = 1; i < selectorCandidates.length - 1; i++) {
+                            selectors.push(selectorCandidates[i]);
+                        }
+                    }
+                }
+            }
+            return selectors;
+        },
+
+        /**
+         * Adds selectors to the supplied URL and returns the modified URL. For example:
+         * <pre>
+         *      BrowserMapUtil.Url.addSelectorsToUrl('http://www.example.com/index.html', ['mobile'])
+         *      ->
+         *      'http://www.example.com/index.mobile.html'
+         * </pre>
+         * @param {String} url - the URL to which selectors need to be added
+         * @param {Array} selectors - an Array with the selectors that have to be applied to the current URL
+         * @return {String} a String containing the new URL
+         */
+        addSelectorsToURL : function(url, selectors) {
+            var file = this.getFileFromURL(url),
+                parameters = BrowserMapUtil.Url.getURLParametersString(url);
+            file = BrowserMapUtil.File.removeSelectorsFromFile(file);
+            if (file && file !== '') {
+                var path = this.getFolderPathFromURL(url);
+                var extension = BrowserMapUtil.File.getFileExtension(file);
+                file = file.replace('.' + extension, '');
+                var newURL = path + file;
+                if (selectors.length > 0) {
+                    newURL += '.';
+                }
+                newURL += selectors.join('.');
+                if (extension && extension !== '') {
+                    newURL += '.' + extension;
+                }
+                newURL += parameters;
+                return newURL;
+            }
+            return url;
+        },
+
+        /**
+         * Transforms a relative URL to an absolute one for IE7 which is not able to resolve relative URLs by itself.
+         *
+         * @param {String} url - the relative URL
+         * @return {String} a String with the absolute URL
+         */
+        qualifyURL : function(url) {
+            var absoluteURL = null,
+                el;
+            if (url) {
+                el = document.createElement('div');
+                el.innerHTML= '<a href="' + encodeURI(url) + '">x</a>';
+                absoluteURL = el.firstChild.href;
+            }
+            return absoluteURL;
+        },
+
+        /**
+         * Searches for a canonical link in the current document. If ones is found, its href attribute's value is returned.
+         *
+         * @return {String} a String with the canonical URL; null if one is not found
+         */
+        getCanonicalURL : function() {
+            var headElement = document.getElementsByTagName('head')[0],
+                links,
+                i,
+                link,
+                url;
+            if (headElement) {
+                links = headElement.getElementsByTagName('link');
+                if (links) {
+                    for (i = 0; i < links.length; i++) {
+                        link = links[i];
+                        if (link.rel && link.rel === 'canonical') {
+                            url = link.href;
+                            break;
+                        }
+                    }
+                }
+            }
+            return url;
+        }
+    };
+
+ })(window.BrowserMapUtil = window.BrowserMapUtil || {});
+
+
+/**
+ * Creates a Cookie object.
+ *
+ * @constructor
+ * @param {String} name - this cookie's name
+ * @param {String} value - this cookie's value (unescaped - the cookie manager will handle escaping / unescaping)
+ * @param {Object} expires - this cookie's expires information; the object can have the following types:
+ *      <ol>
+ *          <li><code>Number</code> - expiration time in seconds</li>
+ *          <li><code>String</code> - expiration time as a String formatted date</li>
+ *          <li><code>Object</code> - expiration time as a Date object</li>
+ *      </ol>
+ * @param {String} path - the path for which this cookie is valid
+ * @param {String} domain - the domain for which this cookie is valid
+ * @param {Boolean} secure - boolean flag to inidicate if this cookie needs to be used only for HTTPS connections or not
+ */
+function Cookie(name, value, expires, path, domain, secure) {
+    if (!(this instanceof Cookie)) {
+        return new Cookie(name, value, expires, path, domain, secure);
+    }
+    this.name = name;
+    this.value = value;
+    this.expires = expires;
+    this.path = path;
+    this.domain = domain;
+    this.secure = secure;
+}
+
+// Array.indexOf polyfill
+// from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
+if (!Array.prototype.indexOf) {
+    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
+        'use strict';
+        if (this === null) {
+            throw new TypeError();
+        }
+        var t = Object(this);
+        var len = t.length >>> 0;
+        if (len === 0) {
+            return -1;
+        }
+        var n = 0;
+        if (arguments.length > 0) {
+            n = Number(arguments[1]);
+            if (n != n) { // shortcut for verifying if it's NaN
+                n = 0;
+            } else if (n !== 0 && n != Infinity && n != -Infinity) {
+                n = (n > 0 || -1) * Math.floor(Math.abs(n));
+            }
+        }
+        if (n >= len) {
+            return -1;
+        }
+        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
+        for (; k < len; k++) {
+            if (k in t && t[k] === searchElement) {
+                return k;
+            }
+        }
+        return -1;
+    };
+}
+
+// String.trim() polyfill
+// from https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/Trim
+if (!String.prototype.trim) {
+    String.prototype.trim = function () {
+        return this.replace(/^\s+|\s+$/g,'');
+    };
+}

Added: incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/devicegroups.js
URL: http://svn.apache.org/viewvc/incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/devicegroups.js?rev=1610605&view=auto
==============================================================================
--- incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/devicegroups.js (added)
+++ incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/devicegroups.js Tue Jul 15 07:51:39 2014
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This file defines the default device groups.
+ */
+
+ /*global BrowserMap:false, Modernizr:false */
+BrowserMap.addDeviceGroup({
+    'ranking' : 0,
+    'name' : 'smartphone',
+    'description' : 'Smartphone',
+    'testFunction' : function() {
+        if (BrowserMap.probe('clientWidth') > 480 && BrowserMap.probe('portrait')) {
+            return false;
+        }
+        if (BrowserMap.probe('clientWidth') >= 900 && BrowserMap.probe('landscape')) {
+            return false;
+        }
+        if (BrowserMap.probe('canResizeBrowserWindow')) {
+            return false;
+        }
+        return true;
+    },
+    'isSelector' : true
+});
+
+BrowserMap.addDeviceGroup({
+    'ranking' : 10,
+    'name' : 'tablet',
+    'description' : 'Standard Tablet',
+    'testFunction' : function() {
+        if (BrowserMap.probe('portrait') && BrowserMap.probe('clientWidth') <= 480) {
+            return false;
+        }
+        if (BrowserMap.probe('landscape') && BrowserMap.probe('clientWidth') < 900) {
+            return false;
+        }
+        if (!Modernizr.touch) {
+            return false;
+        }
+        if (BrowserMap.probe('canResizeBrowserWindow')) {
+            return false;
+        }
+        return true;
+    },
+    'isSelector' : true
+});
+
+BrowserMap.addDeviceGroup({
+    'ranking' : 20,
+    'name' : 'highResolutionDisplay',
+    'description' : 'High Resolution Display',
+    'testFunction' : function() {
+        return BrowserMap.probe('devicePixelRatio') >= 2;
+    },
+    'isSelector' : true
+});
+
+BrowserMap.addDeviceGroup({
+    'ranking' : 30,
+    'name' : 'browser',
+    'description' : 'Modern desktop browser',
+    'testFunction': function () {
+        if (BrowserMap.probe('portrait') && BrowserMap.probe('clientWidth') < 720) {
+            return false;
+        }
+        if (BrowserMap.probe('landscape') && BrowserMap.probe('clientWidth') <1200) {
+            return false;
+        }
+        return Modernizr.csstransforms3d && !Modernizr.touch;
+    },
+    'isSelector' : false
+});
+
+BrowserMap.addDeviceGroup({
+    'ranking' : Number.MAX_VALUE,
+    'name' : 'oldBrowser',
+    'description' : 'Old desktop browser',
+    'testFunction' : function() {
+        for (var i in BrowserMap.getMatchedDeviceGroups()) {
+            if (BrowserMap.getMatchedDeviceGroups().hasOwnProperty(i)) {
+                return false;
+            }
+        }
+        return true;
+    },
+    'isSelector' : false
+});

Added: incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/probes.js
URL: http://svn.apache.org/viewvc/incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/probes.js?rev=1610605&view=auto
==============================================================================
--- incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/probes.js (added)
+++ incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/js/probes.js Tue Jul 15 07:51:39 2014
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This file defines the default probes.
+ */
+
+/*global BrowserMap:false, Modernizr:false */
+BrowserMap.addProbe('BrowserMap.version', function() {
+    return BrowserMap.VERSION;
+}).addProbe('Modernizr.touch', function() {
+    return Modernizr.touch;
+}).addProbe('Modernizr.csstransforms3d', function() {
+    return Modernizr.csstransforms3d;
+}).addProbe('window.devicePixelRatio', function() {
+    return window.devicePixelRatio;
+}).addProbe('window.orientation', function() {
+    return window.orientation;
+}).addProbe('navigator.vendor', function() {
+    return navigator.vendor;
+}).addProbe('navigator.platform', function() {
+    return navigator.platform;
+}).addProbe('navigator.appName', function() {
+    return navigator.appName;
+}).addProbe('navigator.appVersion', function() {
+    return navigator.appVersion;
+}).addProbe('navigator.appCodeName', function() {
+    return navigator.appCodeName;
+}).addProbe('navigator.userAgent', function() {
+    return navigator.userAgent;
+}).addProbe('screenWidth', function() {
+    return screen.width;
+}).addProbe('screenHeight', function() {
+    return screen.height;
+}).addProbe('clientWidth', function() {
+    return document.documentElement.clientWidth;
+}).addProbe('orientation', function() {
+    var orientation = '';
+    if (window.innerWidth > window.innerHeight) {
+        orientation = 'landscape';
+    }
+    else {
+        orientation = 'portrait';
+    }
+    return orientation;
+}).addProbe('portrait', function() {
+    return BrowserMap.probe('orientation') == 'portrait';
+}).addProbe('landscape', function() {
+    return BrowserMap.probe('orientation') == 'landscape';
+}).addProbe('screenWidthDependingOnOrientation', function () {
+    var widthDependingOnOrientation = 0;
+    if (BrowserMap.probe('orientation') === 'portrait') {
+        widthDependingOnOrientation = screen.width > screen.height ? screen.height : screen.width;
+    } else {
+        widthDependingOnOrientation = screen.width < screen.height ? screen.height : screen.width;
+    }
+    return widthDependingOnOrientation;
+}).addProbe('clientWidthDependingOnOrientation', function () {
+    var clientWidthDependingOnOrientation = 0;
+    if (BrowserMap.probe('orientation') == 'portrait') {
+        clientWidthDependingOnOrientation = document.documentElement.clientWidth <
+            document.documentElement.clientHeight ? document.documentElement.clientWidth : document.documentElement.clientHeight;
+    } else {
+        clientWidthDependingOnOrientation = document.documentElement.clientWidth >
+            document.documentElement.clientHeight ? document.documentElement.clientWidth : document.documentElement.clientHeight;
+    }
+    return clientWidthDependingOnOrientation;
+}).addProbe('devicePixelRatio', function() {
+    var mq = window.matchMedia,
+        ratio = -1,
+        userAgent;
+    if (mq) {
+        for (var i = 0.5; i <= 3; i+= 0.05) {
+            var r = Math.round(i * 100)/100;
+            if (mq(
+                    '(max-resolution: ' + r + 'dppx), \
+                    (max-resolution: ' + r * 96 + 'dpi), \
+                    (-webkit-max-device-pixel-ratio: ' + r + '), \
+                    (-o-device-pixel-ratio: ' + r + ')'
+                ).matches) {
+                ratio = r;
+                break;
+            }
+        }
+    }
+
+    // hacks for browsers not returning correct answers to the above media queries
+    userAgent = BrowserMap.probe('navigator.userAgent');
+    if (userAgent.indexOf('BlackBerry') != -1 || userAgent.indexOf('Windows Phone') != -1) {
+        ratio = Math.round(BrowserMap.probe('screenWidthDependingOnOrientation') /
+                BrowserMap.probe('clientWidthDependingOnOrientation') * 100) /
+                100;
+    }
+    return ratio;
+}).addProbe('canResizeBrowserWindow', function() {
+    /**
+     * useful to detect a mobile browser (false) / a desktop browser (true)
+     */
+    return Math.round(BrowserMap.probe('screenWidthDependingOnOrientation') / BrowserMap.probe('devicePixelRatio')) -
+                BrowserMap.THE_ANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING > BrowserMap.probe('clientWidth');
+});

Added: incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/lib/matchMedia/matchMedia.js
URL: http://svn.apache.org/viewvc/incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/lib/matchMedia/matchMedia.js?rev=1610605&view=auto
==============================================================================
--- incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/lib/matchMedia/matchMedia.js (added)
+++ incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/lib/matchMedia/matchMedia.js Tue Jul 15 07:51:39 2014
@@ -0,0 +1,36 @@
+/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
+
+window.matchMedia = window.matchMedia || (function( doc, undefined ) {
+
+  "use strict";
+
+  var bool,
+      docElem = doc.documentElement,
+      refNode = docElem.firstElementChild || docElem.firstChild,
+      // fakeBody required for <FF4 when executed in <head>
+      fakeBody = doc.createElement( "body" ),
+      div = doc.createElement( "div" );
+
+  div.id = "mq-test-1";
+  div.style.cssText = "position:absolute;top:-100em";
+  fakeBody.style.background = "none";
+  fakeBody.appendChild(div);
+
+  return function(q){
+
+    div.innerHTML = "&shy;<style media=\"" + q + "\"> #mq-test-1 { width: 42px; }</style>";
+
+    docElem.insertBefore( fakeBody, refNode );
+    bool = div.offsetWidth === 42;
+    docElem.removeChild( fakeBody );
+
+    return {
+      matches: bool,
+      media: q
+    };
+
+  };
+
+}( document ));
+
+

Added: incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/lib/modernizr/modernizr.custom.js
URL: http://svn.apache.org/viewvc/incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/lib/modernizr/modernizr.custom.js?rev=1610605&view=auto
==============================================================================
--- incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/lib/modernizr/modernizr.custom.js (added)
+++ incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/lib/modernizr/modernizr.custom.js Tue Jul 15 07:51:39 2014
@@ -0,0 +1,284 @@
+/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
+ * Build: http://modernizr.com/download/#-csstransforms3d-touch-teststyles-testprop-testallprops-prefixes-domprefixes
+ */
+;
+
+
+
+window.Modernizr = (function( window, document, undefined ) {
+
+    var version = '2.6.2',
+
+    Modernizr = {},
+
+
+    docElement = document.documentElement,
+
+    mod = 'modernizr',
+    modElem = document.createElement(mod),
+    mStyle = modElem.style,
+
+    inputElem  ,
+
+
+    toString = {}.toString,
+
+    prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
+
+
+
+    omPrefixes = 'Webkit Moz O ms',
+
+    cssomPrefixes = omPrefixes.split(' '),
+
+    domPrefixes = omPrefixes.toLowerCase().split(' '),
+
+
+    tests = {},
+    inputs = {},
+    attrs = {},
+
+    classes = [],
+
+    slice = classes.slice,
+
+    featureName,
+
+
+    injectElementWithStyles = function( rule, callback, nodes, testnames ) {
+
+      var style, ret, node, docOverflow,
+          div = document.createElement('div'),
+                body = document.body,
+                fakeBody = body || document.createElement('body');
+
+      if ( parseInt(nodes, 10) ) {
+                      while ( nodes-- ) {
+              node = document.createElement('div');
+              node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
+              div.appendChild(node);
+          }
+      }
+
+                style = ['&#173;','<style id="s', mod, '">', rule, '</style>'].join('');
+      div.id = mod;
+          (body ? div : fakeBody).innerHTML += style;
+      fakeBody.appendChild(div);
+      if ( !body ) {
+                fakeBody.style.background = '';
+                fakeBody.style.overflow = 'hidden';
+          docOverflow = docElement.style.overflow;
+          docElement.style.overflow = 'hidden';
+          docElement.appendChild(fakeBody);
+      }
+
+      ret = callback(div, rule);
+        if ( !body ) {
+          fakeBody.parentNode.removeChild(fakeBody);
+          docElement.style.overflow = docOverflow;
+      } else {
+          div.parentNode.removeChild(div);
+      }
+
+      return !!ret;
+
+    },
+    _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
+
+    if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
+      hasOwnProp = function (object, property) {
+        return _hasOwnProperty.call(object, property);
+      };
+    }
+    else {
+      hasOwnProp = function (object, property) {
+        return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
+      };
+    }
+
+
+    if (!Function.prototype.bind) {
+      Function.prototype.bind = function bind(that) {
+
+        var target = this;
+
+        if (typeof target != "function") {
+            throw new TypeError();
+        }
+
+        var args = slice.call(arguments, 1),
+            bound = function () {
+
+            if (this instanceof bound) {
+
+              var F = function(){};
+              F.prototype = target.prototype;
+              var self = new F();
+
+              var result = target.apply(
+                  self,
+                  args.concat(slice.call(arguments))
+              );
+              if (Object(result) === result) {
+                  return result;
+              }
+              return self;
+
+            } else {
+
+              return target.apply(
+                  that,
+                  args.concat(slice.call(arguments))
+              );
+
+            }
+
+        };
+
+        return bound;
+      };
+    }
+
+    function setCss( str ) {
+        mStyle.cssText = str;
+    }
+
+    function setCssAll( str1, str2 ) {
+        return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
+    }
+
+    function is( obj, type ) {
+        return typeof obj === type;
+    }
+
+    function contains( str, substr ) {
+        return !!~('' + str).indexOf(substr);
+    }
+
+    function testProps( props, prefixed ) {
+        for ( var i in props ) {
+            var prop = props[i];
+            if ( !contains(prop, "-") && mStyle[prop] !== undefined ) {
+                return prefixed == 'pfx' ? prop : true;
+            }
+        }
+        return false;
+    }
+
+    function testDOMProps( props, obj, elem ) {
+        for ( var i in props ) {
+            var item = obj[props[i]];
+            if ( item !== undefined) {
+
+                            if (elem === false) return props[i];
+
+                            if (is(item, 'function')){
+                                return item.bind(elem || obj);
+                }
+
+                            return item;
+            }
+        }
+        return false;
+    }
+
+    function testPropsAll( prop, prefixed, elem ) {
+
+        var ucProp  = prop.charAt(0).toUpperCase() + prop.slice(1),
+            props   = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
+
+            if(is(prefixed, "string") || is(prefixed, "undefined")) {
+          return testProps(props, prefixed);
+
+            } else {
+          props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
+          return testDOMProps(props, prefixed, elem);
+        }
+    }    tests['touch'] = function() {
+        var bool;
+
+        if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
+          bool = true;
+        } else {
+          injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) {
+            bool = node.offsetTop === 9;
+          });
+        }
+
+        return bool;
+    };
+    tests['csstransforms3d'] = function() {
+
+        var ret = !!testPropsAll('perspective');
+
+                        if ( ret && 'webkitPerspective' in docElement.style ) {
+
+                      injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) {
+            ret = node.offsetLeft === 9 && node.offsetHeight === 3;
+          });
+        }
+        return ret;
+    };
+    for ( var feature in tests ) {
+        if ( hasOwnProp(tests, feature) ) {
+                                    featureName  = feature.toLowerCase();
+            Modernizr[featureName] = tests[feature]();
+
+            classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
+        }
+    }
+
+
+
+     Modernizr.addTest = function ( feature, test ) {
+       if ( typeof feature == 'object' ) {
+         for ( var key in feature ) {
+           if ( hasOwnProp( feature, key ) ) {
+             Modernizr.addTest( key, feature[ key ] );
+           }
+         }
+       } else {
+
+         feature = feature.toLowerCase();
+
+         if ( Modernizr[feature] !== undefined ) {
+                                              return Modernizr;
+         }
+
+         test = typeof test == 'function' ? test() : test;
+
+         if (typeof enableClasses !== "undefined" && enableClasses) {
+           docElement.className += ' ' + (test ? '' : 'no-') + feature;
+         }
+         Modernizr[feature] = test;
+
+       }
+
+       return Modernizr;
+     };
+
+
+    setCss('');
+    modElem = inputElem = null;
+
+
+    Modernizr._version      = version;
+
+    Modernizr._prefixes     = prefixes;
+    Modernizr._domPrefixes  = domPrefixes;
+    Modernizr._cssomPrefixes  = cssomPrefixes;
+
+
+
+    Modernizr.testProp      = function(prop){
+        return testProps([prop]);
+    };
+
+    Modernizr.testAllProps  = testPropsAll;
+
+
+    Modernizr.testStyles    = injectElementWithStyles;
+    return Modernizr;
+
+})(this, this.document);
+;

Added: incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/css/style.css
URL: http://svn.apache.org/viewvc/incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/css/style.css?rev=1610605&view=auto
==============================================================================
--- incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/css/style.css (added)
+++ incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/css/style.css Tue Jul 15 07:51:39 2014
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+body {
+    margin: 0;
+    padding: 36px 15px 0 50px;
+    background-color: #F0F0F0;
+    color: #666666;
+    font-size: 17px;
+    font-family: 'HelveticaNeue-Light', Helvetica, Arial, sans-serif;
+    font-weight: 300;
+}
+
+a {
+    color: #666666;
+}
+
+a:hover, a:active {
+    color: black;
+}
+
+p {
+    width: 100%;
+    padding-left: 0px;
+    margin-top: 0;
+    margin-bottom: 0px;
+}
+
+li {
+    border-top: 1px dotted;
+    width: 578px;
+    padding-top: 6px;
+    padding-bottom: 6px;
+}
+
+h3 {
+    margin: 0px;
+    background-color: #666666;
+    color: #FFFFFF;
+    /*padding: 2px;*/
+    font-size: 110%;
+    font-weight: bold;
+}
+
+div.debug {
+    font-family: monospace;
+    margin-top: 20px;
+    margin-bottom: 50px;
+}
+
+div.console-output {
+    background-color: #000000;
+    color: #24FE51;
+    overflow: auto;
+    padding: 10px;
+}
+
+#result {
+    font-size: 200%;
+    margin: 0.5em;
+    padding: 0.5em;
+
+    background: yellow;
+
+    /* Mozilla: */
+    background: -moz-linear-gradient(left, yellow, #00FF00);
+
+    /* Chrome, Safari:*/
+    background: -webkit-gradient(linear, left top, right top, from(yellow), to(#00FF00));
+    /* MSIE */
+    filter: progid:DXImageTransform.Microsoft.Gradient(StartColorStr='yellow', EndColorStr='#00FF00', GradientType=1);
+
+    /* rounder corners */
+    -moz-border-radius: 1em;
+    border-radius: 1em;
+}
+
+#result span {
+    color: blue;
+    font-weight: bold;
+}
+

Added: incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/index.html
URL: http://svn.apache.org/viewvc/incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/index.html?rev=1610605&view=auto
==============================================================================
--- incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/index.html (added)
+++ incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/index.html Tue Jul 15 07:51:39 2014
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<html>
+<head>
+<title>Browsermap client-side detection library</title>
+<link rel="stylesheet" type="text/css" href="css/style.css">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<script type="text/javascript" src="js/browsermap.min.js"></script>
+</head>
+<body>
+    <div id="result">
+        <p>
+        The BrowserMap client-side detection library thinks your
+        device type is <span id="devicegroup-description">UNIDENTIFIED??</span>,
+        which is part of the <span id="devicegroup-name">NOT FOUND??</span> device group.
+        </p>
+    </div>
+    <div id="description">
+        <p>
+            BrowserMap is a JavaScript browser features detection library. It uses modular probes and code snippets that detect
+            specific features of the client, which can then be grouped in a simple way to create device groups. By identifying the client type,
+            BrowserMap can help optimize page rendering or it can provide the client with alternate representations of the current page.
+        </p>
+        <br />
+        <p>
+            The following is a list of the device groups BrowserMap is capable of identifying. To override the detected device groups you can use the
+            <code>device</code> <code>GET</code> request parameter. To showcase this behaviour, you can click on any device group name from below:
+            <ol id="devicegroups-list"></ol>
+        </p>
+        <p>
+            To reset the override, you need to call <a href="#" onclick="javascript:BrowserMap.removeOverride()"><code>BrowserMap.removeOverride()</code></a>.
+        </p>
+    </div>
+    <div class="debug">
+        <h3>Debugging Info - BrowserMap</h3>
+        <div class="console-output" id="debug-info">
+        </div>
+    </div>
+    <script type="text/javascript" src="js/showcase.js"></script>
+</body>

Added: incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/js/showcase.js
URL: http://svn.apache.org/viewvc/incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/js/showcase.js?rev=1610605&view=auto
==============================================================================
--- incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/js/showcase.js (added)
+++ incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/main/resources/demo/js/showcase.js Tue Jul 15 07:51:39 2014
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+(function () {
+    // Populate test page
+    BrowserMap.forwardRequest();
+    var matchedGroups = BrowserMap.getMatchedDeviceGroups(),
+        matchedGroupsDescription = [],
+        matchedGroupsNames = [],
+        testFunctions = [],
+        dgs = [],
+        deviceGrps,
+        g,
+        element,
+        i;
+    for (g in matchedGroups) {
+        matchedGroupsDescription.push(matchedGroups[g].description);
+        matchedGroupsNames.push(matchedGroups[g].name);
+        testFunctions.push(matchedGroups[g].testFunction);
+    }
+    deviceGrps = BrowserMap.getDeviceGroups();
+    for (var g in deviceGrps) {
+        dgs.push(deviceGrps[g].name);
+    }
+
+    element = document.getElementById('devicegroup-description');
+    element.innerHTML = matchedGroupsDescription.join(', ')
+    element = document.getElementById('devicegroup-name');
+    element.innerHTML = matchedGroupsNames.join(', ');
+
+    element = document.getElementById('debug-info');
+    var probingResults = BrowserMap.getProbingResults();
+    for (i in probingResults) {
+        if (probingResults.hasOwnProperty(i)) {
+            element.innerHTML += i + '=' + probingResults[i] + '<br />';
+        }
+    }
+    element.innerHTML += '<br />';
+    element.innerHTML += 'Test functions: <br />';
+    for (i in testFunctions) {
+        element.innerHTML += testFunctions[i] + '<br />';
+    }
+    element.innerHTML += '<br />';
+
+    var dgs = BrowserMap.getDeviceGroupsInRankingOrder(),
+        urlNoParams = window.location.href.replace(BrowserMapUtil.Url.getURLParametersString(window.location.href), ''),
+        i;
+    element = document.getElementById('devicegroups-list');
+    for (i = 0; i < dgs.length; i++) {
+        element.innerHTML += '<li><a href="' + urlNoParams + '?device=' + dgs[i].name + '">' + dgs[i].name + '</a></li>';
+    }
+})();

Added: incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/test/js/tests.js
URL: http://svn.apache.org/viewvc/incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/test/js/tests.js?rev=1610605&view=auto
==============================================================================
--- incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/test/js/tests.js (added)
+++ incubator/devicemap/trunk/browsermap/tags/browsermap-1.4.0/src/test/js/tests.js Tue Jul 15 07:51:39 2014
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+QUnit.begin(function() {
+    var headElement = document.getElementsByTagName('head')[0],
+        headElementContent = headElement.innerHTML;
+    headElementContent += '\
+        <link rel="canonical" href="http://www.example.com/index.html">\
+        <link rel="alternate" data-bmap-devgroups="browser" hreflang="en" href="http://www.example.com/en/index.html" data-bmap-currentvar="true">\
+        <link rel="alternate" data-bmap-devgroups="browser" hreflang="de" href="http://www.example.com/de/index.html">\
+        <link rel="alternate" data-bmap-devgroups="smartphone" hreflang="de" href="http://www.example.com/de/index.smartphone.html">\
+        <link rel="alternate" data-bmap-devgroups="smartphone" hreflang="en" href="http://www.example.com/en/index.smartphone.html">\
+        <meta name="browsermap.enabled" content="false">';
+    headElement.innerHTML = headElementContent;
+});
+
+module('BrowserMapUtil');
+test('merge', function() {
+    var hash1 = {a : 1, b : 2};
+    var hash2 = {b : 3, c : 3};
+    var hash3 = {a : 1, b : 3, c : 3};
+    deepEqual(BrowserMapUtil.merge(hash1, hash2), hash3, 'merge');
+});
+test('getArrayDifference', function() {
+    var a = [1, 2, 3];
+    var b = [4, 5, 6];
+    var c = [3, 4, 5];
+    deepEqual(BrowserMapUtil.getArrayDifference(a, b), [1, 2, 3], 'getArrayDifference - different sets');
+    deepEqual(BrowserMapUtil.getArrayDifference(a, a), [], 'getArrayDifference - identical sets');
+    deepEqual(BrowserMapUtil.getArrayDifference(a, c), [1, 2], 'getArrayDifference - sets with some common elements');
+});
+test('cookiemanager', function() {
+    var c = 'test=test';
+    document.cookie = c;
+    ok(BrowserMapUtil.CookieManager.cookieExists('test'), 'cookieExists');
+    var cookie = BrowserMapUtil.CookieManager.getCookie('test');
+    ok(cookie instanceof Cookie, 'getCookie - returned object type');
+    strictEqual(cookie.name, 'test', 'getCookie - test cookie name');
+    cookie.value = 'test2';
+    BrowserMapUtil.CookieManager.setCookie(cookie);
+    strictEqual(BrowserMapUtil.CookieManager.getCookie('test').value, 'test2', 'setCookie');
+    BrowserMapUtil.CookieManager.removeCookie('test');
+    equal(BrowserMapUtil.CookieManager.getCookie('test'), null, 'removeCookie');
+});
+test('file', function() {
+    strictEqual(BrowserMapUtil.File.getFileExtension('index.html'), 'html', 'getFileExtension - file with extension');
+    strictEqual(BrowserMapUtil.File.getFileExtension('index.html.exe'), 'exe', 'getFileExtension - file with 2 extensions');
+    strictEqual(BrowserMapUtil.File.getFileExtension('index'), '', 'getFileExtension - file without extension');
+    strictEqual(BrowserMapUtil.File.removeSelectorsFromFile('index.a.b.html'), 'index.html', 'removeSelectorsFromFile - two selectors');
+    strictEqual(BrowserMapUtil.File.removeSelectorsFromFile('index.html'), 'index.html', 'removeSelectorsFromFile - no selectors');
+});
+test('url', function() {
+    strictEqual(BrowserMapUtil.Url.getDomainFromURL('http://www.example.com'), 'www.example.com', 'getDomainFromURL - http, no parameters');
+    strictEqual(BrowserMapUtil.Url.getDomainFromURL('http://www.example.com/?s='), 'www.example.com', 'getDomainFromURL - http, 1 parameter');
+    strictEqual(BrowserMapUtil.Url.decodeURLParameterValue('test+te%20st'), 'test te st', 'decodeURLParameterValue');
+    deepEqual(BrowserMapUtil.Url.getURLParameters('http://www.example.com/?a=a&b=b'), {a : 'a', b : 'b'}, 'getURLParameters - 2 parameters');
+    deepEqual(BrowserMapUtil.Url.getURLParameters('http://www.example.com/'), {}, 'getURLParameters - no parameters');
+    strictEqual(BrowserMapUtil.Url.getValueForParameter('http://www.example.com/?a=test', 'a'), 'test', 'getValueForParameter - 1 parameter');
+    equal(BrowserMapUtil.Url.getValueForParameter('http://www.example.com/', 'a'), null, 'getValueForParameter - no parameters');
+    strictEqual(BrowserMapUtil.Url.getURLParametersString('http://www.example.com?a=test&b=test'), '?a=test&b=test', 'getURLParametersString - with parameters');
+    strictEqual(BrowserMapUtil.Url.getURLParametersString('http://www.example.com'), '', 'getURLParametersString - without parameters');
+    strictEqual(BrowserMapUtil.Url.getFileFromURL('http://www.example.com/index.html?param=true'), 'index.html', 'getFileFromURL - with parameters');
+    strictEqual(BrowserMapUtil.Url.getFileFromURL('http://www.example.com/folder/index.html?param=true'), 'index.html', 'getFileFromURL - with parameters + folder');
+    strictEqual(BrowserMapUtil.Url.getFileFromURL('http://www.example.com/folder/'), '', 'getFileFromURL - no file + folder');
+    strictEqual(BrowserMapUtil.Url.getFileFromURL('http://www.example.com'), '', 'getFileFromURL - web root');
+    strictEqual(BrowserMapUtil.Url.getFolderPathFromURL('http://www.example.com/index.html'), 'http://www.example.com/', 'getFolderPathFromURL - url ends with file');
+    strictEqual(BrowserMapUtil.Url.getFolderPathFromURL('http://www.example.com/'), 'http://www.example.com/', 'getFolderPathFromURL - url ends with /');
+    strictEqual(BrowserMapUtil.Url.getFolderPathFromURL('http://www.example.com'), '', 'getFolderPathFromURL - url ends with TLD');
+    deepEqual(BrowserMapUtil.Url.getSelectorsFromURL('http://www.example.com/index.a.b.html'), ['a', 'b'], 'getSelectorsFromURL - two selectors');
+    deepEqual(BrowserMapUtil.Url.getSelectorsFromURL('http://www.example.com/index.html'), [], 'getSelectorsFromURL - no selectors');
+    strictEqual(BrowserMapUtil.Url.addSelectorsToURL('http://www.example.com/index.html', ['a', 'b']), 'http://www.example.com/index.a.b.html', 'addSelectorsToURL - two selectors');
+    strictEqual(BrowserMapUtil.Url.addSelectorsToURL('http://www.example.com/index.html', []), 'http://www.example.com/index.html', 'addSelectorsToURL - no selectors');
+    strictEqual(BrowserMapUtil.Url.getCanonicalURL(), 'http://www.example.com/index.html', 'getCanonicalURL');
+});
+
+module('Array.indexOf polyfill');
+test('Array.indexOf', function() {
+    ok(!!Array.prototype.indexOf, 'Array.indexOf is defined');
+});
+
+module('BrowserMap');
+test("getAllAlternateSites", function() {
+    var alternateSites = [
+        {href: 'http://www.example.com/en/index.html', hreflang : 'en', devgroups : 'browser', id : ''},
+        {href: 'http://www.example.com/de/index.html', hreflang : 'de', devgroups : 'browser', id : ''},
+        {href: 'http://www.example.com/de/index.smartphone.html', hreflang : 'de', devgroups : 'smartphone', id : ''},
+        {href: 'http://www.example.com/en/index.smartphone.html', hreflang : 'en', devgroups : 'smartphone', id : ''}
+    ];
+    deepEqual(BrowserMap.getAllAlternateSites(), alternateSites);
+});
+test("getAlternateSite", function() {
+    var filter = function(link) {return link.hreflang == 'de'};
+    deepEqual(BrowserMap.getAlternateSite(['browser'], filter), {href: 'http://www.example.com/de/index.html', hreflang : 'de', devgroups : 'browser', id : ''});
+});
+test("getDeviceGroupsInRankingOrder", function() {
+    var expectedDgs = [
+        {ranking: 0, testFunction : function() {}, name : 'smartphone'},
+        {ranking: 10, testFunction : function() {}, name : 'tablet'},
+        {ranking: 20, testFunction : function() {}, name : 'highResolutionDisplay'},
+        {ranking: 30, testFunction : function() {}, name : 'browser'},
+        {ranking: Number.MAX_VALUE, testFunction: function() {}, name : 'oldBrowser'}
+    ];
+    var dgs = BrowserMap.getDeviceGroupsInRankingOrder();
+    for (var i = 0; i < expectedDgs.length; i++) {
+        strictEqual(dgs[i].name, expectedDgs[i].name);
+        strictEqual(dgs[i].ranking, expectedDgs[i].ranking);
+    }
+});
+test("probe", function() {
+    equal(null, BrowserMap.probe('nothingHere'));
+    equal('number', typeof BrowserMap.probe('clientWidth'));
+});
+test("getNewURL", function() {
+    strictEqual(BrowserMap.getNewURL('http://www.example.com/en/index.html', ['smartphone'], ['smartphone']), 'http://www.example.com/en/index.smartphone.html');
+    // assume fallback to selectors-based URL even if no variant is present
+    strictEqual(BrowserMap.getNewURL('http://www.example.com/en/index.html', ['tablet'], ['tablet']), 'http://www.example.com/en/index.tablet.html');
+});
+test("getCurrentVariant", function() {
+    deepEqual(BrowserMap.getCurrentVariant(), {href: 'http://www.example.com/en/index.html', hreflang : 'en', devgroups : 'browser', id : ''});
+});
+test("isEnabled", function() {
+    strictEqual(BrowserMap.isEnabled(), false);
+    // now disable BrowserMap by removing the meta tag and call BrowserMap.isEnabled() once more
+    var headElement = document.getElementsByTagName('head')[0],
+        metaTags,
+        i,
+        tag,
+        name;
+    if (headElement) {
+        metaTags = headElement.getElementsByTagName('meta');
+        for (i = 0; i < metaTags.length; i++) {
+            if ((tag = metaTags[i]) && (name = tag.getAttribute('name'))) {
+                if (name === 'browsermap.enabled' && tag.getAttribute('content') === 'false') {
+                    headElement.removeChild(tag);
+                    strictEqual(BrowserMap.isEnabled(), true);
+                    break;
+                }
+            }
+        }
+    }
+    // re-add the removed tag
+    if (tag) {
+        headElement.appendChild(tag);
+    }
+});