2009/dap/docs/feat-perms feat-perms.css,NONE,1.1 feat-perms.html,NONE,1.1 feat-perms.js,NONE,1.1

Update of /sources/public/2009/dap/docs/feat-perms
In directory hutz:/tmp/cvs-serv29809/feat-perms

Added Files:
	feat-perms.css feat-perms.html feat-perms.js 
Log Message:
Feature Permissions Playground, initial import

--- NEW FILE: feat-perms.js ---
(function () {

function inherit(C, P) {
    C.prototype = new P();
}

function Permissions() {
    // private API
    (function () {
        var isLocalStorage = ('localStorage' in window),
            isJSON = ('JSON' in window),
            isEvents = ('addEventListener' in document),
            isSelector = ('querySelectorAll' in document);
        
        // create fake implementations for challenged browsers
        if (!isLocalStorage) {
            window.localStorage = {
                getItem: function (a) { return null; },
                setItem: function (a, b) {},
                removeItem: function (a) {}
            };
        }
        
        if (!isJSON) {
            window.JSON = {
                stringify: function(a) {},
                parse: function(a) {}
            };
        }
        
        if (!('permissions_load_error' in window) &&
            (!isLocalStorage || !isJSON || !isEvents || !isSelector)) {
            window.permissions_load_error = true;
            document.write(
                '<div id="warning">Warning: Your browser does not support all the features required:<ul>' +
                '<li>localStorage (' + isLocalStorage + ')</li>' +
                '<li>JSON (' + (isJSON) + ')</li>' +
                '<li>DOM2 Events (' + (isEvents) + ')</li>' +
                '<li>querySelectorAll (' + isSelector + ')</li></ul></div>');
        }
        
        if (isLocalStorage && isJSON &&
            localStorage.getItem('feature_permissions') !== null) {
            navigator.feature_permissions = JSON.parse(localStorage.getItem('feature_permissions'));
        } else {
            // default features and permission levels for demo purposes
            navigator.feature_permissions = [
                    {feature: 'contacts', permission_level: -1, requested: false},
                    {feature: 'calendar', permission_level: -1, requested: false},
                    {feature: 'system',   permission_level: -1, requested: false}
            ];
        }
    }());
}

Permissions.prototype = (function () {
    // private API
    var _is_infobar_invoked = false,
        _success_callback = null,
        _error_callback = null; // TODO
    
    function _setFeaturePermission(feature, options) {
        var i, l;
        
        for (i = 0, l = navigator.feature_permissions.length; i < l; i++) {
            if (navigator.feature_permissions[i].feature === feature) {
                if (options.permission_level) {
                    navigator.feature_permissions[i].permission_level = options.permission_level;
                }
                if (options.requested) {
                    navigator.feature_permissions[i].requested = options.requested;
                }
            }
        }
    }
    
    function _createInfobar() {
        var infobar = document.createElement('div'),
            feature, permission_level, action, i, l;
            
        infobar.id = 'infobar';
        infobar.className = 'transition box_shadow_infobar';
        infobar.style.display = 'block';
        infobar.innerHTML +=
            '<div id="infobar_header">Allow ' + (window.location.hostname || 'localhost') +
            ' to access your:' +
            '<button id="close_infobar" class="close_button">X</button>' +
            '<div id="settings_container">' +
            '(<a href="javascript:void(0)" id="settings_link">advanced settings</a>)</div>' +
            '</div>';
        
        for (i = 0, l = navigator.feature_permissions.length; i < l; i++) {
            if (navigator.feature_permissions[i].requested) {
                feature = navigator.feature_permissions[i].feature;
                permission_level = navigator.feature_permissions[i].permission_level;
                action = (permission_level > 0) ? 'allow' : 'deny';
                action = (permission_level === -1) ? '' : action;
                                
                infobar.innerHTML += 
                    '<div class="feature_request background_transition" ' +
                    'style=\'background-color:' + _getElementStyleByAction(action).color + '\'>' +
                    '<button class="allow" id="allow_' + feature + '">Allow</button>' +
                    '<button class="deny" id="deny_' + feature + '">Deny</button>' +
                    feature + '</div>';
            }
        }
        
        if (_getRequestedFeatures().length > 1) {
            infobar.innerHTML +=
                '<div id="allow_deny_all">' +
                '<button class="allow" id="allow_all">Allow All</button>' +
                '<button class="deny" id="deny_all">Deny All</button>';
            infobar.innerHTML +=
                '<div id="ok_cancel">' +
                '<button id="ok">OK</button><button id="cancel">Cancel</button>' +
                '</div>';
        }
        
        document.body.appendChild(infobar);
        _delegateEvents(infobar);
        _showInfobar();
    }
    
    function _delegateEvents(infobar) {
        infobar.addEventListener('click', _infobarEventHandler, false);
    }
    
    function _infobarEventHandler(e) {
        var element = _getActionByEvent(e).element,
            action = _getActionByEvent(e).action,
            feature = _getActionByEvent(e).feature,
            requested_features = _getRequestedFeatures(),
            features_request_rows, i, l;
                
        function _success() {
            var response = _getResponse(navigator.feature_permissions);
            _success_callback(response);
            _persistFeaturePermissions();
            _hideInfobar();
        }
        
        if (!element) { return; }
        
        // inspect element.id and act on it
        switch (element.id) { // omit break, because we'll return on match
            case 'close_infobar': _hideInfobar(); return;
            case 'close_settings': _hideSettings(); return;
            case 'settings_link': _showSettings(); return;
            case 'ok': _success(); return;
            case 'cancel': _hideInfobar(); return;
        }
        
        // process all feature requests
        if (feature === 'all') {                    
            features_request_rows = document.querySelectorAll('.feature_request');
            for (i = 0, l = features_request_rows.length; i < l; i++) {
                features_request_rows[i].style.backgroundColor = _getElementStyleByAction(action).color;
            }
            
            for (i = 0, l = requested_features.length; i < l; i++) {
                _setFeaturePermission(requested_features[i],
                    {permission_level: _getPermissionLevelByActionName(action)});
            }
            
            _success();
        // process a single feature request
        } else {
            element.parentNode.style.backgroundColor = _getElementStyleByAction(action).color;
            _setFeaturePermission(feature,
                {permission_level: _getPermissionLevelByActionName(action)});
            
            if (requested_features.length > 1) {
                document.getElementById('allow_deny_all').style.display = 'none';
                document.getElementById('ok_cancel').style.display = 'block';
            } else {
                _success();
            }
        }
    }
    
    function _persistFeaturePermissions() {
        var i, l;
        
        // set requested flags to false before persistence
        for (i = 0, l = navigator.feature_permissions.length; i < l; i++) {
            navigator.feature_permissions[i].requested = false;
        }
        
        localStorage.setItem('feature_permissions', JSON.stringify(navigator.feature_permissions));
    }
    
    function _getActionByEvent(e) {
        var element = e.target || e.srcElement,
            action = element.id.split('_')[0] || null,
            feature = element.id.split('_')[1] || null,
            elementName = element.nodeName.toLowerCase();
            
        if (elementName !== 'button' && elementName !== 'a') { element = null; }
        
        return { element: element, action: action, feature: feature };
    }
    
    function _getElementStyleByAction(action) {
        var color;
        
        switch (action) {
            case 'allow':
                color = '#00C957';
                break;
            case 'deny':
                color = '#FF6A6A';
                break;
            default:
                color = 'transparent';
        }
        
        return { color: color };
    }
    
    function _getPermissionLevelByActionName(action) {
        switch (action) {
            case 'allow':
                return 2; // USER_ALLOWED
            case 'deny':
                return -2; // USER_DENIED
            default:
                return -1; // DEFAULT_DENIED
        }
    }
    
    function _getRequestedFeatures() {
        var requested_features = [];
        
        for (i = 0, l = navigator.feature_permissions.length; i < l; i++) {
            if (navigator.feature_permissions[i].requested) {
                requested_features.push(navigator.feature_permissions[i].feature);
            }
        }
        
        return requested_features;
    }
    
    function _hideInfobar() {
        var infobar = document.getElementById('infobar');
    
        // hide
        infobar.style.top = '-125px';
        document.body.style.marginTop = '0px';
        
        // destroy
        setTimeout(function () {
            document.body.removeChild(infobar);
        }, 500);
    }
    
    function _showInfobar() {
        var infobar = document.getElementById('infobar'),
            infobarHeight = parseInt(document.defaultView.
                getComputedStyle(infobar, null).getPropertyValue('height'), 10);
        
        infobar.style.top = '0px';
        document.body.style.marginTop = (infobarHeight + 40) + 'px';
    }

    function _hideSettings() {
        var settings = document.getElementById('settings_window');
        
        settings.style.display = 'none';
    }
    
    function _showSettings() {
        var settings_window = document.getElementById('settings_window'); 
        
        if (settings_window) {
            settings_window.style.display = 'block';
            return;
        }
        
        var settings_window = document.createElement('div');
        
        settings_window.id = 'settings_window';
        settings_window.className ='box_shadow_window';
        settings_window.innerHTML =
            '<button id="close_settings" class="close_button">X</button>' +
            '<div id="settings_header">Feature Permission Settings</div>' +
            '<button onclick="localStorage.removeItem(\'feature_permissions\');' +
            'document.location.reload(true);">Set to defaults</button>';
            
        document.body.appendChild(settings_window);
        _delegateEvents(settings_window);
    }
    
    function _getResponse(feature_permissions) {
        var response = [], i, l;
        
        for (i = 0, l = navigator.feature_permissions.length; i < l; i++) {
            if (navigator.feature_permissions[i].requested) {
                response.push({
                    feature: navigator.feature_permissions[i].feature,
                    permission_level: navigator.feature_permissions[i].permission_level
                });
            }
        }
        
        return response;
    }
    
    function _getPrivilegedFeatures() {
        var privileged_features = [],
            i, l;
        
        for (i = 0, l = navigator.feature_permissions.length; i < l; i++) {
            privileged_features.push(navigator.feature_permissions[i].feature);
        }
        
        return privileged_features;
    }
    
    function _requestPermissionWarning(msg) {
        var logEl = document.getElementById('log');
        logEl.style.backgroundColor = '#FF6A6A';
        log(msg);
        window.scrollTo(0, 0);
        setTimeout(function () {
            logEl.style.backgroundColor = '#F0F0F0';
        }, 2000);
    }
    
    // public API
    return {
        USER_ALLOWED: 2,
        DEFAULT_ALLOWED: 1,
        DEFAULT_DENIED: -1,
        USER_DENIED: -2,
        privilegedFeatures: ['contacts', 'calendar', 'system'],
        
        permissionLevel: function (feature) {
            var i, l;
            
            for (i = 0, l = navigator.feature_permissions.length; i < l; i++) {
                if (navigator.feature_permissions[i].feature === (feature || this.feature)) {
                    return navigator.feature_permissions[i].permission_level;
                }
            }
            
            throw {
                name: 'Error',
                message: 'Feature "' + feature + '" is not known to the user agent.'
            };
        },
        
        requestPermission: function (success, error) {
            // infobar can be invoked once, set requested accordingly
            _setFeaturePermission(this.feature, {requested: !_is_infobar_invoked});
            
            if (typeof success === 'function' || typeof error === 'function') {
                if (_is_infobar_invoked) {
                    _requestPermissionWarning('The feature permissions infobar can be invoked ' +
                        'once per active browsing context. Reload the page to replay.');
                    return;
                }
                
                _is_infobar_invoked = true;
                _success_callback = success;
                _error_callback = error;
                _createInfobar();
            }
        }
    };

}());

function Contacts(feature) {
    this.feature = feature;
}

function Calendar(feature) {
    this.feature = feature;
}

function System(feature) {
    this.feature = feature;
}

inherit(Contacts, Permissions);
inherit(Calendar, Permissions);
inherit(System, Permissions);

navigator.contacts = new Contacts('contacts');
navigator.calendar = new Calendar('calendar');
navigator.system = new System('system');

}());
--- NEW FILE: feat-perms.html ---
<!DOCTYPE HTML>
<html>
<head>
    <title>Feature Permissions Playground</title>
    <link href="feat-perms.css" rel="stylesheet"/>
    <script src="feat-perms.js"></script>
    <script>
    window.onload = function () {
        document.getElementById('example1').textContent = example1.toString();
        document.getElementById('example2').textContent = example2.toString();
        document.getElementById('example3').textContent = example3.toString();
    };
    
    function example1() {
        navigator.contacts.requestPermission();
        navigator.calendar.requestPermission();
        navigator.system.requestPermission(function(response) { log(JSON.stringify(response)); });
    }

    function example2() {
        navigator.contacts.requestPermission();
        navigator.calendar.requestPermission(function(response) { log(JSON.stringify(response)); });
    }

    function example3() {
        navigator.contacts.requestPermission(function(response) { log(JSON.stringify(response)); });
    }
    
    function log(str) {
        var log = document.getElementById('log').textContent = str;
    }
    
    function evalScript() {
        eval(document.getElementById('example_custom').value);
    }
    </script>
