﻿/*  Loki Javascript API
 *
 *  This is a helper script to help you detect and gracefully handle
 *  users with Loki Plugin installed
 *
 * $Id: loki.js.in 6369 2010-01-19 17:02:09Z lyurchenko $
 *
 * Copyright (C) 2005-2009 Skyhook Wireless, Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted subject to the following:
 *
 * * Use and redistribution is subject to the Software License and Development
 * Agreement, available at
 * <a href="http://www.skyhookwireless.com">www.skyhookwireless.com</a>
 *
 * * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/////////////////////////////////////////////////////////////////////////////////////////
// Version
/////////////////////////////////////////////////////////////////////////////////////////

LokiAPI.availableVersion = "3.4.2.20";
LokiAPI.scriptRevision = "14";
LokiAPI.installedVersion = null; // Will be filled by isInstalled()

/////////////////////////////////////////////////////////////////////////////////////////
// Loki API wrapper
/////////////////////////////////////////////////////////////////////////////////////////

function LokiAPI()
{
    return Try.these(
                     function() {return new LokiPlugin()},
                     function() {return new LokiNull()}
                    ) || false;
}

LokiAPI.isInstalled = function()
{
    return LokiPlugin.isInstalled();
}

LokiAPI.isUpgradeAvailable = function()
{
    // May be isInstalled wasn't called yet. 
    // Call to ensure that LokiAPI.installedVersion is filled.
    if (!LokiAPI.installedVersion)
        LokiAPI.isInstalled(); 

    if (!LokiAPI.availableVersion)
        return false;

    if (!LokiAPI.installedVersion)
        return true;

    return (compareVersions(LokiAPI.installedVersion, LokiAPI.availableVersion) < 0);
}

LokiAPI.startUpgrade = function()
{
    var lokiForUpgrade = new LokiAPI();
    lokiForUpgrade.runUpgrade();
}

/////////////////////////////////////////////////////////////////////////////////////////
// LokiPlugin class, common parts
/////////////////////////////////////////////////////////////////////////////////////////

function LokiPlugin()
{
    if (LokiPlugin.isInstalled())
        LokiPlugin.initPlugin();
    else
        this.tryToInstallPlugin();
}

LokiPlugin.isRunning = function()
{
    if (   LokiPlugin.activex == undefined
        && LokiPlugin.plugin == undefined
        && LokiPlugin.xpcom == undefined)
    {
        if (LokiPlugin.isInstalled())
            LokiPlugin.initPlugin();

        return false;
    }

    return true;
}

LokiPlugin.isInstalled = function()
{
    switch (BrowserDetect.browser)
    {
        case "Explorer":
            return LokiPlugin.isInstalled_IE();

        case "Firefox":
            {
                try
                {
                    var lokixpcom = new Loki();
                    if (lokixpcom)
                    {
                        LokiPlugin.xpcom = lokixpcom;
                        return true;
                    }
                } catch (e) {}
            }
            /* FALLTHROUGH */
        case "Opera":
        case "Safari":
        case "Chrome":
            return LokiPlugin.isInstalled_NPAPI();

        default:
            return false;
    }
}

LokiPlugin.initPlugin = function()
{
    LokiPlugin.installationFinished();

    //If LokiPlugin.xpcom is set, then xpcom is installed, inited and should be used
    if (LokiPlugin.xpcom != null)
        return;

    switch (BrowserDetect.browser)
    {
        case "Explorer":
            LokiPlugin.init_IE();
            break;

        case "Firefox":
        case "Opera":
        case "Safari":
        case "Chrome":
            LokiPlugin.init_NPAPI();
            break;
    }
}

LokiPlugin.browserSupported = function()
{
    // Here is complete list of well supported platforms and browsers
    // TODO add Konqueror/Flock support

    if ((BrowserDetect.OS != "Windows" &&
         BrowserDetect.OS != "Mac") ||
        (BrowserDetect.browser != "Explorer" &&
         BrowserDetect.browser != "Firefox" && 
         BrowserDetect.browser != "Safari" &&
         BrowserDetect.browser != "Chrome" &&
         BrowserDetect.browser != "Opera"))
    {
        return false;
    }

    return true;
}

/////////////////////////////////////////////////////////////////////////////////////////
// Main interface functions
/////////////////////////////////////////////////////////////////////////////////////////

LokiPlugin.prototype.runUpgrade = function()
{
    var newReq = new Object();

    newReq.type = LokiPlugin.REQUEST_UPGRADE;
    newReq.lokiInstance = this;
    newReq.waitingResponce = false;
   
    LokiPlugin.addRequestToQueue(newReq);
}

LokiPlugin.prototype.showManageDomains = function()
{
    var newReq = new Object();

    newReq.type = LokiPlugin.REQUEST_MANAGE_DOMAINS;
    newReq.lokiInstance = this;
    newReq.waitingResponce = false;
   
    LokiPlugin.addRequestToQueue(newReq);
}

LokiPlugin.prototype.reverseGeocode = function(lat, lon, addressLookup)
{
    var newReq = new Object();

    newReq.type = LokiPlugin.REQUEST_REVGEO;
    newReq.lokiInstance = this;
    newReq.waitingResponce = false;
    newReq.lat = lat;
    newReq.lon = lon;
    newReq.addressLookup = (addressLookup != undefined) ? addressLookup : 0;
   
    LokiPlugin.addRequestToQueue(newReq);
}

LokiPlugin.prototype.tuneLocation = function(lat, lon)
{
    var newReq = new Object();

    newReq.type = LokiPlugin.REQUEST_TUNE;
    newReq.lokiInstance = this;
    newReq.waitingResponce = false;
    newReq.lat = lat;
    newReq.lon = lon;
   
    LokiPlugin.addRequestToQueue(newReq);
}

LokiPlugin.prototype.requestLocation = function(latlon, addressLookup)
{
    this.requestLocationBy(false, latlon, addressLookup);
}

LokiPlugin.prototype.requestIPLocation = function(latlon, addressLookup)
{
    this.requestLocationBy(true, latlon, addressLookup);
}

LokiPlugin.prototype.requestLocationBy = function(IP, latlon, addressLookup)
{
    var newReq = new Object();

    newReq.type = LokiPlugin.REQUEST_LOCATION;
    newReq.lokiInstance = this;
    newReq.waitingResponce = false;
    newReq.IP = IP;
    newReq.latlon = (latlon != undefined) ? latlon : false;
    newReq.addressLookup = (addressLookup != undefined) ? addressLookup : 0;
   
    LokiPlugin.addRequestToQueue(newReq);
}

LokiPlugin.prototype.requestPeriodicLocation = function(addressLookup, period, iterations)
{
    var newReq = new Object();

    newReq.type = LokiPlugin.REQUEST_PERIODIC_LOCATION;
    newReq.lokiInstance = this;
    newReq.waitingResponce = false;
    newReq.addressLookup = (addressLookup != undefined) ? addressLookup : 0;
    newReq.period = (period != undefined) ? period : 1000;
    newReq.iterations = (iterations != undefined) ? iterations : 0;

    LokiPlugin.addRequestToQueue(newReq);
    
    // Returns request object that can be used to identify periodic location request when cancelling
    return newReq;
}

LokiPlugin.prototype.cancelPeriodicLocation = function(periodicLocationHandler)
{
    if (periodicLocationHandler == undefined ||
        periodicLocationHandler == null ||
        this != periodicLocationHandler.lokiInstance)
    {
        return;
    };

    if (LokiPlugin.currentRequest != periodicLocationHandler)
    {
        // remove one pending request from queue
        LokiPlugin.removeRequest(periodicLocationHandler);
        return;
    }

    
    if (BrowserDetect.browser == "Explorer")
        this.cancelPeriodicLocation_IE();
    else
        this.cancelPeriodicLocation_NPAPI();
}

/////////////////////////////////////////////////////////////////////////////////////////
// Queue processing functions
/////////////////////////////////////////////////////////////////////////////////////////

LokiPlugin.continueTicking = function()
{
    if (LokiPlugin.reqTickTimer)
        clearTimeout(LokiPlugin.reqTickTimer);
    LokiPlugin.reqTickTimer = setTimeout(LokiPlugin.reqTick, 100);
}

LokiPlugin.addRequestToQueue = function(newReq)
{
    LokiPlugin.requestsQueue.push(newReq);

    if (!LokiPlugin.reqTickTimer)
        LokiPlugin.continueTicking();
}

LokiPlugin.removeRequest = function(request)
{
    for (var i = 0; i < LokiPlugin.requestsQueue.length; i++)
    {
        if (LokiPlugin.requestsQueue[i] == request)
        {
            LokiPlugin.requestsQueue.splice(i, 1);
            return;
        }
    }
}

