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.