</head>
<body>
<h1>Feature Permissions Playground</h1>

<p>This demo implements the
<a href="http://dev.w3.org/2006/webapi/WebNotifications/publish/FeaturePermissions.html#idl-Permissions">Permissions interface</a>
with some <a href="#changes">changes</a>. The demo should work with any modern browser. Click <em>Run</em> buttons in <a href="#examples">Examples</a> to experiment. Contacts, Calendar and System Information are used as placeholders only, and may not be applicable for this model.</p>

<p>This document is merely a W3C-internal document. It has no official standing of any kind and does not represent consensus of the W3C Membership.</p>

<h2>log() Output</h2>
<pre id="log" class="background_transition">...</pre>

<h2>Examples</h2>
<div id="examples">
    <div class="example">
        <button onclick="example1()">Run</button>
        <pre id="example1"></pre>
    </div>
    <div class="example">
        <button onclick="example2()">Run</button>
        <pre id="example2"></pre>
    </div>
    <div class="example">
        <button onclick="example3()">Run</button>
        <pre id="example3"></pre>
    </div>
    <div class="example">
        <button onclick="evalScript()">Run</button>
        <textarea id="example_custom">navigator.system.requestPermission(function(response) { log(JSON.stringify(response)); });</textarea>
    </div>
</div>