LokiPlugin.reqTick = function()
{
    // Skip ticks while plugin is not inited yet
    if (!LokiPlugin.isRunning())
    {
        LokiPlugin.continueTicking();
        return;
    }

    if (!LokiPlugin.currentRequest && LokiPlugin.requestsQueue.length > 0)
        LokiPlugin.currentRequest = LokiPlugin.requestsQueue.shift();

    if (LokiPlugin.currentRequest)
    {
        LokiPlugin.processCurrReq();
        LokiPlugin.continueTicking();
        return;
    }

    // Do not continue ticking
    clearInterval(LokiPlugin.reqTickTimer);
    LokiPlugin.reqTickTimer = null;
}

LokiPlugin.processCurrReqCommonErrors = function()
{
    var errCode = LokiPlugin.WPS_OK;

    if (!LokiPlugin.browserSupported())
        errCode = LokiPlugin.WPS_ERROR_PLUGIN_BROWSER_NOT_SUPPORTED;

    if (LokiPlugin.failedInstall)
        errCode = LokiPlugin.WPS_ERROR_PLUGIN_COULD_NOT_BE_INSTALLED;

    if (LokiPlugin.pluginDisabled)
        errCode = LokiPlugin.WPS_ERROR_PLUGIN_DISABLED;

    if (LokiPlugin.xpcom != null &&
        LokiPlugin.currentRequest.type != LokiPlugin.REQUEST_LOCATION)
        errCode = LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED;

    if (errCode != LokiPlugin.WPS_OK)
    {
        var failProxy;
        switch (LokiPlugin.currentRequest.type)
        {
            case LokiPlugin.REQUEST_LOCATION:
            case LokiPlugin.REQUEST_MANAGE_DOMAINS:
            case LokiPlugin.REQUEST_UPGRADE:
                failProxy = LokiPlugin.currentRequest.lokiInstance.onFailureProxy;
                break;
            case LokiPlugin.REQUEST_REVGEO:
                failProxy = LokiPlugin.currentRequest.lokiInstance.onReverseGeocodedProxy;
                break;
            case LokiPlugin.REQUEST_TUNE:
                failProxy = LokiPlugin.currentRequest.lokiInstance.onTuneCompletedProxy;
                break;
            case LokiPlugin.REQUEST_PERIODIC_LOCATION:
                failProxy = LokiPlugin.currentRequest.lokiInstance.onPeriodicLocationEndProxy;
                break;
        }

        failProxy(errCode);
    }

    return errCode;
}

LokiPlugin.processCurrReq = function()
{
    if (LokiPlugin.processCurrReqCommonErrors() != LokiPlugin.WPS_OK)
    {
        LokiPlugin.currentRequest = null;
        return;
    }

    // Ticks request if it was allready launched. Launches otherwise.

    if (LokiPlugin.currentRequest.waitingResponce)
    {
        if (BrowserDetect.browser != "Explorer" && LokiPlugin.xpcom == null)
        {
            // NPAPI plugin version requires ticking from JS
            LokiPlugin.tickPluginRequestForNPAPI();
        }
        return;
    }

    switch(LokiPlugin.currentRequest.type)
    {
        case LokiPlugin.REQUEST_LOCATION:
            LokiPlugin.currentRequest.waitingResponce = true;
            LokiPlugin.currentRequest.lokiInstance.runReqRequestLocation(
                LokiPlugin.currentRequest.IP, 
                LokiPlugin.currentRequest.latlon,
                LokiPlugin.currentRequest.addressLookup);
            break;
        case LokiPlugin.REQUEST_REVGEO:
            LokiPlugin.currentRequest.waitingResponce = true;
            LokiPlugin.currentRequest.lokiInstance.runReqReverseGeocode(
                LokiPlugin.currentRequest.lat, 
                LokiPlugin.currentRequest.lon,
                LokiPlugin.currentRequest.addressLookup);
            break;
        case LokiPlugin.REQUEST_TUNE:
            LokiPlugin.currentRequest.waitingResponce = true;
            LokiPlugin.currentRequest.lokiInstance.runReqTuneLocation(
                LokiPlugin.currentRequest.lat, 
                LokiPlugin.currentRequest.lon);
            break;
        case LokiPlugin.REQUEST_MANAGE_DOMAINS:
            LokiPlugin.currentRequest.lokiInstance.runReqShowManageDomains();
            LokiPlugin.currentRequest = null; // request processing complete
            break;
        case LokiPlugin.REQUEST_UPGRADE:
            LokiPlugin.currentRequest.lokiInstance.runRunUpgrade();
            LokiPlugin.currentRequest = null; // request processing complete
            break;
        case LokiPlugin.REQUEST_PERIODIC_LOCATION:
            LokiPlugin.currentRequest.waitingResponce = true;
            LokiPlugin.currentRequest.lokiInstance.runReqPeriodicLocation(
                LokiPlugin.currentRequest.addressLookup,
                LokiPlugin.currentRequest.period,
                LokiPlugin.currentRequest.iterations);
            break;
    }
}

LokiPlugin.currentRequestCompleted = function(location_rc, locationToCache)
{
    if (location_rc != undefined)
    {
        var newLocCacheEntry = new Object;
        newLocCacheEntry.utcTimestamp = new Date().getTime();
        newLocCacheEntry.IP = LokiPlugin.currentRequest.IP;
        newLocCacheEntry.latlon = LokiPlugin.currentRequest.latlon;
        newLocCacheEntry.addressLookup = LokiPlugin.currentRequest.addressLookup;
        newLocCacheEntry.rc = location_rc;
    
        if (locationToCache)
            newLocCacheEntry.location = locationToCache;
    
        LokiPlugin.locationCache.push(newLocCacheEntry);
    }
    
    LokiPlugin.currentRequest = null;
}

LokiPlugin.locationCacheLookup = function(IP, latlon, addressLookup, rc, maximumAge)
{
    for (var i = 0; i < LokiPlugin.locationCache.length; i++)
    {
        // Zero or null max age means no limitation for age
        if (maximumAge != undefined)
        {
            var entryAge = new Date().getTime() - LokiPlugin.locationCache[i].utcTimestamp;
            if (entryAge > maximumAge)
            {
                LokiPlugin.locationCache.splice(i, 1);
                i--;
                continue;
            }
        }
    
        // Filter by result code
        if (rc != undefined)
            if (LokiPlugin.locationCache[i].rc != rc)
                continue;

        // Looking for same request
        if (LokiPlugin.locationCache[i].IP == IP &&
            LokiPlugin.locationCache[i].latlon == latlon &&
            LokiPlugin.locationCache[i].addressLookup >= addressLookup)
        {
            return LokiPlugin.locationCache[i];
        }
    }
    
    return null;
}


/////////////////////////////////////////////////////////////////////////////////////////
// Launching requests functions
/////////////////////////////////////////////////////////////////////////////////////////

LokiPlugin.prototype.runRunUpgrade = function()
{
    var distrURL = LokiPlugin.getDistributiveUrl(false);

    // Try to run upgrade from native code if available. Run 
    // common js/java upgrade otherwise
    var nativeUpgradeStarted;
    if (BrowserDetect.browser == "Explorer")
        nativeUpgradeStarted = this.runUpgrade_IE(distrURL);
    else
        nativeUpgradeStarted = this.runUpgrade_NPAPI(distrURL);

    if (!nativeUpgradeStarted)
        this.tryToInstallPlugin();
}

LokiPlugin.prototype.runReqShowManageDomains = function()
{
    if (BrowserDetect.browser == "Explorer")
        this.showManageDomains_IE();
    else
        this.showManageDomains_NPAPI();
}

LokiPlugin.prototype.runReqReverseGeocode = function(lat, lon, addressLookup)
{
    this.lastLatArg = lat;
    this.lastLonArg = lon;
    this.lastAddressLookupArg = addressLookup;
    
    if (BrowserDetect.browser == "Explorer")
        this.reverseGeocoding_IE(lat, lon, addressLookup);
    else
        this.reverseGeocoding_NPAPI(lat, lon, addressLookup);
}

LokiPlugin.prototype.runReqTuneLocation = function(lat, lon)
{
    if (BrowserDetect.browser == "Explorer")
        this.tuneLocation_IE(lat, lon);
    else
        this.tuneLocation_NPAPI(lat, lon);
}

LokiPlugin.prototype.runReqRequestLocation = function(IP, latlon, addressLookup)
{
    if (latlon == undefined)
        latlon = false;
    if (addressLookup == undefined)
        addressLookup = LokiPlugin.NO_STREET_ADDRESS_LOOKUP;

    // Look inside cache
    var cachedEntry = LokiPlugin.locationCacheLookup(IP, latlon, addressLookup);
    if (cachedEntry)
    {
        if (cachedEntry.rc == 0)
        {
            if (this.onSuccess)
                this.onSuccess(cachedEntry.location);
        }
        else
        {
             if (this.onFailure)
                this.onFailure(cachedEntry.rc, LokiPlugin.returnMessages[cachedEntry.rc]);
        }
        LokiPlugin.currentRequestCompleted();
        return;
    }

    // No cache entry, run request
    this.lastAddressLookupArg = addressLookup;
   
    if (LokiPlugin.xpcom != null)
        this.runRequestLocation_XPCOM(IP, latlon, addressLookup);
    else
    if (BrowserDetect.browser == "Explorer")
        this.runRequestLocation_IE(IP, latlon, addressLookup);
    else
        this.runRequestLocation_NPAPI(IP, latlon, addressLookup);
}

