4

This code sums up my question:

localStorage.setItem('getItem', 4)
undefined
localStorage.getItem
function getItem() { [native code] }


localStorage['getItem'] = 'user'
"user"
localStorage.getItem
"user"
localStorage.getItem('getItem')
TypeError: Property 'getItem' of object #<Storage> is not a function

Using the setItem method or using the property accessor changes the way localStorage reacts.

Is this a bug? A specification inconsistency? Something else?

I feel like I should report this somewhere, but don't really know where.

Florian Margaine
  • 58,730
  • 15
  • 91
  • 116

1 Answers1

5

Using the setItem method or using the property accessor changes the way localStorage reacts.

Is this a bug?

It doesn't seem like it. From the spec:

When the setItem(), removeItem(), and clear() methods are called on a Storage object x that is associated with a local storage area, if the methods did something, then in every Document object whose Window object's localStorage attribute's Storage object is associated with the same storage area, other than x, a storage event must be fired, as described below.

The localStorage object is an IDL attribute with a reference to the Storage interface. The Storage interface extends Object.prototype, which brings in extra properties, and also defines its own set of properties.

The setItem method stores a property in the storage list. The object also fires an event when there are changes on the object properties, and updates the storage area in those cases.

So the expected behavior for setItem is to store the key value pair in the storage list. The expected behavior for getItem is to pull the value associated with a key from the storage list.

So what is the expected behavior when we try to access a property directly?. This line says that the key-value list acts as "supported properties":

The supported property names on a Storage object are the keys of each key/value pair currently present in the list associated with the object.

What does this mean? It means that those properties are mapped to the getters and setters defined by the interface spec. For Storage the interface specification looks like this:

interface Storage {
  readonly attribute unsigned long length;
  DOMString? key(unsigned long index);
  getter DOMString getItem(DOMString key);
  setter creator void setItem(DOMString key, DOMString value);
  deleter void removeItem(DOMString key);
  void clear();
};

So localStorage["x"] is mapped to the value of getItem("x"), which returns the value of "x" in the storage list, and localStorage["x"] = y will be mapped to setItem("x",y).

However, these will only be run if there is not a native property by that name on the object unless the overridebuiltins attribute is set on the interface (which it is not for Storage).

If the [OverrideBuiltins] extended attribute appears on an interface, it indicates that for a platform object implementing the interface, properties corresponding to all of the object’s supported property names will appear to be on the object, regardless of what other properties exist on the object or its prototype chain. This means that named properties will always shadow any properties that would otherwise appear on the object. This is in contrast to the usual behavior, which is for named properties to be exposed only if there is no property with the same name on the object itself or somewhere on its prototype chain.

So for properties we should expect that if a function is defined on an object it will always access/set that on a request. Otherwise it will check the storage list and see if it has a value and change or return it.

Summary (TL;DR)

The expected behavior from the spec, and what we find when you test is that setItem and getItem will store values of pre-existing properties/functions as key/value pairs, and access them, without overwriting the existing values. When we attempt to access or modify those values directly through localStorage["getItem"], we'll instead hit the local properties, because the "supported property" spec calls for not overriding built in properties by default.

Ben McCormick
  • 25,260
  • 12
  • 52
  • 71
  • What is your question exactly? If you're overriding default properties they're not going to work as expected. Its not a bug, its just a result of how you're editing the object – Ben McCormick Mar 23 '13 at 22:16
  • Is this a bug? Inconsistency? Where should I report this? – Florian Margaine Mar 23 '13 at 22:33
  • What did you expect to happen differently? – Ben McCormick Mar 23 '13 at 22:55
  • @FlorianMargaine Well this led to a bit more research than I expected, but I've significantly edited my answer to show the relevant parts of the spec. Basically the behavior is documented thoroughly (though not all in one place) and is not a bug. – Ben McCormick Mar 24 '13 at 02:41
  • @ben336 yeps, not a bug. The note in this section makes it super clear: http://www.w3.org/TR/WebIDL/#idl-named-properties ::: As with indexed properties, if an named property getter, setter or deleter is specified using an operation with an identifier, then indexing an object with a name that is not a supported property name does not necessarily elicit the same behavior as invoking the operation with that name; the behavior is language binding specific. – Kyle Mar 24 '13 at 05:50