<h2 id="changes">Changes to Feature Permissions Interface</h2>

<p>navigator.foobar implements Permissions (instead of navigator implements Permissions), where foobar is {notifications|sensor|you-name-it|...}:</p>
<pre>
interface FoobarSensor : Permissions {
// ...
}
</pre>

<p>Each interface that implements Permissions has a unique <code>feature</code> id:</p>
<pre>
console.log(navigator.foobar.feature); // returns "foobar"
</pre>

<p><code>requestPermission()</code> can drop <code>feature</code> argument.
If <code>requestPermission()</code> is invoked with undefined arguments the user
interaction (such as displaying an infobar) is deferred until <code>requestPermission()</code>
is invoked with a non-null callback:</p>
<pre>
navigator.foobar.requestPermission(); // returns nothing
</pre>

<p>If the <code>callback</code> is non-null in <code>requestPermission(callback)</code>, a user interface
is shown to the user from where the user is able to change feature permission settings or dismiss the UI.
The UI includes all feature requests, including those deferred (callback is undefined)
in the browsing context. The callback is invoked only after user's explicit consent is received. If the
user chooses to dismiss the UI the callback is not invoked. For example, to ask for foobar and baz feature permissions:</p>
<pre>
navigator.foobar.requestPermission();
navigator.baz.requestPermission(function(response) { console.log(response); });
</pre>