LokiPlugin.prototype.runReqPeriodicLocation = function(addressLookup, period, iterations)
{
    if (addressLookup == undefined)
        addressLookup = LokiPlugin.NO_STREET_ADDRESS_LOOKUP;

    if (period == undefined)
        period = 1000;

    if (iterations == undefined)
        iterations = 1000;

    this.lastAddressLookupArg = addressLookup;

    if (BrowserDetect.browser == "Explorer")
        this.runReqPeriodicLocation_IE(addressLookup, period, iterations);
    else
        this.runReqPeriodicLocation_NPAPI(addressLookup, period, iterations);
}

LokiPlugin.prototype.googleRevLocCB = function(addresses, callback) 
{
    if(addresses.Status.code != 200) 
    {
        var result = new Object();
        result.returnCode = -1;
        callback(result);
    }
    else 
    {
        var country_code = "";
        var country_name = "";
        var region_code = "";
        var region_name = "";
        var city = "";
        var street_address = "";
        var postal_code = "";
        var newLat = "";
        var newLon = "";
        var county = "";

        try {
            var place = addresses.Placemark[0];
            newLat = place.Point.coordinates[1];
            newLon = place.Point.coordinates[0];
        } catch (e) { /* drop exception, continue getting info */ };

        try {
            country_code = place.AddressDetails.Country.CountryNameCode;
            country_name = place.AddressDetails.Country.CountryName;
        } catch (e) { /* drop exception, continue getting info */ };

        try {
            // For US it returns region code (MA) 
            region_code = place.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName;
            region_name = place.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName;                
            // For britain it returns county
            county = place.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName;
        } catch (e) { /* drop exception, continue getting info */ };

        var localityParent;
        try {
            localityParent = place.AddressDetails.Country.AdministrativeArea;

            if (place.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea)
                localityParent = place.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea;

        } catch (e) {/* drop exception, continue getting info */}

        try {
            city = localityParent.Locality.LocalityName;
        } catch (e) { /* drop exception, continue getting info */ };

        var locality;
        try {
            locality = localityParent.Locality;
            if (localityParent.Locality.DependentLocality)
                locality = localityParent.Locality.DependentLocality;
        } catch (e) { /* drop exception, continue getting info */ };

        try {
            street_address = locality.Thoroughfare.ThoroughfareName;
        } catch (e) { /* drop exception, continue getting info */ };

        try {
            postal_code = locality.PostalCode.PostalCodeNumber;
        } catch (e) { /* drop exception, continue getting info */ };

        var result = new Object();
        result.returnCode = 0;
        result.latitude = newLat;
        result.longitude = newLon;
        result.country_code = country_code;
        result.country = country_name;
        result.region_code = region_code;
        result.region = region_name;
        result.city = city;
        result.county = county;
        result.street = street_address;
        result.postal_code = postal_code;

        // No info for fields:
        result.house_number = "";

        callback(result);
    }
}


LokiPlugin.prototype.fallbackToGoogleRevGeo = function(callback)
{
    var self = this;
    var geocoder = new GClientGeocoder();
    var latlng = new GLatLng(this.lastLatArg, this.lastLonArg);

    if (!geocoder || !latlng)
    {
        var result = new Object();
        result.returnCode = -1;
        callback(result);
    }

    geocoder.getLocations(latlng, function(addresses) { self.googleRevLocCB(addresses, callback) });
}

LokiPlugin.prototype.onReverseGeocodedProxy = function(result, fromFallback)
{
    if ((result && result.returnCode == 0) || fromFallback || typeof(window["G_API_VERSION"]) == "undefined")
    {
        if (this.onReverseGeocoded != undefined)
            this.onReverseGeocoded(result);

        LokiPlugin.currentRequestCompleted();
    }
    else
    {
        var self = this;
        this.fallbackToGoogleRevGeo( function(geocoderesult) { self.onReverseGeocodedProxy(geocoderesult, true) } );
    }
}

LokiPlugin.prototype.onTuneCompletedProxy = function(errcode)
{
    if (this.onTuneCompleted != undefined)
        this.onTuneCompleted(errcode, LokiPlugin.returnMessages[errcode]);
    LokiPlugin.currentRequestCompleted();
}

LokiPlugin.prototype.onFailureProxy = function(errcode)
{
    if (this.onFailure != undefined)
        this.onFailure(errcode, LokiPlugin.returnMessages[errcode]);
    LokiPlugin.currentRequestCompleted(errcode);
}

LokiPlugin.prototype.onSuccessProxy = function(location, fromFallback)
{
    var shouldGetAddress = true;
    if (fromFallback || typeof(window["G_API_VERSION"]) == "undefined")
        shouldGetAddress = false;
    else if (this.lastAddressLookupArg == LokiPlugin.NO_STREET_ADDRESS_LOOKUP)
        shouldGetAddress = false;
    else if (location.city && location.city != "")
        shouldGetAddress = false;
    
    if (shouldGetAddress)
    {
        this.lastLatArg = location.latitude;
        this.lastLonArg = location.longitude;
        
        var self = this;
        this.fallbackToGoogleRevGeo( function(geocoderesult) { self.onSuccessProxy(geocoderesult, true) } );
        
        return;
    }
    
    if (!fromFallback)
    {
        if (this.onSuccess != undefined)
            this.onSuccess(location);
        LokiPlugin.currentRequestCompleted(0, location);
        return;
    }

    // This is from fallback, no matter what retcode google rev geo returned
    location.returnCode = 0;
    
    // Restore lat/lon that was adjusted by google reverse geocoding
    location.latitude = this.lastLatArg;
    location.longitude = this.lastLonArg;
    
    // Remove unwanted information if it was not requested
    if (this.lastAddressLookupArg != LokiPlugin.FULL_STREET_ADDRESS_LOOKUP)
        location.street = ""; // Google reverse geo contains house numbes in street field
    
    if (this.onSuccess != undefined)
        this.onSuccess(location);
    LokiPlugin.currentRequestCompleted(0, location);
}

LokiPlugin.prototype.onPeriodicLocationProxy = function(location)
{
    if (this.onPeriodicLocation != undefined)
        this.onPeriodicLocation(location);

    // Do not call currentRequestCompleted, because an iteration continues
    // currentRequestCompleted will be called from onPeriodicLocEndedProxy
}

LokiPlugin.prototype.onPeriodicLocationEndProxy = function(rc)
{
    // Plugin versions without turned on private location determination 
    // are returning LokiPlugin.WPS_ERROR when trying to launch periodic location.

    if (rc == LokiPlugin.WPS_ERROR)
        rc = LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED;

    if (this.onPeriodicLocationEnd != undefined)
        this.onPeriodicLocationEnd(rc, LokiPlugin.returnMessages[rc]);

    LokiPlugin.currentRequestCompleted();
}


/////////////////////////////////////////////////////////////////////////////////////////
// LokiPlugin class, Internet Explorer specific parts
/////////////////////////////////////////////////////////////////////////////////////////

LokiPlugin.isInstalled_IE = function()
{
    if (LokiPlugin.toolbarDetected)
        return true;

    var loki;

    if (LokiPlugin.activex != null)
        loki = LokiPlugin.activex;
    else
    {
        try
        {
            loki = new ActiveXObject("Loki.LocationFinder.1");
        }
        catch (e)
        {
            return false;
        }

        if (!loki)
            return false;
    }

    LokiAPI.pluginDescription = loki.description;
    LokiAPI.installedVersion = LokiAPI.pluginDescription.substring(
        LokiAPI.pluginDescription.indexOf("v.") + 2);

    return true;
}

LokiPlugin.init_IE = function()
{
    LokiPlugin.activex = new ActiveXObject("Loki.LocationFinder.1");
}

LokiPlugin.prototype.reverseGeocoding_IE = function(lat, lon, addressLookup)
{
    try
    {
        var self = this;
        LokiPlugin.activex.onReverseGeocoded = function(arg1) { self.onReverseGeocodedProxy(arg1) };

        if ("" != this.getKey())
            LokiPlugin.activex.setKey(this.getKey());
    
        LokiPlugin.activex.reverseGeocoding(lat, lon, addressLookup);
    } catch (e)
    {
        this.onReverseGeocodedProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
    }
}

LokiPlugin.prototype.showManageDomains_IE = function()
{
    try
    {
        LokiPlugin.activex.showManageDomains();
    } catch (e)
    {
        this.onFailureProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
    }
}

