Skip to main content

Adam Barth's abandoned(?) HTML Cookie API proposal.

// Adam Barth's abandoned(?) HTML Cookie API proposal
// https://docs.google.com/Doc?docid=0AZpchfQ5mBrEZGQ0cDh3YzRfMTRmdHFma21kMg&hl=en&pli=1

(function () {
  "use strict";

  /** @constructor */
  function Cookie(name, value) {
    this.name = name;
    this.value = value;
    this.httpOnly = false;
  }

  function parseCookies(findName) {
    var array = document.cookie.split(/;\s+/),
        i, str, idx, cookie;
    for (i = 0; i < array.length; i += 1) {
      str = array[i];
      idx = str.indexOf('=');
      cookie = new Cookie(str.substring(0, idx), str.substring(idx + 1));
      if (cookie.name === findName) {
        return cookie;
      } else {
        array[i] = cookie;
      }
    }
    return arguments.length > 0 ? null : array;
  }

  function getCookie(name, callback) {
    setTimeout(function () {
      callback(parseCookies(name));
    }, 0);
  }

  function getAllCookies(callback) {
    setTimeout(function () {
      callback(parseCookies());
    }, 0);
  }

  function setCookie(cookie, errback) {
    setTimeout(function () {
      var str = cookie.name + '=' + (cookie.value || ""),
          foundCookie, expectDeleted;

      if (cookie.expires) {
        str += "; expires=" + cookie.expires;
      }
      if (cookie.domain) {
        str += "; domain=" + cookie.domain;
      }
      if (cookie.secure) {
        str += "; secure";
      }
      str += "; path=" + (cookie.path || "/");
      document.cookie = str;

      foundCookie = parseCookies(cookie.name);
      expectDeleted = cookie.expires && (new Date(cookie.expires) < new Date());
      if (!foundCookie && !expectDeleted) {
        errback("Cookie not set");
      } else if (foundCookie.value !== cookie.value) {
        errback("Cookie not set");
      }
    }, 0);
  }

  function deleteCookie(name, errback) {
    setTimeout(function () {
      document.cookie = name + "=; expires=" + (new Date(0).toGMTString()) + "; path=/";
      var foundCookie = parseCookies(name);
      if (foundCookie) {
        errback("Cookie not deleted");
      }
    }, 0);
  }

  document.getCookie = document.getCookie || getCookie;
  document.getAllCookies = document.getAllCookies || getAllCookies;
  document.setCookie = document.setCookie || setCookie;
  document.deleteCookie = document.deleteCookie || deleteCookie;

} ());

//
// HTML Cookie API
// https://docs.google.com/Doc?docid=0AZpchfQ5mBrEZGQ0cDh3YzRfMTRmdHFma21kMg&hl=en&pli=1
//
// The document.cookie API is kind of terrible.  Web developers shouldn't have
// to parse a cookie-string or prepare a properly formated set-cookie-string.
// This document present a less terrible cookie API for HTML.
//
// interface Cookie {
//   attribute DOMString name;
//   attribute DOMString value;
//   attribute DOMString domain;
//   attribute DOMString expires;
//   attribute DOMString path;
//   attribute Boolean secure;
//   attribute Boolean httpOnly;
// };
//
// For session cookies, the value of |expires| is the empty string.  For
// host-only cookies, the |domain| attribute is the empty string.  For Cookie
// objects created by the DOM, the |domain| attribute is not prefixed with a dot
// (".").  Because HttpOnly cookies are inaccessible to script, the |httpOnly|
// attribute will usually be false.  Modifying a Cookie object in memory does
// not affect the underlying cookie store.
//
// interface Document {
//   ...
//   void getCookie(in DOMString name, in CookieCallback callback);
//   void getAllCookies(in CookieListCallback callback);
//   void setCookie(in Cookie cookie, in optional VoidCallback error);
//   void deleteCookie(in DOMString name, in optional VoidCallback error);
//   ...
// };
//
// [Callback=FunctionOnly, NoInterfaceObject]
// interface CookieCallback {
//   handleEvent(in Cookie cookie);
// };
//
// [Callback=FunctionOnly, NoInterfaceObject]
// interface CookieListCallback {
//   handleEvent(in CookieList cookieList);
// };
//
// [Callback=FunctionOnly, NoInterfaceObject]
// interface VoidCallback {
//   void handleEvent();
// };
//
// The getCookie() API calls |callback| asynchronously with a copy of the first
// cookie (in document.cookie order) for the current document whose name is
// |name|.
//
// document.getCookie("SID", function(cookie) {
//   alert("The SID cookie has value " + cookie.value);
// });
//
// The getAllCookies() API calls |callback| asynchronously with an array-like
// object containing copies of the Cookies for the document (with HttpOnly
// cookies excluded).  This API provides a snapshot of the data in the cookie
// store at some time, in the order they would appear if returned from
// document.cookie.  Modifications to the cookie list returned this way are not
// reflected in the cookie store and modifications to the cookie store are not
// reflected in the returned cookie list.
//
// document.getAllCookies(function(cookies) {
//   for (var i=0; i < cookies.length; ++i)
//   alert(cookies[i].name + " has value " + cookies[i].value);
// });
//
// The setCookie() API stores a cookie in the cookie store with fields that
// match the attributes of |cookie|.  If setCookie() fails to set the cookie
// (e.g., because of third-party cookie blocking), the user agent calls the
// |error| callback.
//
// I'm not entirely sure how to state this requirement, but the DOM should use
// "duck typing" so that the following works properly:
//
// document.setCookie({name: "foo", value: "bar"});
//
// The deleteCookie() API deletes all the cookies for this document with a name
// of |name|.