<p>The callback <code>response</code> returned is an array of objects consisting of <code>feature</code>
identifier and feature's <code>permission_level</code>. System default permission levels are used, 
if user does not explicitly set the permission permission level. For example, if the user allows access to <code>foobar</code> but 
leaves the <code>baz</code> permission level to default the following <code>response</code> is returned:</p>
<pre>
[
  {feature: "foobar", permission_level: 2}, // USER_ALLOWED
  {feature: "baz", permission_level: -1}    // DEFAULT_DENIED
]
</pre>

<p>If <code>feature</code> parameter passed to <code>permissionLevel(feature)</code> getter is undefined (i.e. not defined),
the permission level of the interface that implements <code>Permissions</code> is returned:</p>
<pre>
navigator.foobar.permissionLevel(); // returns foobar's permission_level, e.g. 2 for USER_ALLOWED
</pre
>

<h2>Misc Notes</h2>
<ul>
    <li>Decouples feature request(s) from API invocation(s), allows to request multiple permissions up-front
        (re <a href="http://www.w3.org/2009/dap/track/issues/109">ISSUE-109</a>).</li>
    <li>Not for all APIs, e.g. not well applicable to Contacts et al. although used
        in the examples as placeholders) -- 
        Should go through the existing DAP APIs and see if Feature Permissions works with them.</li>
    <li>At the minimum, DAP should spec what Web Notifications WG needs for Web Notifications.</li>
    <li>Feature Permissions spec says: <em>[requestPermission] method should only 
        be called while handling a user gesture; in other circumstances the user
        agent should take no action in response</em> -- should align with the <em>API Invocation
        via DOM Events</em> model spec'd in Contacts.</li>