LokiPlugin.prototype.runUpgrade_IE = function(distrUrl)
{
    try
    {
        LokiPlugin.activex.runUpgrade(distrUrl);
        return true;
    } catch (e)
    {}
    
    return false;
}

LokiPlugin.prototype.runRequestLocation_IE = function(IP, latlon, addressLookup)
{
    try
    {
        var self = this;

        if ("" != this.getKey())
            LokiPlugin.activex.setKey(this.getKey());
    
        LokiPlugin.activex.onSuccess = function(arg1) { self.onSuccessProxy(arg1) };
        LokiPlugin.activex.onFailure = function(arg1) { self.onFailureProxy(arg1) };
    
        if (IP)
            LokiPlugin.activex.requestIPLocation(latlon, addressLookup);
        else
            LokiPlugin.activex.requestLocation(latlon, addressLookup);
    } catch (e)
    {
        this.onFailureProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
    }
}

LokiPlugin.prototype.runReqPeriodicLocation_IE = function(addressLookup, period, iterations)
{
    try
    {
        var self = this;

        if ("" != this.getKey())
            LokiPlugin.activex.setKey(this.getKey());

        LokiPlugin.activex.onPeriodicLocation = function(arg1) { self.onPeriodicLocationProxy(arg1) };
        LokiPlugin.activex.onPeriodicLocationEnd = function(arg1) { self.onPeriodicLocationEndProxy(arg1) };
        LokiPlugin.activex.requestPeriodicLocation(addressLookup, period, iterations);
    } catch (e)
    {
        this.onPeriodicLocationEndProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
    }
    
}

LokiPlugin.prototype.cancelPeriodicLocation_IE = function()
{
    try
    {
        LokiPlugin.activex.cancelPeriodicLocation();
    }
    catch (e)
    {}
}

LokiPlugin.prototype.tuneLocation_IE = function(lat, lon)
{
    try
    {
        if ("" != this.getKey())
            LokiPlugin.activex.setKey(this.getKey());

        var self = this;
        LokiPlugin.activex.onTuneCompleted = function(arg1) { self.onTuneCompletedProxy(arg1) };

        LokiPlugin.activex.tuneLocation(lat, lon);
    } catch (e)
    {
        this.onTuneCompletedProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
// LokiPlugin class, XPCOM specific parts
/////////////////////////////////////////////////////////////////////////////////////////

LokiPlugin.prototype.runRequestLocation_XPCOM = function(IP, latlon, addressLookup)
{
    if (IP)
    {
        this.onFailureProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
        return;
    }

    var self = this;

    LokiPlugin.xpcom.onSuccess = function(arg1) { self.onSuccessProxy(arg1) };
    LokiPlugin.xpcom.onFailure = function(arg1) { self.onFailureProxy(arg1) };

    if ("" != this.getKey())
        LokiPlugin.xpcom.setKey(this.getKey());
    
    try
    {
        LokiPlugin.xpcom.requestLocation(latlon, addressLookup);
    } catch (e)
    {
        this.onFailureProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
// LokiPlugin class, NPAPI specific parts
/////////////////////////////////////////////////////////////////////////////////////////

LokiPlugin.isInstalled_NPAPI = function()
{
    var deprecatedVersionIdx = -1;

    navigator.plugins.refresh(false);

    for (var i = 0; i < navigator.plugins.length; ++i)
    {
        plugin = navigator.plugins[i];

        if (plugin == undefined)
            continue;

        if (plugin.name == "Loki Plugin")
        {
            numTypes = plugin.length;
            LokiPlugin.pluginDisabled = true;
            for (j = 0; j < numTypes; j++) 
            {
                mimetype = plugin[j];
                if (mimetype.type == "application/x-loki") 
                {
                    enabledPlugin = mimetype.enabledPlugin;
                    if (enabledPlugin && (enabledPlugin.name == plugin.name))
                    {
                        LokiPlugin.pluginDisabled = false;
                        break;
                    }
                }
            }
            
            // Workaround for skip activeX plugin in Chrome. 
            // Chrome also detects activex plugin, but we should use npapi
            // TODO: Chrome can partially support activex plugin. May be we can use it 
            // if no npapi version installed.
            if (BrowserDetect.browser == "Chrome" && plugin.filename == "loki.dll")
                continue;
    
            LokiAPI.pluginDescription = plugin.description;
            LokiAPI.installedVersion = LokiAPI.pluginDescription.substring(
                LokiAPI.pluginDescription.indexOf("v.") + 2);

            return true;
        } 

        if (plugin.name == "FindMe Plugin" ||
            plugin.name == "Find Me Plugin")
        {
            deprecatedVersionIdx = i;
        }

    }

    if (-1 != deprecatedVersionIdx)
    {
        LokiAPI.pluginDescription = navigator.plugins[deprecatedVersionIdx].description;
        LokiAPI.installedVersion = LokiAPI.pluginDescription.substring(
            LokiAPI.pluginDescription.indexOf("v.") + 2);

        return true;
    }

    return false;
}

LokiPlugin.init_NPAPI = function()
{
    if (!LokiPlugin.isRunningPluginInstance)
    {
        var pluginAttributes =
        {
            id     : "__lokiPlugin",
            width  : "1",
            height : "1",
            type   : "application/x-loki"
        };

        var pluginDom = document.createElement("object");
        for (var x in pluginAttributes)
        {
            pluginDom.setAttribute(x, pluginAttributes[x]);
        }

        document.getElementsByTagName('body').item(0).appendChild(pluginDom);

        LokiPlugin.plugin = pluginDom;
        LokiPlugin.isRunningPluginInstance = true;
    }
}

LokiPlugin.prototype.showManageDomains_NPAPI = function()
{
    if (!LokiPlugin.plugin.showManageDomains)
    {
        this.onFailureProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
        return;
    }
    
    LokiPlugin.plugin.showManageDomains();
}

LokiPlugin.prototype.runUpgrade_NPAPI = function(distrURL)
{
    if (!LokiPlugin.plugin.runUpgrade)
        return false;

    LokiPlugin.plugin.runUpgrade(distrURL);
    return true;
}

LokiPlugin.prototype.reverseGeocoding_NPAPI = function(lat, lon, addressLookup)
{
    if (!LokiPlugin.plugin.asynchronousReverseGeoCode)
    {
        var result = new Object();
        result.returnCode = LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED;
        this.onReverseGeocodedProxy(result);
        return;
    }

    LokiPlugin.plugin.asynchronousReverseGeoCode(lat, lon, addressLookup);
}

LokiPlugin.prototype.tuneLocation_NPAPI = function(lat, lon)
{
    if (!LokiPlugin.plugin.asynchronousTuneLocation)
    {
        this.onTuneCompletedProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
        return;
    }

    LokiPlugin.plugin.asynchronousTuneLocation(lat, lon);
}

LokiPlugin.prototype.runRequestLocation_NPAPI = function(IP, latlon, addressLookup)
{
    if (!LokiPlugin.plugin.asynchronousRequestLocation)
    {
        this.onFailureProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
        return;
    }

    if (IP)
    {
        LokiPlugin.plugin.asynchronousRequestIPLocation(this.getKey(), latlon, addressLookup);
    }
    else
    {
        // Versions prior to 3.4 can't use WPS_locations in MacOSX 10.6 and later
        if ( BrowserDetect.OS == "Mac" &&
             compareVersions(BrowserDetect.OSVersion, "10.6") >= 0 &&
             compareVersions(LokiAPI.installedVersion, "3.4.0") < 0)
        {
            this.onFailureProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
            return;
        }

        LokiPlugin.plugin.asynchronousRequestLocation(this.getKey(), latlon, addressLookup);
    }
}

LokiPlugin.prototype.runReqPeriodicLocation_NPAPI = function(addressLookup, period, iterations)
{
    if (!LokiPlugin.plugin.asynchronousRequestPeriodicLocation)
    {
        this.onPeriodicLocationEndProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
        return;
    }

    var self = this;
    self.callbackObject = new Object();
    self.callbackObject.onPeriodicLocation = function(location)
    {
        self.onPeriodicLocationProxy(location);
    }

    LokiPlugin.plugin.asynchronousRequestPeriodicLocation(this.getKey(), self.callbackObject, addressLookup, period, iterations);
}

LokiPlugin.prototype.cancelPeriodicLocation_NPAPI = function()
{
    if (!LokiPlugin.plugin.cancelPeriodicLocation)
    {
        this.onPeriodicLocationEndProxy(LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED);
        return;
    }

    LokiPlugin.plugin.cancelPeriodicLocation();
}

LokiPlugin.tickPluginRequestForNPAPI = function()
{
    if (LokiPlugin.currentRequest.noMoreTicksForPlugin)
        return;

    var self = LokiPlugin.currentRequest.lokiInstance;
    var tickResult;

    // LokiPlugin.plugin.tickRunHttpRequest may be null if plugin installed, but can't be loaded (pre 3.4 plugins on SnowLeo)
    if (LokiPlugin.plugin.tickRunHttpRequest)
    {
        if (!LokiPlugin.plugin.tickRunHttpTuneRequest && !LokiPlugin.plugin.tickRunHttpRevGCRequest)
        {
            tickResult = LokiPlugin.plugin.tickRunHttpRequest();
        }
        else
        { 
            // Temporary workaround for supporting 3.1.1.x plugins with several tick methods
            // Should be removed when no 3.1.1.x (before .06) plugin will left in testing
            // Not needed by 3.1.0.18 and previous and by 3.1.1.06 and next
    
            switch (LokiPlugin.currentRequest.type)
            {
                case LokiPlugin.REQUEST_LOCATION:
                    tickResult = LokiPlugin.plugin.tickRunHttpRequest();
                    break;
                case LokiPlugin.REQUEST_REVGEO:
                    tickResult = LokiPlugin.plugin.tickRunHttpRevGCRequest();
                    break;
                case LokiPlugin.REQUEST_TUNE:
                    tickResult = LokiPlugin.plugin.tickRunHttpTuneRequest();
                    break;
                default:
                    tickResult = new Object;
                    tickResult.returnCode = LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED;
            }
        }
    }

    if (!tickResult || tickResult.returnCode == undefined)
        return;

    // Result from plugin received. Blocking further plugin ticking (Queue ticking still operable, and currentRequest still active)
    LokiPlugin.currentRequest.noMoreTicksForPlugin = true;

    // Process received result:
    switch (LokiPlugin.currentRequest.type)
    {
        case LokiPlugin.REQUEST_LOCATION:
            if (tickResult.returnCode != LokiPlugin.WPS_OK)
            {
                self.onFailureProxy(tickResult.returnCode);
                return;
            }
            self.onSuccessProxy(tickResult);
            break;
        case LokiPlugin.REQUEST_REVGEO:
            self.onReverseGeocodedProxy(tickResult);
            break;
        case LokiPlugin.REQUEST_TUNE:
            self.onTuneCompletedProxy(tickResult.returnCode);
            break;
        case LokiPlugin.REQUEST_PERIODIC_LOCATION:
            self.onPeriodicLocationEndProxy(tickResult.returnCode)
            break;
    }
}

LokiPlugin.prototype.setKey = function(key /*string*/)
{
    if (key == undefined || key == null)
        this.key = "";
    else
        this.key = key;
}

LokiPlugin.prototype.getKey = function()
{
    if (this.key != "")
        return this.key;

    gadgetName = lokiGetGadgetName();
    if (gadgetName && "" != gadgetName)
        return gadgetName.toLowerCase();

    return "";
}

/////////////////////////////////////////////////////////////////////////////////////////
// Install Plugin stuff
/////////////////////////////////////////////////////////////////////////////////////////


LokiPlugin.prototype.tryToInstallPlugin = function()
{
    if (!LokiPlugin.browserSupported())return;

    if (this.installationStarted()) return;

    if (BrowserDetect.javaAvail)
    {
        if (LokiAPI_PreloadNullapplet && !LokiPlugin.isInstalled())
        {
            if (  BrowserDetect.javaWaitingConfirmation &&
                ( BrowserDetect.browser == "Explorer" || 
                 (BrowserDetect.browser == "Safari" && BrowserDetect.OS == "Windows") ||
                 (BrowserDetect.browser == "Chrome" && BrowserDetect.OS == "Windows") ||
                 (BrowserDetect.browser == "Firefox" && LokiPlugin.javaPluginDescription.indexOf('1.4.') != -1)
                )
               )
            {
                // Internet Explorer and Safari requires confirmation that java is available. callback from nullapplet used
                var nullappletUptime = (new Date()).getTime() - BrowserDetect.javaWaitingConfirmationSince;
                if (nullappletUptime < LokiPlugin.fallbackToNativeTimeout)
                {
                    LokiPlugin.nullappletShouldRunInstaller = true;
                    setTimeout(fallbackToNativeInstaller, LokiPlugin.fallbackToNativeTimeout - nullappletUptime);
                } 
                else
                {
                    fallbackToNativeInstaller();
                }
            }
            else
            {
                LokiPlugin.startInstallApplet();
            }
        }
        else
        {
            if ( BrowserDetect.browser == "Explorer" || 
                (BrowserDetect.browser == "Safari" && BrowserDetect.OS == "Windows") ||
                (BrowserDetect.browser == "Chrome" && BrowserDetect.OS == "Windows") ||
                (BrowserDetect.browser == "Firefox" && LokiPlugin.javaPluginDescription.indexOf('1.4.') != -1)
               )
            {
                //Internet Explorer and Safari requires confirmation that java is available. callback from nullapplet used
                LokiPlugin.nullappletShouldRunInstaller = true;
                LokiPlugin.runNullapplet();
                setTimeout(fallbackToNativeInstaller, LokiPlugin.fallbackToNativeTimeout);
            }
            else
            {
                LokiPlugin.startInstallApplet();
            }
        }
    }
    else
    {
        LokiPlugin.downloadNativeInstaller();
    }
}

LokiPlugin.downloadNativeInstaller = function()
{
    if (BrowserDetect.browser == "Explorer" && !window.XMLHttpRequest)
    {
        // IE6 workaround
        var downloadDiv = document.createElement("div");
        document.getElementsByTagName('body').item(0).appendChild(downloadDiv);
        downloadDiv.innerHTML = '<iframe src="' + LokiPlugin.getDistributiveUrl(true) + '"></iframe>';
    }
    else
    {
        document.location.href = LokiPlugin.getDistributiveUrl(true);
    }
}

LokiPlugin.getDistributiveUrl = function(forManualInstall)
{
    switch(BrowserDetect.OS)
    {
        case "Windows":
            if (BrowserDetect.browser == "Explorer")
            {
                if (window.navigator.cpuClass == "x64")
                    return LokiPlugin.globalURLPrefix + "loki_activex_x64.exe"
                else
                    return LokiPlugin.globalURLPrefix + "loki_activex.exe"
            }
            else
                return LokiPlugin.globalURLPrefix + "loki_setup.exe";

        case "Mac":
            if (forManualInstall)
                return LokiPlugin.globalURLPrefix + "LokiPlugin.zip";
            else
                return LokiPlugin.globalURLPrefix + "LokiPlugin_AppletInst_Mac.tgz";

        case "Linux":
                return LokiPlugin.globalURLPrefix + "LokiPlugin_Installer.sh";

    }
}

// This function is called from an install applet and can be renamed only with renaming also in applet
function appletInstallationSuccessfull()
{
    LokiPlugin.upgradeCompletedSuccessful = true;
}

function fallbackToNativeInstaller()
{
    if (BrowserDetect.javaWaitingConfirmation)
    {
        BrowserDetect.javaAvail = false;
        LokiPlugin.nullappletShouldRunInstaller = false;
        LokiPlugin.downloadNativeInstaller();
    }
}

// This function is called from an install applet and can be renamed only with renaming also in applet
function appletInstallationFailed()
{
    LokiPlugin.downloadNativeInstaller();
}

LokiPlugin.startInstallApplet = function()
{
    var appletDiv = document.createElement("div");
    var codebase_par = LokiPlugin.useGlobalURLs ?  '<PARAM NAME="archive" VALUE="' + LokiPlugin.globalURLPrefix + 'LokiApplet.jar"/>' : '';

    document.getElementsByTagName('body').item(0).appendChild(appletDiv);

    var globalUrlParameter = LokiPlugin.useGlobalURLs ?
                                 '<PARAM NAME="globalUrlPrefix" VALUE="' + LokiPlugin.globalURLPrefix + '">'
                                 : '';

    if (BrowserDetect.browser == "Explorer")
        appletDiv.innerHTML = '<OBJECT id="LokiApplet" classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" WIDTH="0" HEIGHT="0">'
                              + codebase_par
                              + '<PARAM NAME="CODE" VALUE="LokiApplet.class"/>'
                              + globalUrlParameter
                              + '<PARAM NAME="scriptable" VALUE="true"/></OBJECT>';
    else
        appletDiv.innerHTML = '<object classid="java:LokiApplet.class" type="application/x-java-applet" code="LokiApplet.class" archive="' + LokiPlugin.globalURLPrefix + 'LokiApplet.jar" width=0 height=0><PARAM NAME="MAYSCRIPT" VALUE="true">'
                              + globalUrlParameter
                              + '</object>';


}

/////////////////////////////////////////////////////////////////////////////////////////
// Wrappers for watching install process (allowing setting timeouts for them) and 
// operation process. 
/////////////////////////////////////////////////////////////////////////////////////////

LokiPlugin.prototype.installationTimedOut = function()
{
    LokiPlugin.installationTimer = 0;
    this.onFailureProxy(LokiPlugin.WPS_ERROR_PLUGIN_COULD_NOT_BE_INSTALLED);
    LokiPlugin.failedInstall = true;
}

LokiPlugin.prototype.installationStarted = function()
{
    if (LokiPlugin.attemptedInstall) 
        return true;

    if (LokiPlugin.installationTimer)
        clearTimeout(LokiPlugin.installationTimer);

    LokiPlugin.failedInstall = false;

    var self = this;
    LokiPlugin.installationTimer = setTimeout(function(){self.installationTimedOut();}, LokiPlugin.installationTimeout);

    LokiPlugin.attemptedInstall = true;
    return false;
}

LokiPlugin.installationFinished = function()
{
    if (LokiPlugin.installationTimer)
        clearTimeout(LokiPlugin.installationTimer);
    LokiPlugin.installationTimer = 0;
}

/////////////////////////////////////////////////////////////////////////////////////////
// Helper functions and clesses
/////////////////////////////////////////////////////////////////////////////////////////

function IsLokiToolbarInstalled()
{
    try
    {
        if (Try.these(function() {return new ActiveXObject("Loki.LokiButton.1")}) || false)
            return true;
        return false;
    } catch (e)
    {
        return false;
    }
}

// "Null" loki object to ultimately fall back to
function LokiNull(){}

LokiNull.prototype.setKey = function(){}
LokiNull.prototype.isInstalled = function(){ return false; }

LokiNull.prototype.showManageDomains = function()
{
    if (this.onFailure)
    {
        var error = LokiPlugin.WPS_ERROR_PLUGIN_COULD_NOT_BE_INSTALLED;
        this.onFailure(error, LokiPlugin.returnMessages[error]);
    }
}

LokiNull.prototype.reverseGeocode = function()
{
    if (this.onReverseGeocoded)
    {
        this.onReverseGeocoded(LokiPlugin.WPS_ERROR_PLUGIN_COULD_NOT_BE_INSTALLED);
    }
}

LokiNull.prototype.tuneLocation = function()
{
    if (this.onTuneCompleted)
    {
        var error = LokiPlugin.WPS_ERROR_PLUGIN_COULD_NOT_BE_INSTALLED;
        this.onTuneCompleted(error, LokiPlugin.returnMessages[error]);
    }
}

LokiNull.prototype.requestLocation = function()
{
    if (this.onFailure)
    {
        var error = LokiPlugin.WPS_ERROR_PLUGIN_COULD_NOT_BE_INSTALLED;
        this.onFailure(error, LokiPlugin.returnMessages[error]);
    }
}

LokiNull.prototype.requestIPLocation = function()
{
    if (this.onFailure)
    {
        var error = LokiPlugin.WPS_ERROR_PLUGIN_COULD_NOT_BE_INSTALLED;
        this.onFailure(error, LokiPlugin.returnMessages[error]);
    }
}

// Borrowed from the Prototype Library
var Try = {
  these: function() {
    var returnValue;

    for (var i = 0; i < arguments.length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) {}
    }

    return returnValue;
  }
}

// Returns 0 if equal, positive if version1 > version2, negative otherwise (1.2 and 1.2.3 are equal)
function compareVersions(version1, version2)
{
    versions1 = version1.split(".");
    versions2 = version2.split(".");

    for (i = 0; i < versions1.length && i < versions2.length; i++)
    {
        if (parseInt(versions1[i], 10) > parseInt(versions2[i], 10))
            return i+1;
        if (parseInt(versions1[i], 10) < parseInt(versions2[i], 10))
            return -(i+1);
    }
    return 0;
}

var BrowserDetect = {

    init: function ()
        {
        this.browser = this.searchString(this.dataBrowser) || "Unknown browser";

        this.version = this.searchVersion(navigator.userAgent)
                       || this.searchVersion(navigator.appVersion)
                       || "Unknown version";

        this.OS = this.searchString(this.dataOS) || "Unknown OS";
        this.OSVersion = this.detectOSVersion();

        if (this.browser == "Explorer")
            this.javaAvail = true;
        else if (this.browser == "Firefox" && this.version == 3)
            this.javaAvail = this.findJava();
        else if (this.browser == "Opera")
            this.javaAvail = navigator.javaEnabled();
        else
            this.javaAvail = this.findJava() && navigator.javaEnabled();

        this.javaWaitingConfirmation = true;
        this.javaWaitingConfirmationSince = (new Date()).getTime();
    },

    detectOSVersion: function ()
    {
        switch (this.OS)
        {
        case "Mac":
            version = navigator.userAgent.substring(navigator.userAgent.indexOf("OS X ") + 5);
            version = version.substring(0, version.indexOf(";"));
            version = version.replace("_", ".");
            return version;
        default:
            return "";
        }
    },

    findJava: function ()
        {
        if (!navigator || !navigator.plugins)
            return true;

            navigator.plugins.refresh(false);
            for (var i = 0; i < navigator.plugins.length; ++i)
            {
                if (navigator.plugins[i] == undefined)
                    continue;
                if (navigator.plugins[i].name.indexOf('Java') != -1)
                {
                    LokiPlugin.javaPluginDescription = navigator.plugins[i].description;
                    return true;
                }
            }

            return false;
        },

    searchString: function (data)
    {
        for (var i = 0; i < data.length; i++)
        {
            var dataString = data[i].string;
            var dataProp = data[i].prop;
            this.versionSearchString = data[i].versionSearch || data[i].identity;
            if (dataString)
            {
                if (dataString.indexOf(data[i].subString) != -1)
                    return data[i].identity;
            }
            else if (dataProp)
                return data[i].identity;
        }
    },

    searchVersion: function (dataString)
    {
        var index = dataString.indexOf(this.versionSearchString);
        if (index == -1)
            return;

        return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
    },

    dataBrowser: [
        {
            string: navigator.userAgent,
            subString: "MSIE",
            identity: "Explorer",
            versionSearch: "MSIE"
        },
        {
            string: navigator.userAgent,
            subString: "Firefox",
            identity: "Firefox"
        },
        {
            prop: window.opera,
            identity: "Opera"
        },
        {
            string: navigator.vendor,
            subString: "Apple",
            identity: "Safari"
        },
        {
            string: navigator.vendor,
            subString: "Google",
            identity: "Chrome"
        },
        {
            string: navigator.vendor,
            subString: "KDE",
            identity: "Konqueror"
        },
        {
            string: navigator.userAgent,
            subString: "OmniWeb",
            versionSearch: "OmniWeb/",
            identity: "OmniWeb"
        },
        {
            string: navigator.vendor,
            subString: "iCab",
            identity: "iCab"
        },
        {
            string: navigator.vendor,
            subString: "Camino",
            identity: "Camino"
        },
        {        // for newer Netscapes (6+)
            string: navigator.userAgent,
            subString: "Netscape",
            identity: "Netscape"
        },
        {
            string: navigator.userAgent,
            subString: "Gecko",
            identity: "Mozilla",
            versionSearch: "rv"
        },
        {         // for older Netscapes (4-)
            string: navigator.userAgent,
            subString: "Mozilla",
            identity: "Netscape",
            versionSearch: "Mozilla"
        }
    ],

    dataOS : [
        {
            string: navigator.platform,
            subString: "Win",
            identity: "Windows"
        },
        {
            string: navigator.platform,
            subString: "Mac",
            identity: "Mac"
        },
        {
            string: navigator.platform,
            subString: "Linux",
            identity: "Linux"
        }
    ]
};

function loadjsfile(filename)
{
    var fileref=document.createElement('script');
    fileref.setAttribute("type","text/javascript");
    fileref.setAttribute("src", filename);
    if (typeof fileref!="undefined")
        document.getElementsByTagName("head")[0].appendChild(fileref);
}

function getLokiLocale()
{
    var lang;
    if (navigator.userLanguage) // Explorer
        lang = navigator.userLanguage;
    else if (navigator.language) // FF
        lang = navigator.language;

    lang = lang.toLowerCase();
    lang = lang.replace("_","-");

    switch(lang)
    {
        case "zh-cn":
        case "chs":
        case "zh":
            return "chs";

        case "zh-tw":
        case "cht":
            return "cht";

        case "de-de":
        case "deu":
        case "de":
            return "deu";

        case "en-us":
        case "en-gb":
        case "enu":
        case "en":
            return "enu";

        case "es-es":
        case "esp":
        case "es":
            return "esp";

        case "fr-fr":
        case "fra":
        case "fre":
        case "fr":
            return "fra";

        case "it-it":
        case "ita":
        case "it":
            return "ita";

        case "ja-jp":
        case "jpn":
        case "jp":
        case "ja":
            return "jpn";

        case "ko-kr":
        case "kor":
        case "ko":
            return "kor";

        case "nl-nl":
        case "nld":
        case "dut":
        case "nl":
            return "nld";

        case "pt-br":
        case "pt-pt":
        case "ptb":
        case "pt":
            return "ptb";

        case "ru-ru":
    case "rus":
    case "ru":
        return "rus";

    default:
        return "enu";
    }
}

function loadLocale(locale)
{
    // Skip loading external js, since english is listed in this main file as default one
    if (locale == "enu")
        return;

    loadjsfile(LokiPlugin.globalURLPrefix + "locale/" + locale + ".js");
}

function lokiGetGadgetName()
{
    try
    {
        if (System != undefined && System != null)
            if (System.Gadget != undefined && System.Gadget != null)
                if (System.Gadget.name != undefined && System.Gadget.name != null)
                    return System.Gadget.name;
    } catch (e)
    {};
    return "";
}

/////////////////////////////////////////////////////////////////////////////////////////
// Initialization
/////////////////////////////////////////////////////////////////////////////////////////

LokiPlugin.javaPluginDescription = "";

BrowserDetect.init();

LokiPlugin.prototype.key = "";

// Request types
LokiPlugin.REQUEST_LOCATION = 0;
LokiPlugin.REQUEST_REVGEO = 1;
LokiPlugin.REQUEST_TUNE = 2;
LokiPlugin.REQUEST_MANAGE_DOMAINS = 3;
LokiPlugin.REQUEST_UPGRADE = 4;
LokiPlugin.REQUEST_PERIODIC_LOCATION = 5;

// Street address lookup types
LokiPlugin.NO_STREET_ADDRESS_LOOKUP = 0;
LokiPlugin.LIMITED_STREET_ADDRESS_LOOKUP = 1;
LokiPlugin.FULL_STREET_ADDRESS_LOOKUP = 2;

LokiPlugin.prototype.NO_STREET_ADDRESS_LOOKUP = 0;
LokiPlugin.prototype.LIMITED_STREET_ADDRESS_LOOKUP = 1;
LokiPlugin.prototype.FULL_STREET_ADDRESS_LOOKUP = 2;

LokiPlugin.WPS_OK = 0;
LokiPlugin.WPS_ERROR_SCANNER_NOT_FOUND = 1;
LokiPlugin.WPS_ERROR_WIFI_NOT_AVAILABLE = 2;
LokiPlugin.WPS_ERROR_NO_WIFI_IN_RANGE = 3;
LokiPlugin.WPS_ERROR_UNAUTHORIZED = 4;
LokiPlugin.WPS_ERROR_SERVER_UNAVAILABLE = 5;
LokiPlugin.WPS_ERROR_LOCATION_CANNOT_BE_DETERMINED = 6;
LokiPlugin.WPS_ERROR_PROXY_UNAUTHORIZED = 7;
LokiPlugin.WPS_ERROR_FILE_IO = 8;
LokiPlugin.WPS_ERROR_INVALID_FILE_FORMAT = 9;
LokiPlugin.WPS_NOMEM = 98;
LokiPlugin.WPS_ERROR = 99;
LokiPlugin.WPS_ERROR_PLUGIN_COULD_NOT_BE_INSTALLED = 1000;
LokiPlugin.WPS_ERROR_PERMISSION_DENIED = 1001;
LokiPlugin.WPS_ERROR_PLUGIN_BROWSER_NOT_SUPPORTED = 1002;
LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED = 1003;
LokiPlugin.WPS_ERROR_PLUGIN_DISABLED = 1004;
LokiPlugin.WPS_ERROR_INVALID_ARG = 1005;

// Return messages are filled by loadLocale
// English messages are listed here as default language and for cases when additional localization scripts are failed to be downloaded.
LokiPlugin.returnMessages = new Object();
LokiPlugin.returnMessages[LokiPlugin.WPS_OK] = "Successful";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_SCANNER_NOT_FOUND] = "Wi-Fi Scanner was not found";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_WIFI_NOT_AVAILABLE] = "Wi-Fi is not available";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_NO_WIFI_IN_RANGE] = "No Wi-Fi access points are in range";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_UNAUTHORIZED] = "Invalid application key, please contact the site owner";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_SERVER_UNAVAILABLE] = "Network error";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_LOCATION_CANNOT_BE_DETERMINED] = "No Wi-Fi access points were recognized";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_PROXY_UNAUTHORIZED] = "Proxy error";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_FILE_IO] = "A file I/O error was encountered";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_INVALID_FILE_FORMAT] = "Invalid file format";
LokiPlugin.returnMessages[LokiPlugin.WPS_NOMEM] = "Not enough memory";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR] = "Unknown error";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_PLUGIN_COULD_NOT_BE_INSTALLED] = "Error installing plugin";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_PERMISSION_DENIED] = "Permission denied";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_PLUGIN_BROWSER_NOT_SUPPORTED] = "Browser is not supported";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED] = "Feature is not supported by installed version of plugin";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_PLUGIN_DISABLED] = "Plugin is disabled. Check browser configuration";
LokiPlugin.returnMessages[LokiPlugin.WPS_ERROR_INVALID_ARG] = "Invalid argument to Loki Plugin call";