</ul>

<h2>Misc UI Ideas</h2>
<ul>
    <li>Drag and drop features/feature icons from the infobar/other chrome UI element to the web content/sink element.</li>
    <li>Feature icons could display information about the action (such as contacts, e.g. their names, to be shared with the web page) while hovered/activated/dragged.</li>
    <li>Infobar could also have two sinks for allowed and denied features, features could be drag and dropped from one to another.</li>
</ul>
<div id="footer">by Anssi Kostiainen</div>
</body>
</html>
--- NEW FILE: feat-perms.css ---
body {
    margin-top: 0;
    padding-top: 0;
    font: 100% sans-serif;
    top: 0px;
    margin-left: auto;
    margin-right: auto;
    max-width: 50em;
}

h1, h2, h3 {
    color: #005a9c;
}

h1 {
    font: 170% sans-serif;
    margin-top: 30px;
}

h2 {
    font: 140% sans-serif;
}

button {
    -webkit-border-radius: 12px;
    -moz-border-radius: 12px;
    -o-border-radius: 12px;
    border-radius: 12px;
    border: solid 1px gray;
    background: #ededed;
    /* IE 5.5+ */
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed', endColorstr='#cbcbcb');
    /* WebKit 528+ */
    background: -webkit-gradient(linear, left top, left bottom, from(#ededed), to(#cbcbcb));
    /* FF 3.6+ (https://developer.mozilla.org/en/CSS/-moz-linear-gradient) */
    background: -moz-linear-gradient(top, #ededed, #cbcbcb);
    /* ED (http://dev.w3.org/csswg/css3-images/#linear-gradients) */
    background: linear-gradient(top, #ededed, #cbcbcb);
    -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .2);
    -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, .2);
    box-shadow: 0 1px 1px rgba(0, 0, 0, .2);
    text-shadow: 0 1px 0 rgba(255, 255, 255, .8);
}

#infobar {
    font-size: 14px;
    display: none;
    background-color: #fdecb3;
    border-bottom: solid 1px #7a7a7a;
    padding: 5px;
    position: fixed;
    left: 0px;
    top: -100px;
    width: 99%;
    height: 45px;
}

#infobar_header {
    height: 20px;
}