// Requests queue
LokiPlugin.requestsQueue = new Array();
LokiPlugin.currentRequest = null;
LokiPlugin.reqTickTimer = null;
LokiPlugin.locationCache = new Array();

LokiPlugin.fallbackToNativeTimeout = 10000;
LokiPlugin.xpcom = null;
LokiPlugin.activex = null;
LokiPlugin.isRunningPluginInstance = false;
LokiPlugin.timer = 0;
LokiPlugin.installationTimer = 0;
LokiPlugin.installationTimeout = 90000;
LokiPlugin.attemptedInstall = false;
LokiPlugin.failedInstall = false;
LokiPlugin.upgradeCancelled = false;
LokiPlugin.upgradeStarted = false;
LokiPlugin.upgradeCompletedSuccessful = false;
LokiPlugin.nullappletShouldRunInstaller = false;
LokiPlugin.pluginDisabled = false;

var LokiAPI_PreloadNullapplet;
var LokiAPI_FilesLocation;

if (LokiAPI_FilesLocation == undefined)
    LokiAPI_FilesLocation = "http://loki.com/plugin/files/";

if (LokiAPI_FilesLocation.substring(7,0) != "http://")
{
    if (LokiAPI_FilesLocation.substring(1,0) == "/")
    {
        LokiAPI_FilesLocation = "http://" + location.host + LokiAPI_FilesLocation;
    } 
    else
    {
        var urlPrefix = location.href.substring(0, location.href.lastIndexOf("/") + 1);
        LokiAPI_FilesLocation = urlPrefix + LokiAPI_FilesLocation;
    }
}

if (LokiAPI_PreloadNullapplet == undefined)
    LokiAPI_PreloadNullapplet = false;

LokiPlugin.toolbarDetected = IsLokiToolbarInstalled();

LokiPlugin.useGlobalURLs = (LokiAPI_FilesLocation != undefined && LokiAPI_FilesLocation != "");
LokiPlugin.globalURLPrefix = LokiPlugin.useGlobalURLs ? LokiAPI_FilesLocation : "";

if (BrowserDetect.javaAvail && !LokiPlugin.isInstalled())
{
    if (LokiAPI_PreloadNullapplet)
    {
        // Nullapplet should be runned after scripts loads to be able to insert itself into dom
        setTimeout(function(){LokiPlugin.runNullapplet();}, 200);
    }
}

loadLocale(getLokiLocale());

LokiPlugin.runNullapplet = function()
{
    var codebase = LokiPlugin.useGlobalURLs ?  'codebase="' + LokiPlugin.globalURLPrefix + '"' : '';
    var codebase_par = LokiPlugin.useGlobalURLs ?  '<PARAM NAME="CODEBASE" VALUE="' + LokiPlugin.globalURLPrefix + '"/>' : '';
    var appletDiv = document.createElement("div");

    document.getElementsByTagName('body').item(0).appendChild(appletDiv);

    if (!LokiAPI_PreloadNullapplet)
    {
        LokiPlugin.nullappletShouldRunInstaller = true;
    }
    
    BrowserDetect.javaWaitingConfirmationSince = (new Date()).getTime();

    if (BrowserDetect.browser == "Safari" || BrowserDetect.browser == "Chrome")
        appletDiv.innerHTML = '<object type="application/x-java-applet" code="nullapplet.class" ' + codebase + ' width=0 height=0><PARAM NAME="MAYSCRIPT" VALUE="true"><param name="JAVA_CODEBASE" value="' + LokiPlugin.globalURLPrefix + '"></object>';
    else if (BrowserDetect.browser == "Explorer")
        appletDiv.innerHTML = '<OBJECT id="nullapplet" classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" WIDTH="0" HEIGHT="0">' + codebase_par + '<PARAM NAME="CODE" VALUE="nullapplet.class"/><PARAM NAME="scriptable" VALUE="true"/></OBJECT>';
    else
        appletDiv.innerHTML = '<applet name="nullapplet" id="nullapplet" ' + codebase + ' code="nullapplet.class" width="0" height="0" mayscript=true><param name="mayscript" value="true"></applet>';

}