#footer {
    border-top: dotted 1px gray;
    color: gray;
    font: 75% sans-serif;
    padding: 10px;
}

#warning {
    background-color: #ff6a6a;
    border: solid 1px red;
    padding: 10px;
    margin-top: 15px;
}

.example {
    border: dotted 1px gray;
    padding: 5px;
    margin-bottom: 10px;
    background-color: #f0f0f0;
}

.example > pre {
    border: 0;
}

.feature_request {
    margin-bottom: 0px;
    margin-right: 20px;
    width: 190px;
    height: 22px;
    -webkit-border-radius: 12px;
	-moz-border-radius: 12px;
	-o-border-radius: 12px;
    border-radius: 12px;
    border: solid 1px #ABABAB;
    float: left;
}

.feature_request > button {
    height: 18px;
    margin-right: 10px;
    margin-left: 2px;
    margin-top: 2px;
    margin-bottom: 2px;
}

.allow {
    color: green;
}

.deny {
    color: red;
}

.close_button {
    font-family: arial;
    float: right !important;
    background: transparent;
    color: #a1a1a1;
    padding: 0;
    margin: 0;
    font-size: 10px;
    font-weight: bold;
    width: 20px;
    height: 20px;
    -webkit-border-radius: 10px;
    -moz-border-radius: 10px;
    -o-border-radius: 10px;
    border-radius: 10px;
}

#allow_deny_all {
    margin-bottom: 5px;
    float: left;
}

#ok_cancel {
    display: none;
    float: left;
}

#allow_deny_all > button, #ok_cancel > button {
    margin-top: 2px;
    margin-right: 10px;
    font-weight: bold;
    margin-bottom: 5px;
}

#examples > button {
    margin-right: 10px;
}

#log {
    font-size: 10px;
    border: dotted 1px gray;
    background-color: #f0f0f0;
    padding: 15px;
    white-space: pre-wrap;
    margin-left: 0px !important;
}

pre {
    font-size: 12px;
    font-weight: normal;
    border: dotted 1px gray;
    background-color: #f0f0f0;
    padding: 10px;
    margin-left: 20px;
    white-space: pre-wrap;
    font-family: courier;
    font-size: 12px;
}

textarea {
    width: 99%;
    height: 50px;
    font-family: courier;
    font-size: 12px;
    margin-top: 10px;
}

#settings_container {
    margin-top: 3px;
    margin-right: 15px;
    float: right;
    color: gray;
    font-size: 10px;
}

#settings_window {
    position: fixed;
    top: 30px;
    right: 80px;
    height: 100px;
    width: 250px;
    border: solid 1px #7a7a7a;
    background-color: #fff;
    padding: 10px;
}

#settings_header {
    font-weight: bold;
    font-size: 16px;
    margin-top: 10px;
    margin-bottom: 20px;
}

.box_shadow_infobar {
    -webkit-box-shadow: 0 5px 5px #aaa;
	-moz-box-shadow: 0 5px 5px #aaa;
	-o-box-shadow: 0 5px 5px #aaa;
	box-shadow: 0 5px 5px #aaa;
}

.box_shadow_window {
    -webkit-box-shadow: 0 0 25px #aaa;
	-moz-box-shadow: 0 0 25px #aaa;
	-o-box-shadow: 0 0 25px #aaa;
	box-shadow: 0 0 25px #aaa;
}

.transition {
    -webkit-transition:all 0.2s ease-in-out;
    -moz-transition:all 0.2s ease-in-out;
    -o-transition:all 0.2s ease-in-out;
    -transition:all 0.2s ease-in-out;
}

.background_transition {
    -webkit-transition: background-color 300ms;
    -moz-transition: background-color 300ms;
    -o-transition: background-color 300ms;
    transition: background-color 300ms;
}

Received on Thursday, 3 March 2011 11:16:51 UTC