// Called from nullapplet to confirm that java available
function confirmJavaOK()
{
    BrowserDetect.javaWaitingConfirmation = false;
    if (LokiPlugin.nullappletShouldRunInstaller)
    {
        LokiPlugin.nullappletShouldRunInstaller = false;
        LokiPlugin.startInstallApplet();
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
// W3C Geolocation API implementation (http://dev.w3.org/geo/api/spec-source.html)
/////////////////////////////////////////////////////////////////////////////////////////

function LokiGeolocation()
{
    // Try to add LokiGeolocation oblect to navigator as a geolocation
    if (navigator.geolocation == undefined)
        navigator.geolocation = this;
}

LokiGeolocation.WATCH_INTERVAL = 60000; // 1 min

function LokiPositionError() {}
LokiPositionError.UNKNOWN_ERROR = 0;
LokiPositionError.PERMISSION_DENIED = 1;
LokiPositionError.POSITION_UNAVAILABLE = 2;
LokiPositionError.TIMEOUT = 3;

// Public W3C spec methods implementation

LokiGeolocation.prototype.getCurrentPosition = 
    function (successCallback, errorCallback, options)
{
    // If maximumAge is set, then look in cache
    if (options &&
        options.maximumAge != undefined)
    {
        // First try WPS locations
        var cachedEntry = LokiPlugin.locationCacheLookup(false, // No IP Location
                                                         true,  // Do Lat/Lon lookup
                                                         LokiPlugin.NO_STREET_ADDRESS_LOOKUP,
                                                         0,
                                                         options.maximumAge);

        // Then try IP location
        if (!cachedEntry)
            cachedEntry = LokiPlugin.locationCacheLookup(true,  // This time try IP Location
                                                         true,  // Do Lat/Lon lookup
                                                         LokiPlugin.NO_STREET_ADDRESS_LOOKUP,
                                                         0,
                                                         options.maximumAge);
            

        if (cachedEntry)
        {
            this.successCallbackProxy(successCallback, cachedEntry.location, cachedEntry.utcTimestamp);
            return;
        }
    }

    // Zero timeout means cache only, we are here, so no suitable entry in cache
    if (options &&
        options.timeout != undefined &&
        options.timeout == 0)
    {
        var posError = new LokiPositionError();
        posError.code = LokiPositionError.TIMEOUT;
        posError.message = "Position timeout";
        errorCallback(posError);
        return;
    }

    var timeoutTimer;

    // Schedule timeout if nonzero timeout defined
    if (options &&
        options.timeout != undefined)
    {
        var selfErrorCallback = (errorCallback != undefined) ? errorCallback : function() {};
        timeoutTimer = setTimeout(
            function()
            {
                // Abandon loki callbacks to ignore them
                loki.onSuccess = function() {};
                loki.onFailure = function() {};

                var posError = new LokiPositionError();
                posError.code = LokiPositionError.TIMEOUT;
                posError.message = "Position timeout";
                selfErrorCallback(posError);
            },
            options.timeout);
    }

    var loki = new LokiAPI();
    var self = this;

    loki.onSuccess = function(location)
    {
        self.successCallbackProxy(successCallback, location, new Date().getTime(), timeoutTimer);
    }

    loki.onFailure = function(errcode, message)
    {
        // Fallback to using IP
        loki.onFailure = function(errcode, message)
        {
            self.errorCallbackProxy(errorCallback, errcode, message, timeoutTimer);
        }
        loki.requestIPLocation(true, LokiPlugin.NO_STREET_ADDRESS_LOOKUP);
    }

    loki.requestLocation(true, LokiPlugin.NO_STREET_ADDRESS_LOOKUP);
}

LokiGeolocation.prototype.watchPosition = function (successCallback, errorCallback, options)
{
    var newPositionWatch = new Object();
    var self = this;

    newPositionWatch.watchSuccessCallback = successCallback;
    newPositionWatch.watchErrorCallback = errorCallback;
    newPositionWatch.watchOptions = options;
    newPositionWatch.running = true;
    newPositionWatch.loki = new LokiAPI();
        
    newPositionWatch.loki.onPeriodicLocation = function(location)
    {
        self.watchSuccessProxy(newPositionWatch, location);
    };
    
    newPositionWatch.loki.onPeriodicLocationEnd = function(errcode, message)
    {
        if (errcode != 0)
            self.watchErrorProxy(newPositionWatch, LokiGeolocation.lokiErrorToW3C(errcode));
    };

    newPositionWatch.periodicLocHandler = 
        newPositionWatch.loki.requestPeriodicLocation(newPositionWatch.loki.NO_STREET_ADDRESS_LOOKUP, 2000, 0);

    return newPositionWatch;
}

LokiGeolocation.prototype.clearWatch = function(positionWatch)
{
    if (!positionWatch)
        return;

    if (!positionWatch.running)
        return;
        
    positionWatch.loki.cancelPeriodicLocation(positionWatch.periodicLocHandler);
    
    positionWatch.running = false;
}

// Private stuff
LokiGeolocation.lokiLocToW3C = function(lokiLocation, timestamp)
{
    var w3cPos = new Object();
    w3cPos.timestamp = timestamp;
    w3cPos.coords = new Object();    

    w3cPos.coords.latitude = lokiLocation.latitude;
    w3cPos.coords.longitude = lokiLocation.longitude;
    w3cPos.coords.accuracy = Math.round(lokiLocation.accuracy);

    // Until we have them passed to JS from Loki, these values should be null to meet w3c specs
    w3cPos.coords.altitude = null;
    w3cPos.coords.altitudeAccuracy = null;
    w3cPos.coords.heading = null;
    w3cPos.coords.speed = null;

    return w3cPos;
}

LokiGeolocation.lokiErrorToW3C = function(lokiErrcode)
{
    switch (lokiErrcode)
    {
        case LokiPlugin.WPS_ERROR_PERMISSION_DENIED:
            return LokiPositionError.PERMISSION_DENIED;
            break;
        case LokiPlugin.WPS_ERROR_SCANNER_NOT_FOUND:
        case LokiPlugin.WPS_ERROR_WIFI_NOT_AVAILABLE:
        case LokiPlugin.WPS_ERROR_NO_WIFI_IN_RANGE:
        case LokiPlugin.WPS_ERROR_SERVER_UNAVAILABLE:
        case LokiPlugin.WPS_ERROR_LOCATION_CANNOT_BE_DETERMINED:
        case LokiPlugin.WPS_ERROR_PROXY_UNAUTHORIZED:
        case LokiPlugin.WPS_ERROR_FILE_IO:
        case LokiPlugin.WPS_ERROR_INVALID_FILE_FORMAT:
        case LokiPlugin.WPS_ERROR_PLUGIN_COULD_NOT_BE_INSTALLED:
        case LokiPlugin.WPS_ERROR_UNAUTHORIZED:
        case LokiPlugin.WPS_ERROR_PLUGIN_BROWSER_NOT_SUPPORTED:
        case LokiPlugin.WPS_ERROR_FEATURE_NOT_SUPPORTED:
        case LokiPlugin.WPS_ERROR_PLUGIN_DISABLED:
        case LokiPlugin.WPS_ERROR_INVALID_ARG:
            return LokiPositionError.POSITION_UNAVAILABLE;
            break;
        default:
            return LokiPositionError.UNKNOWN_ERROR;
    }
}

LokiGeolocation.prototype.watchSuccessProxy = function(positionWatch, location)
{
    if (!positionWatch.running)
        return;
        
    // Calling success callback only if position changed
    if (positionWatch.lastLat != location.latitude ||
        positionWatch.lastLon != location.longitude || 
        positionWatch.lastAcc != Math.round(location.accuracy))
    {
        positionWatch.lastLat = location.latitude;
        positionWatch.lastLon = location.longitude;
        positionWatch.lastAcc = Math.round(location.accuracy);
        
        var w3cPosition = LokiGeolocation.lokiLocToW3C(location, new Date().getTime());
        positionWatch.watchSuccessCallback(w3cPosition);
    }
}

LokiGeolocation.prototype.watchErrorProxy = function(positionWatch, error)
{
    if (!positionWatch.running)
        return;

    positionWatch.watchErrorCallback(error);
}

LokiGeolocation.prototype.successCallbackProxy = function(successCallback, lokiLocation, timestamp, timeoutTimer)
{
    if (timeoutTimer)
        clearTimeout(timeoutTimer);

    if (!successCallback)
        return;

    var pos = LokiGeolocation.lokiLocToW3C(lokiLocation, timestamp);
    successCallback(pos);
}

LokiGeolocation.prototype.errorCallbackProxy = function(errorCallback, lokiErrcode, message, timeoutTimer)
{
    if (timeoutTimer)
        clearTimeout(timeoutTimer);

    if (!errorCallback)
        return;

    var posError = new LokiPositionError();
    posError.code = LokiGeolocation.lokiErrorToW3C(lokiErrcode);
    posError.message = message;
    errorCallback(posError);
}

LokiAPI.W3CGeolocation = new LokiGeolocation();


