WebCore:
authorbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 Apr 2008 23:11:29 +0000 (23:11 +0000)
committerbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 Apr 2008 23:11:29 +0000 (23:11 +0000)
2008-04-07  Brady Eidson  <beidson@apple.com>

        Lovingly reviewed by Sam Weinig

        <rdar://problem/5797684> - HTML5 SessionStorage and underpinnings for LocalStorage

        Tests: storage/domstorage/sessionstorage/iframe-events.html
               storage/domstorage/sessionstorage/index-get-and-set.html
               storage/domstorage/sessionstorage/simple-events.html
               storage/domstorage/sessionstorage/simple-usage.html
               storage/domstorage/sessionstorage/window-open.html
               storage/domstorage/window-attributes-exist.html

        * Configurations/WebCore.xcconfig: Define to enable DOM_STORAGE

        * bindings/js/JSDOMWindowCustom.cpp:
        (WebCore::JSDOMWindow::mark): Add optionalSessionStorage case

        * bindings/js/JSEventCustom.cpp:
        (WebCore::toJS): Add StorageEvent case

        * bindings/js/JSStorageCustom.cpp:
        (WebCore::JSStorage::canGetItemsForName):
        (WebCore::JSStorage::nameGetter): If the property doesn't exist on the object, call through to getItem()
        (WebCore::JSStorage::customPut): If the property doesn't exist on the object, call through to setItem()

        * dom/Event.cpp:
        (WebCore::Event::isStorageEvent):
        * dom/Event.h:

        * dom/EventNames.h: Add "storage"

        * dom/EventTargetNode.cpp:
        (WebCore::EventTargetNode::dispatchStorageEvent):
        * dom/EventTargetNode.h:

        * loader/FrameLoader.cpp:
        (WebCore::FrameLoader::createWindow): After a new page has been created, set its SessionStorage object
          to a copy of the previous Page's

        * page/DOMWindow.cpp:
        (WebCore::DOMWindow::sessionStorage): Accessor to pull the appropriate OriginStorage out of the Page's
          SessionStorage.
        (WebCore::DOMWindow::localStorage): To be filled in later
        * page/DOMWindow.h:
        (WebCore::DOMWindow::optionalSessionStorage): Return the session Storage object for this window to mark,
          if any exists
        * page/DOMWindow.idl:

        * page/Page.cpp:
        (WebCore::Page::sessionStorage):  Create and/or return the SessionStorage for this Page.
        (WebCore::Page::setSessionStorage): Set the SessionStorage for this Page - used in FrameLoader after a
          Window.open();
        * page/Page.h:

        * storage/OriginStorage.cpp: Intermediate layer between individual Storage objects, and shared StorageMap
          objects.  There is one OriginStorage object per SecurityOrigin in each "unique set of storage areas", such
          as the SessionStorage.  This layer forwards DOM-level calls down to the backing StorageMap, handles
          copy-on-write along with the StorageMap, fires StorageEvents to the DOM when a value is changed, and will
          eventually handle quota enforcement.
        (WebCore::OriginStorage::create):
        (WebCore::OriginStorage::OriginStorage):
        (WebCore::OriginStorage::~OriginStorage):
        (WebCore::OriginStorage::copy):
        (WebCore::OriginStorage::length):
        (WebCore::OriginStorage::key):
        (WebCore::OriginStorage::getItem):
        (WebCore::OriginStorage::setItem):
        (WebCore::OriginStorage::removeItem):
        (WebCore::OriginStorage::contains):
        (WebCore::OriginStorage::dispatchStorageEvent):
        * storage/OriginStorage.h:

        * storage/SessionStorage.cpp: From the HTML5 spec:
          "Each top-level browsing context has a unique set of session storage areas, one for each origin."
          This object represents that "unique set of session storage areas", and creates or returns the Storage
          object for the requested SecurityOrigin
        (WebCore::SessionStorage::create):
        (WebCore::SessionStorage::SessionStorage):
        (WebCore::SessionStorage::copy):
        (WebCore::SessionStorage::originStorage):
        * storage/SessionStorage.h:
        (WebCore::SessionStorage::page):

        * storage/Storage.cpp: Representation of the DOM-level object, wrapped by JSStorage.  There is a unique
          Storage object per Window (per-Frame) that wraps a specific shared OriginStorage object.
        (WebCore::Storage::create):
        (WebCore::Storage::Storage):
        (WebCore::Storage::length):
        (WebCore::Storage::key):
        (WebCore::Storage::getItem):
        (WebCore::Storage::setItem):
        (WebCore::Storage::removeItem):
        (WebCore::Storage::contains):
        * storage/Storage.h:
        * storage/Storage.idl:

        * storage/StorageEvent.cpp:
        (WebCore::StorageEvent::StorageEvent):
        (WebCore::StorageEvent::initStorageEvent):
        * storage/StorageEvent.h:
        (WebCore::StorageEvent::isStorageEvent):

        * storage/StorageMap.cpp: The physical map of key/value pairs that is shared between OriginStorage objects,
          and implements copy-on-write semantics whenever a value is changed
        (WebCore::StorageMap::create):
        (WebCore::StorageMap::StorageMap):
        (WebCore::StorageMap::copy):
        (WebCore::StorageMap::invalidateIterator): Used to support the key(unsigned i) part of the API
        (WebCore::StorageMap::setIteratorToIndex): Ditto
        (WebCore::StorageMap::length):
        (WebCore::StorageMap::key):
        (WebCore::StorageMap::getItem):
        (WebCore::StorageMap::setItem):
        (WebCore::StorageMap::removeItem):
        (WebCore::StorageMap::contains):
        * storage/StorageMap.h:

LayoutTests:

2008-04-07  Brady Eidson  <beidson@apple.com>

        Begrudgingly reviewed by Sam Weinig

        Initial suite of layout tests for HTML5 key/value SessionStorage (<rdar://problem/5797684>)

        * fast/dom/Window/window-properties-expected.txt:
        * storage/domstorage: Added.
        * storage/domstorage/localstorage: Added.
        * storage/domstorage/sessionstorage: Added.
        * storage/domstorage/sessionstorage/iframe-events-expected.txt: Added.
        * storage/domstorage/sessionstorage/iframe-events.html: Added.
        * storage/domstorage/sessionstorage/index-get-and-set-expected.txt: Added.
        * storage/domstorage/sessionstorage/index-get-and-set.html: Added.
        * storage/domstorage/sessionstorage/resources: Added.
        * storage/domstorage/sessionstorage/resources/clearSessionStorage.js: Added.
        * storage/domstorage/sessionstorage/resources/iframe-events-second.html: Added.
        * storage/domstorage/sessionstorage/resources/window-open-second.html: Added.
        * storage/domstorage/sessionstorage/simple-events-expected.txt: Added.
        * storage/domstorage/sessionstorage/simple-events.html: Added.
        * storage/domstorage/sessionstorage/simple-usage-expected.txt: Added.
        * storage/domstorage/sessionstorage/simple-usage.html: Added.
        * storage/domstorage/sessionstorage/window-open-expected.txt: Added.
        * storage/domstorage/sessionstorage/window-open.html: Added.
        * storage/domstorage/window-attributes-exist-expected.txt: Added.
        * storage/domstorage/window-attributes-exist.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@31697 268f45cc-cd09-0410-ab3c-d52691b4dbfc

44 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/dom/Window/window-properties-expected.txt
LayoutTests/storage/domstorage/sessionstorage/iframe-events-expected.txt [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/iframe-events.html [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/index-get-and-set-expected.txt [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/index-get-and-set.html [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/resources/clearSessionStorage.js [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/resources/iframe-events-second.html [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/resources/window-open-second.html [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/simple-events-expected.txt [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/simple-events.html [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/simple-usage-expected.txt [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/simple-usage.html [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/window-open-expected.txt [new file with mode: 0644]
LayoutTests/storage/domstorage/sessionstorage/window-open.html [new file with mode: 0644]
LayoutTests/storage/domstorage/window-attributes-exist-expected.txt [new file with mode: 0644]
LayoutTests/storage/domstorage/window-attributes-exist.html [new file with mode: 0644]
WebCore/ChangeLog
WebCore/Configurations/WebCore.xcconfig
WebCore/bindings/js/JSDOMWindowCustom.cpp
WebCore/bindings/js/JSEventCustom.cpp
WebCore/bindings/js/JSStorageCustom.cpp
WebCore/dom/Event.cpp
WebCore/dom/Event.h
WebCore/dom/EventNames.h
WebCore/dom/EventTargetNode.cpp
WebCore/dom/EventTargetNode.h
WebCore/loader/FrameLoader.cpp
WebCore/page/DOMWindow.cpp
WebCore/page/DOMWindow.h
WebCore/page/DOMWindow.idl
WebCore/page/Page.cpp
WebCore/page/Page.h
WebCore/storage/OriginStorage.cpp
WebCore/storage/OriginStorage.h
WebCore/storage/SessionStorage.cpp
WebCore/storage/SessionStorage.h
WebCore/storage/Storage.cpp
WebCore/storage/Storage.h
WebCore/storage/Storage.idl
WebCore/storage/StorageEvent.cpp
WebCore/storage/StorageEvent.h
WebCore/storage/StorageMap.cpp
WebCore/storage/StorageMap.h

index 4a0c1e2..2283879 100644 (file)
@@ -1,3 +1,30 @@
+2008-04-07  Brady Eidson  <beidson@apple.com>
+
+        Begrudgingly reviewed by Sam Weinig
+
+        Initial suite of layout tests for HTML5 key/value SessionStorage (<rdar://problem/5797684>)
+
+        * fast/dom/Window/window-properties-expected.txt:
+        * storage/domstorage: Added.
+        * storage/domstorage/localstorage: Added.
+        * storage/domstorage/sessionstorage: Added.
+        * storage/domstorage/sessionstorage/iframe-events-expected.txt: Added.
+        * storage/domstorage/sessionstorage/iframe-events.html: Added.
+        * storage/domstorage/sessionstorage/index-get-and-set-expected.txt: Added.
+        * storage/domstorage/sessionstorage/index-get-and-set.html: Added.
+        * storage/domstorage/sessionstorage/resources: Added.
+        * storage/domstorage/sessionstorage/resources/clearSessionStorage.js: Added.
+        * storage/domstorage/sessionstorage/resources/iframe-events-second.html: Added.
+        * storage/domstorage/sessionstorage/resources/window-open-second.html: Added.
+        * storage/domstorage/sessionstorage/simple-events-expected.txt: Added.
+        * storage/domstorage/sessionstorage/simple-events.html: Added.
+        * storage/domstorage/sessionstorage/simple-usage-expected.txt: Added.
+        * storage/domstorage/sessionstorage/simple-usage.html: Added.
+        * storage/domstorage/sessionstorage/window-open-expected.txt: Added.
+        * storage/domstorage/sessionstorage/window-open.html: Added.
+        * storage/domstorage/window-attributes-exist-expected.txt: Added.
+        * storage/domstorage/window-attributes-exist.html: Added.
+
 2008-04-07  Alexey Proskuryakov  <ap@webkit.org>
 
         Reviewed by Dan Bernstein.
index 4a7269e..0395749 100644 (file)
@@ -1261,6 +1261,7 @@ window.history.length [number]
 window.innerHeight [number]
 window.innerWidth [number]
 window.length [number]
+window.localStorage [null]
 window.location [object Location]
 window.location.assign [function]
 window.location.hash [string]
@@ -1347,6 +1348,12 @@ window.scrollY [number]
 window.scrollbars [object BarInfo]
 window.scrollbars.visible [boolean]
 window.self [printed above as window]
+window.sessionStorage [object Storage]
+window.sessionStorage.getItem [function]
+window.sessionStorage.key [function]
+window.sessionStorage.length [number]
+window.sessionStorage.removeItem [function]
+window.sessionStorage.setItem [function]
 window.setInterval [function]
 window.setTimeout [function]
 window.status [string]
diff --git a/LayoutTests/storage/domstorage/sessionstorage/iframe-events-expected.txt b/LayoutTests/storage/domstorage/sessionstorage/iframe-events-expected.txt
new file mode 100644 (file)
index 0000000..3d9ed80
--- /dev/null
@@ -0,0 +1,25 @@
+This is the main frame of a 2-frame document. Each frame is in the same security origin and therefore shares the same sessionStorage object. As a result, each frame should receive a StorageEvent when either frame changes the sessionStorage object.
+
+Main frame about to change sessionStorage...
+Main Frame received StorageEvent:
+Key - Main Frame
+New Value - SET
+Old Value - null
+
+Subframe received storage event:
+Key - Main Frame
+New Value - SET
+Old Value - null
+
+Subframe about to change sessionStorage...
+Main Frame received StorageEvent:
+Key - Subframe
+New Value - SET
+Old Value - null
+
+Subframe received storage event:
+Key - Subframe
+New Value - SET
+Old Value - null
+
+
diff --git a/LayoutTests/storage/domstorage/sessionstorage/iframe-events.html b/LayoutTests/storage/domstorage/sessionstorage/iframe-events.html
new file mode 100644 (file)
index 0000000..7221c8f
--- /dev/null
@@ -0,0 +1,55 @@
+<html>
+<head>
+<script src="resources/clearSessionStorage.js"></script>
+<script>
+
+if (layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+function log(a)
+{
+    document.getElementById("logger").innerHTML += a + "<br>";
+}
+
+function finish()
+{
+    if (layoutTestController)
+        layoutTestController.notifyDone()
+}
+
+function handleStorageEvent(e)
+{
+    log("Main Frame received StorageEvent:");
+    log("Key           - " + e.key);
+    log("New Value     - " + e.newValue);
+    log("Old Value     - " + e.oldValue);
+    log("");
+    
+    if (e.key == "Subframe")
+        finish();
+}
+
+function startTest()
+{
+    if (!window.sessionStorage) {
+        log("window.sessionStorage DOES NOT exist");
+        finish();
+        return;
+    }
+    document.body.addEventListener("storage", handleStorageEvent, false);
+    log("Main frame about to change sessionStorage...");
+    sessionStorage.setItem("Main Frame", "SET");
+}
+
+</script>
+</head>
+<body onload="startTest();">
+This is the main frame of a 2-frame document.  Each frame is in the same security origin and therefore shares the same sessionStorage object.
+As a result, each frame should receive a StorageEvent when either frame changes the sessionStorage object.<br>
+<iframe src="resources/iframe-events-second.html"></iframe><br>
+<div id="logger"></div>
+</body>
+</html>
diff --git a/LayoutTests/storage/domstorage/sessionstorage/index-get-and-set-expected.txt b/LayoutTests/storage/domstorage/sessionstorage/index-get-and-set-expected.txt
new file mode 100644 (file)
index 0000000..f98ed7b
--- /dev/null
@@ -0,0 +1,57 @@
+This is a test to make sure you can get and set values in SessionStorage by index.
+Setting FOO using the index setter.
+Storage event fired:
+Key - FOO
+New Value - BAR
+Old Value - null
+
+Reading FOO:
+BAR
+BAR
+BAR
+
+Setting FOO again, using setItem.
+Storage event fired:
+Key - FOO
+New Value - BAZ
+Old Value - BAR
+
+Reading FOO:
+BAZ
+BAZ
+BAZ
+
+Setting FOO again, using the index setter.
+Storage event fired:
+Key - FOO
+New Value - BAT
+Old Value - BAZ
+
+Reading FOO:
+BAT
+BAT
+BAT
+
+Setting FOO again, using property-slot syntax
+Storage event fired:
+Key - FOO
+New Value - BATMAN
+Old Value - BAT
+
+Reading FOO:
+BATMAN
+BATMAN
+BATMAN
+
+Removing FOO, then trying to read it
+Storage event fired:
+Key - FOO
+New Value - null
+Old Value - BATMAN
+
+Reading FOO:
+undefined
+undefined
+null
+
+
diff --git a/LayoutTests/storage/domstorage/sessionstorage/index-get-and-set.html b/LayoutTests/storage/domstorage/sessionstorage/index-get-and-set.html
new file mode 100644 (file)
index 0000000..a4d4964
--- /dev/null
@@ -0,0 +1,90 @@
+<html>
+<head>
+<script src="resources/clearSessionStorage.js"></script>
+<script>
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+function log(a)
+{
+    document.getElementById("logger").innerHTML += a + "<br>";
+}
+
+function finish()
+{
+    if (layoutTestController)
+        layoutTestController.notifyDone()
+}
+
+function handleStorageEvent(e)
+{
+    log("Storage event fired:");
+    log("Key           - " + e.key);
+    log("New Value     - " + e.newValue);
+    log("Old Value     - " + e.oldValue);
+    log("");
+}
+
+function runTest()
+{
+    if (!window.sessionStorage) {
+        log("window.sessionStorage DOES NOT exist");
+        finish();
+        return;
+    }
+    
+    document.body.addEventListener("storage", handleStorageEvent, false);
+    
+    log("Setting FOO using the index setter.");
+    sessionStorage["FOO"] = "BAR";
+    log("Reading FOO:");
+    log(sessionStorage.FOO);
+    log(sessionStorage["FOO"]);
+    log(sessionStorage.getItem("FOO"));
+    log("");
+    
+    log("Setting FOO again, using setItem.");
+    sessionStorage.setItem("FOO", "BAZ");
+    log("Reading FOO:");
+    log(sessionStorage.FOO);
+    log(sessionStorage["FOO"]);
+    log(sessionStorage.getItem("FOO"));
+    log("");
+
+    log("Setting FOO again, using the index setter.");
+    sessionStorage["FOO"] = "BAT";
+    log("Reading FOO:");
+    log(sessionStorage.FOO);
+    log(sessionStorage["FOO"]);
+    log(sessionStorage.getItem("FOO"));
+    log("");
+    
+    log("Setting FOO again, using property-slot syntax");
+    sessionStorage.FOO = "BATMAN";
+    log("Reading FOO:");
+    log(sessionStorage.FOO);
+    log(sessionStorage["FOO"]);
+    log(sessionStorage.getItem("FOO"));
+    log("");
+    
+    log("Removing FOO, then trying to read it");
+    sessionStorage.removeItem("FOO");
+    log("Reading FOO:");
+    log(sessionStorage.FOO);
+    log(sessionStorage["FOO"]);
+    log(sessionStorage.getItem("FOO"));
+    log("");
+    
+    finish();
+}
+
+</script>
+</head>
+<body onload="runTest();">
+This is a test to make sure you can get and set values in SessionStorage by index.<br>
+<div id="logger"></div>
+</body>
+</html>
diff --git a/LayoutTests/storage/domstorage/sessionstorage/resources/clearSessionStorage.js b/LayoutTests/storage/domstorage/sessionstorage/resources/clearSessionStorage.js
new file mode 100644 (file)
index 0000000..921e47a
--- /dev/null
@@ -0,0 +1,9 @@
+function clearSessionStorage()
+{
+    var i = 0;
+    for (;i < sessionStorage.length; ++i)
+        sessionStorage.removeItem(sessionStorage.key(i));
+}
+
+if (window.sessionStorage)
+    clearSessionStorage();
diff --git a/LayoutTests/storage/domstorage/sessionstorage/resources/iframe-events-second.html b/LayoutTests/storage/domstorage/sessionstorage/resources/iframe-events-second.html
new file mode 100644 (file)
index 0000000..f7ee9c5
--- /dev/null
@@ -0,0 +1,29 @@
+<html>
+<head>
+<script>
+
+function log(a)
+{
+    parent.document.getElementById("logger").innerHTML += a + "<br>";
+}
+
+function handleStorageEvent(e)
+{
+    log("Subframe received storage event:");
+    log("Key           - " + e.key);
+    log("New Value     - " + e.newValue);
+    log("Old Value     - " + e.oldValue);
+    log("");
+    
+    if (e.key != "Subframe") {
+        log("Subframe about to change sessionStorage...");
+        sessionStorage.setItem("Subframe", "SET");
+    }
+}
+
+</script>
+</head>
+<body onload="document.body.addEventListener('storage', handleStorageEvent, false);">
+This is the subframe which exists to make sure that both frames of a same security origin receive the event for that origin's sessionStorage object mutating
+</body>
+</html>
diff --git a/LayoutTests/storage/domstorage/sessionstorage/resources/window-open-second.html b/LayoutTests/storage/domstorage/sessionstorage/resources/window-open-second.html
new file mode 100644 (file)
index 0000000..1f6d98a
--- /dev/null
@@ -0,0 +1,36 @@
+<html>
+<head>
+<script>
+
+var secondWindowLog = "Logging from second window:<br>";
+
+function log(a)
+{
+    secondWindowLog += a + "<br>";
+}
+
+function runTest()
+{
+    if (!window.sessionStorage) {
+        log("window.sessionStorage DOES NOT exist");
+        return;
+    }
+    
+    log("Value for FOO is " + window.sessionStorage.getItem("FOO"));
+    window.sessionStorage.setItem("FOO", "BAR-NEWWINDOW");
+    log("Value for FOO after changing my own copy is " + window.sessionStorage.getItem("FOO"));
+    
+    log("Value for FOO in my opening window is " + window.opener.sessionStorage.getItem("FOO"));
+    
+    window.opener.log(secondWindowLog);
+    
+    if (layoutTestController)
+        layoutTestController.notifyDone();
+}
+
+</script>
+</head>
+<body onload="runTest();">
+This is a new window to make sure there is a copy of the previous window's sessionStorage, and that they diverge after a change<br>
+</body>
+</html>
diff --git a/LayoutTests/storage/domstorage/sessionstorage/simple-events-expected.txt b/LayoutTests/storage/domstorage/sessionstorage/simple-events-expected.txt
new file mode 100644 (file)
index 0000000..29e2a07
--- /dev/null
@@ -0,0 +1,30 @@
+This is a test to make sure SessionStorage mutations fire StorageEvents
+Storage event fired:
+Key - FOO
+New Value - BAR
+Old Value - null
+Event has a URI
+Event has a source DOMWindow
+
+Storage event fired:
+Key - FU
+New Value - BAR
+Old Value - null
+Event has a URI
+Event has a source DOMWindow
+
+Storage event fired:
+Key - FOO
+New Value - null
+Old Value - BAR
+Event has a URI
+Event has a source DOMWindow
+
+Storage event fired:
+Key - FU
+New Value - null
+Old Value - BAR
+Event has a URI
+Event has a source DOMWindow
+
+
diff --git a/LayoutTests/storage/domstorage/sessionstorage/simple-events.html b/LayoutTests/storage/domstorage/sessionstorage/simple-events.html
new file mode 100644 (file)
index 0000000..efb3202
--- /dev/null
@@ -0,0 +1,59 @@
+<html>
+<head>
+<script src="resources/clearSessionStorage.js"></script>
+<script>
+
+if (layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+function log(a)
+{
+    document.getElementById("logger").innerHTML += a + "<br>";
+}
+
+function finish()
+{
+    if (layoutTestController)
+        layoutTestController.notifyDone()
+}
+
+function handleStorageEvent(e)
+{
+    log("Storage event fired:");
+    log("Key           - " + e.key);
+    log("New Value     - " + e.newValue);
+    log("Old Value     - " + e.oldValue);
+    if (e.uri)
+        log("Event has a URI");
+    if (e.source)
+        log("Event has a source DOMWindow");
+    log("");
+}
+
+function runTest()
+{
+    if (!window.sessionStorage) {
+        log("window.sessionStorage DOES NOT exist");
+        finish();
+        return;
+    }
+    
+    document.body.addEventListener("storage", handleStorageEvent, false);
+
+    window.sessionStorage.setItem("FOO", "BAR");
+    window.sessionStorage.setItem("FU", "BAR");
+    window.sessionStorage.removeItem("FOO");
+    window.sessionStorage.removeItem("FU");
+    
+    finish();
+}
+
+</script>
+</head>
+<body onload="runTest();">
+This is a test to make sure SessionStorage mutations fire StorageEvents<br>
+<div id="logger"></div>
+</body>
+</html>
diff --git a/LayoutTests/storage/domstorage/sessionstorage/simple-usage-expected.txt b/LayoutTests/storage/domstorage/sessionstorage/simple-usage-expected.txt
new file mode 100644 (file)
index 0000000..4ad29f2
--- /dev/null
@@ -0,0 +1,13 @@
+This test trys simple operations on SessionStorage
+Length is 0
+Value for FOO is null
+Length is 1
+Value for FOO is BAR
+Key for index 0 is FOO
+Correctly caught exception trying to access key at index 1
+Error: INDEX_SIZE_ERR: DOM Exception 1
+Length is 1
+Value for FOO is BAZ
+Length is 0
+Value for FOO is null
+
diff --git a/LayoutTests/storage/domstorage/sessionstorage/simple-usage.html b/LayoutTests/storage/domstorage/sessionstorage/simple-usage.html
new file mode 100644 (file)
index 0000000..9633b3a
--- /dev/null
@@ -0,0 +1,53 @@
+<html>
+<head>
+<script src="resources/clearSessionStorage.js"></script>
+<script>
+
+if (layoutTestController)
+    layoutTestController.dumpAsText();
+
+function log(a)
+{
+    document.getElementById("logger").innerHTML += a + "<br>";
+}
+
+function runTest()
+{
+    if (!window.sessionStorage) {
+        log("window.sessionStorage DOES NOT exist");
+        return;
+    }
+    
+    log("Length is " + window.sessionStorage.length);
+    log("Value for FOO is " + window.sessionStorage.getItem("FOO"));
+
+    window.sessionStorage.setItem("FOO", "BAR");
+    
+    log("Length is " + window.sessionStorage.length);
+    log("Value for FOO is " + window.sessionStorage.getItem("FOO"));
+    log("Key for index 0 is " + window.sessionStorage.key(0));
+    try {
+        window.sessionStorage.key(1);
+    } catch(e) {
+        log("Correctly caught exception trying to access key at index 1");
+        log(e);
+    }
+    
+    window.sessionStorage.setItem("FOO", "BAZ");
+    
+    log("Length is " + window.sessionStorage.length);
+    log("Value for FOO is " + window.sessionStorage.getItem("FOO"));
+    
+    window.sessionStorage.removeItem("FOO");
+    
+    log("Length is " + window.sessionStorage.length);
+    log("Value for FOO is " + window.sessionStorage.getItem("FOO"));
+}
+
+</script>
+</head>
+<body onload="runTest();">
+This test trys simple operations on SessionStorage<br>
+<div id="logger"></div>
+</body>
+</html>
diff --git a/LayoutTests/storage/domstorage/sessionstorage/window-open-expected.txt b/LayoutTests/storage/domstorage/sessionstorage/window-open-expected.txt
new file mode 100644 (file)
index 0000000..c613414
--- /dev/null
@@ -0,0 +1,8 @@
+This is a new window to make sure there is a copy of the previous window's sessionStorage, and that they diverge after a change
+Value for FOO is BAR
+Logging from second window:
+Value for FOO is BAR
+Value for FOO after changing my own copy is BAR-NEWWINDOW
+Value for FOO in my opening window is BAR
+
+
diff --git a/LayoutTests/storage/domstorage/sessionstorage/window-open.html b/LayoutTests/storage/domstorage/sessionstorage/window-open.html
new file mode 100644 (file)
index 0000000..0ce645b
--- /dev/null
@@ -0,0 +1,39 @@
+<html>
+<head>
+<script src="resources/clearSessionStorage.js"></script>
+<script>
+
+if (layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.setCanOpenWindows();
+    layoutTestController.waitUntilDone();
+}
+
+function log(a)
+{
+    document.getElementById("logger").innerHTML += a + "<br>";
+}
+
+function runTest()
+{
+    if (!window.sessionStorage) {
+        log("window.sessionStorage DOES NOT exist");
+        return;
+    }
+    
+    window.log = log;
+    
+    window.sessionStorage.setItem("FOO", "BAR");
+    log("Value for FOO is " + window.sessionStorage.getItem("FOO"));    
+    window.open("resources/window-open-second.html");
+    
+
+}
+
+</script>
+</head>
+<body onload="runTest();">
+This is a new window to make sure there is a copy of the previous window's sessionStorage, and that they diverge after a change<br>
+<div id="logger"></div>
+</body>
+</html>
diff --git a/LayoutTests/storage/domstorage/window-attributes-exist-expected.txt b/LayoutTests/storage/domstorage/window-attributes-exist-expected.txt
new file mode 100644 (file)
index 0000000..175ce39
--- /dev/null
@@ -0,0 +1,9 @@
+This test checks to see if window.localStorage and window.sessionStorage exist.
+window.sessionStorage exists
+Storage object sessionStorage has length
+Storage object sessionStorage has key
+Storage object sessionStorage has getItem
+Storage object sessionStorage has setItem
+Storage object sessionStorage has removeItem
+window.localStorage DOES NOT exist
+
diff --git a/LayoutTests/storage/domstorage/window-attributes-exist.html b/LayoutTests/storage/domstorage/window-attributes-exist.html
new file mode 100644 (file)
index 0000000..1f7624c
--- /dev/null
@@ -0,0 +1,48 @@
+<html>
+<head>
+<script>
+
+if (layoutTestController)
+    layoutTestController.dumpAsText();
+
+function log(a)
+{
+    document.getElementById("logger").innerHTML += a + "<br>";
+}
+
+function testStorage(name, storage)
+{
+    if ("length" in storage)
+        log("Storage object " + name + " has length");
+    if ("key" in storage)
+        log("Storage object " + name + " has key");
+    if ("getItem" in storage)
+        log("Storage object " + name + " has getItem");
+    if ("setItem" in storage)
+        log("Storage object " + name + " has setItem");
+    if ("removeItem" in storage)
+        log("Storage object " + name + " has removeItem");
+}
+
+function runTest()
+{
+    if (window.sessionStorage) {
+        log("window.sessionStorage exists");
+        testStorage("sessionStorage", window.sessionStorage);
+    } else
+        log("window.sessionStorage DOES NOT exist");
+
+    if (window.localStorage) {
+        log("window.localStorage exists");
+        testStorage("localStorage", window.localStorage);
+    } else
+        log("window.localStorage DOES NOT exist");
+}
+
+</script>
+</head>
+<body onload="runTest();">
+This test checks to see if window.localStorage and window.sessionStorage exist.<br>
+<div id="logger"></div>
+</body>
+</html>
index fdae469..1d0afb7 100644 (file)
@@ -1,3 +1,121 @@
+2008-04-07  Brady Eidson  <beidson@apple.com>
+
+        Lovingly reviewed by Sam Weinig
+
+        <rdar://problem/5797684> - HTML5 SessionStorage and underpinnings for LocalStorage
+
+        Tests: storage/domstorage/sessionstorage/iframe-events.html
+               storage/domstorage/sessionstorage/index-get-and-set.html
+               storage/domstorage/sessionstorage/simple-events.html
+               storage/domstorage/sessionstorage/simple-usage.html
+               storage/domstorage/sessionstorage/window-open.html
+               storage/domstorage/window-attributes-exist.html
+
+        * Configurations/WebCore.xcconfig: Define to enable DOM_STORAGE
+
+        * bindings/js/JSDOMWindowCustom.cpp:
+        (WebCore::JSDOMWindow::mark): Add optionalSessionStorage case
+
+        * bindings/js/JSEventCustom.cpp:
+        (WebCore::toJS): Add StorageEvent case
+
+        * bindings/js/JSStorageCustom.cpp:
+        (WebCore::JSStorage::canGetItemsForName):
+        (WebCore::JSStorage::nameGetter): If the property doesn't exist on the object, call through to getItem()
+        (WebCore::JSStorage::customPut): If the property doesn't exist on the object, call through to setItem()
+
+        * dom/Event.cpp:
+        (WebCore::Event::isStorageEvent):
+        * dom/Event.h:
+
+        * dom/EventNames.h: Add "storage"
+
+        * dom/EventTargetNode.cpp:
+        (WebCore::EventTargetNode::dispatchStorageEvent):
+        * dom/EventTargetNode.h:
+
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::createWindow): After a new page has been created, set its SessionStorage object
+          to a copy of the previous Page's
+
+        * page/DOMWindow.cpp:
+        (WebCore::DOMWindow::sessionStorage): Accessor to pull the appropriate OriginStorage out of the Page's
+          SessionStorage.
+        (WebCore::DOMWindow::localStorage): To be filled in later
+        * page/DOMWindow.h:
+        (WebCore::DOMWindow::optionalSessionStorage): Return the session Storage object for this window to mark, 
+          if any exists
+        * page/DOMWindow.idl:
+
+        * page/Page.cpp:
+        (WebCore::Page::sessionStorage):  Create and/or return the SessionStorage for this Page.
+        (WebCore::Page::setSessionStorage): Set the SessionStorage for this Page - used in FrameLoader after a
+          Window.open();
+        * page/Page.h:
+
+        * storage/OriginStorage.cpp: Intermediate layer between individual Storage objects, and shared StorageMap 
+          objects.  There is one OriginStorage object per SecurityOrigin in each "unique set of storage areas", such
+          as the SessionStorage.  This layer forwards DOM-level calls down to the backing StorageMap, handles 
+          copy-on-write along with the StorageMap, fires StorageEvents to the DOM when a value is changed, and will
+          eventually handle quota enforcement.
+        (WebCore::OriginStorage::create):
+        (WebCore::OriginStorage::OriginStorage):
+        (WebCore::OriginStorage::~OriginStorage):
+        (WebCore::OriginStorage::copy):
+        (WebCore::OriginStorage::length):
+        (WebCore::OriginStorage::key):
+        (WebCore::OriginStorage::getItem):
+        (WebCore::OriginStorage::setItem):
+        (WebCore::OriginStorage::removeItem):
+        (WebCore::OriginStorage::contains):
+        (WebCore::OriginStorage::dispatchStorageEvent):
+        * storage/OriginStorage.h:
+
+        * storage/SessionStorage.cpp: From the HTML5 spec:
+          "Each top-level browsing context has a unique set of session storage areas, one for each origin."
+          This object represents that "unique set of session storage areas", and creates or returns the Storage
+          object for the requested SecurityOrigin
+        (WebCore::SessionStorage::create):
+        (WebCore::SessionStorage::SessionStorage):
+        (WebCore::SessionStorage::copy):
+        (WebCore::SessionStorage::originStorage):
+        * storage/SessionStorage.h:
+        (WebCore::SessionStorage::page):
+
+        * storage/Storage.cpp: Representation of the DOM-level object, wrapped by JSStorage.  There is a unique
+          Storage object per Window (per-Frame) that wraps a specific shared OriginStorage object.
+        (WebCore::Storage::create):
+        (WebCore::Storage::Storage):
+        (WebCore::Storage::length):
+        (WebCore::Storage::key):
+        (WebCore::Storage::getItem):
+        (WebCore::Storage::setItem):
+        (WebCore::Storage::removeItem):
+        (WebCore::Storage::contains):
+        * storage/Storage.h:
+        * storage/Storage.idl:
+
+        * storage/StorageEvent.cpp:
+        (WebCore::StorageEvent::StorageEvent):
+        (WebCore::StorageEvent::initStorageEvent):
+        * storage/StorageEvent.h:
+        (WebCore::StorageEvent::isStorageEvent):
+
+        * storage/StorageMap.cpp: The physical map of key/value pairs that is shared between OriginStorage objects, 
+          and implements copy-on-write semantics whenever a value is changed
+        (WebCore::StorageMap::create):
+        (WebCore::StorageMap::StorageMap):
+        (WebCore::StorageMap::copy):
+        (WebCore::StorageMap::invalidateIterator): Used to support the key(unsigned i) part of the API
+        (WebCore::StorageMap::setIteratorToIndex): Ditto
+        (WebCore::StorageMap::length):
+        (WebCore::StorageMap::key):
+        (WebCore::StorageMap::getItem):
+        (WebCore::StorageMap::setItem):
+        (WebCore::StorageMap::removeItem):
+        (WebCore::StorageMap::contains):
+        * storage/StorageMap.h:
+
 2008-04-07  Timothy Hatcher  <timothy@apple.com>
 
         Renamed various functions that are internal to the Console
index 555f764..26e3984 100644 (file)
@@ -15,4 +15,4 @@ PRODUCT_NAME = WebCore;
 OTHER_LDFLAGS = -lWebCoreSQLite3 -lobjc -sub_library libobjc -umbrella WebKit;
 
 // This needs to be kept sorted, and in sync with FEATURE_DEFINES in JavaScriptCore.xcconfig, WebKit.xcconfig and the default settings of build-webkit.
-FEATURE_DEFINES = ENABLE_CROSS_DOCUMENT_MESSAGING ENABLE_DATABASE ENABLE_ICONDATABASE ENABLE_SVG ENABLE_SVG_ANIMATION ENABLE_SVG_AS_IMAGE ENABLE_SVG_FONTS ENABLE_SVG_FOREIGN_OBJECT ENABLE_SVG_USE ENABLE_VIDEO ENABLE_XPATH ENABLE_XSLT;
+FEATURE_DEFINES = ENABLE_CROSS_DOCUMENT_MESSAGING ENABLE_DATABASE ENABLE_DOM_STORAGE ENABLE_ICONDATABASE ENABLE_SVG ENABLE_SVG_ANIMATION ENABLE_SVG_AS_IMAGE ENABLE_SVG_FONTS ENABLE_SVG_FOREIGN_OBJECT ENABLE_SVG_USE ENABLE_VIDEO ENABLE_XPATH ENABLE_XSLT;
index 0ff8e3a..dfd8d92 100644 (file)
@@ -60,6 +60,9 @@ void JSDOMWindow::mark()
     markDOMObjectWrapper(impl()->optionalStatusbar());
     markDOMObjectWrapper(impl()->optionalToolbar());
     markDOMObjectWrapper(impl()->optionalLocation());
+#if ENABLE(DOM_STORAGE)
+    markDOMObjectWrapper(impl()->optionalSessionStorage());
+#endif
 }
 
 bool JSDOMWindow::customGetOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
index f6eafff..279de4a 100644 (file)
 #include "WheelEvent.h"
 #include "kjs_events.h"
 
+#if ENABLE(DOM_STORAGE)
+#include "JSStorageEvent.h"
+#include "StorageEvent.h"
+#endif
+
 #if ENABLE(CROSS_DOCUMENT_MESSAGING)
 #include "JSMessageEvent.h"
 #include "MessageEvent.h"
@@ -105,6 +110,10 @@ JSValue* toJS(ExecState* exec, Event* event)
 #endif
     else if (event->isProgressEvent())
         ret = new JSProgressEvent(JSProgressEventPrototype::self(exec), static_cast<ProgressEvent*>(event));
+#if ENABLE(DOM_STORAGE)
+    else if (event->isStorageEvent())
+        ret = new JSStorageEvent(JSStorageEventPrototype::self(exec), static_cast<StorageEvent*>(event));
+#endif
     else
         ret = new JSEvent(JSEventPrototype::self(exec), event);
 
index 946a6df..926db62 100644 (file)
 #include "config.h"
 #include "JSStorage.h"
 
-// FIXME: Code will go here
+#if ENABLE(DOM_STORAGE)
+
+#include "PlatformString.h"
+#include "Storage.h"
+
+using namespace KJS;
+
+namespace WebCore {
+
+bool JSStorage::canGetItemsForName(ExecState*, Storage* impl, const Identifier& propertyName)
+{
+    return impl->contains(propertyName);
+}
+
+JSValue* JSStorage::nameGetter(ExecState* exec, JSObject* originalObject, const Identifier& propertyName, const PropertySlot& slot)
+{
+    JSStorage* thisObj = static_cast<JSStorage*>(slot.slotBase());
+    return jsStringOrNull(thisObj->impl()->getItem(propertyName));
+}
+
+bool JSStorage::customPut(ExecState* exec, const Identifier& propertyName, JSValue* value)
+{
+    // We need to only perform the custom put if the object doesn't have a native property by this name.
+    // since hasProperty() would end up calling canGetItemsForName() and be fooled, we need to check
+    // the native property slots manually.
+    PropertySlot slot;
+    if (getStaticValueSlot<JSStorage, Base>(exec, s_info.propHashTable, this, propertyName, slot))
+        return false;
+        
+    JSValue* prototype = this->prototype();
+    if (prototype->isObject() && static_cast<JSObject*>(prototype)->hasProperty(exec, propertyName))
+        return false;
+    
+    String stringValue = valueToStringWithNullCheck(exec, value);
+    if (exec->hadException())
+        return true;
+    
+    ExceptionCode ec = 0;
+    impl()->setItem(propertyName, stringValue, ec);
+    setDOMException(exec, ec);
+
+    return true;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(DOM_STORAGE)
index 543dfff..07f68f4 100644 (file)
@@ -141,6 +141,12 @@ bool Event::isSVGZoomEvent() const
 }
 #endif
 
+#if ENABLE(DOM_STORAGE)
+bool Event::isStorageEvent() const
+{
+    return false;
+}
+#endif
 
 bool Event::storesResultAsString() const
 {
index b74bc16..caa6646 100644 (file)
@@ -111,6 +111,9 @@ namespace WebCore {
 #if ENABLE(SVG)
         virtual bool isSVGZoomEvent() const;
 #endif
+#if ENABLE(DOM_STORAGE)
+        virtual bool isStorageEvent() const;
+#endif
 
         bool propagationStopped() const { return m_propagationStopped; }
 
index 843fb16..7e0b797 100644 (file)
@@ -70,6 +70,7 @@ namespace WebCore { namespace EventNames {
     macro(search) \
     macro(select) \
     macro(selectstart) \
+    macro(storage) \
     macro(submit) \
     macro(textInput) \
     macro(unload) \
index 146ee8b..79e7bbc 100644 (file)
 #include "TextEvent.h"
 #include "WheelEvent.h"
 
+#if ENABLE(DOM_STORAGE)
+#include "StorageEvent.h"
+#endif
+
 namespace WebCore {
 
 using namespace EventNames;
@@ -351,6 +355,15 @@ bool EventTargetNode::dispatchProgressEvent(const AtomicString &eventType, bool
     return dispatchEvent(new ProgressEvent(eventType, lengthComputableArg, loadedArg, totalArg), ec, true);
 }
 
+#if ENABLE(DOM_STORAGE)
+void EventTargetNode::dispatchStorageEvent(const AtomicString &eventType, const String& key, const String& oldValue, const String& newValue, Frame* source)
+{
+    ASSERT(!eventDispatchForbidden());
+    ExceptionCode ec = 0;
+    dispatchEvent(new StorageEvent(eventType, key, oldValue, newValue, source->document()->documentURI(), source->domWindow()), ec, true); 
+}
+#endif
+
 void EventTargetNode::removeHTMLEventListener(const AtomicString &eventType)
 {
     if (!m_regdListeners) // nothing to remove
index 9a0647b..c5d8dcd 100644 (file)
@@ -30,6 +30,8 @@
 
 namespace WebCore {
 
+class Frame;
+
 class EventTargetNode : public Node,
                         public EventTarget {
 public:
@@ -63,6 +65,9 @@ public:
     void dispatchSimulatedMouseEvent(const AtomicString& eventType, PassRefPtr<Event> underlyingEvent = 0);
     void dispatchSimulatedClick(PassRefPtr<Event> underlyingEvent, bool sendMouseEvents = false, bool showPressedLook = true);
     bool dispatchProgressEvent(const AtomicString &eventType, bool lengthComputableArg, unsigned loadedArg, unsigned totalArg);
+#if ENABLE(DOM_STORAGE)
+    void dispatchStorageEvent(const AtomicString &eventType, const String& key, const String& oldValue, const String& newValue, Frame* source);
+#endif
 
     virtual void handleLocalEvents(Event*, bool useCapture);
 
index 7116f9d..6182d26 100644 (file)
 #include "SVGViewSpec.h"
 #endif
 
+#if ENABLE(DOM_STORAGE)
+#include "OriginStorage.h"
+#include "SessionStorage.h"
+#endif
+
 using KJS::UString;
 using KJS::JSLock;
 using KJS::JSValue;
@@ -327,13 +332,20 @@ Frame* FrameLoader::createWindow(FrameLoader* frameLoaderForFrameLookup, const F
     // FIXME: Setting the referrer should be the caller's responsibility.
     FrameLoadRequest requestWithReferrer = request;
     requestWithReferrer.resourceRequest().setHTTPReferrer(m_outgoingReferrer);
-    
-    Page* page = m_frame->page();
-    if (page)
-        page = page->chrome()->createWindow(m_frame, requestWithReferrer, features);
+
+    Page* oldPage = m_frame->page();
+    if (!oldPage)
+        return 0;
+        
+    Page* page = oldPage->chrome()->createWindow(m_frame, requestWithReferrer, features);
     if (!page)
         return 0;
 
+#if ENABLE(DOM_STORAGE)
+    if (SessionStorage* oldSessionStorage = oldPage->sessionStorage(false))
+        page->setSessionStorage(oldSessionStorage->copy(page));
+#endif
+
     Frame* frame = page->mainFrame();
     if (request.frameName() != "_blank")
         frame->tree()->setName(request.frameName());
index 5208500..9aec4f0 100644 (file)
 #include "Database.h"
 #endif
 
+#if ENABLE(DOM_STORAGE)
+#include "OriginStorage.h"
+#include "SessionStorage.h"
+#include "Storage.h"
+#endif
+
 using std::min;
 using std::max;
 
@@ -168,6 +174,12 @@ void DOMWindow::clear()
     if (m_location)
         m_location->disconnectFrame();
     m_location = 0;
+    
+#if ENABLE(DOM_STORAGE)
+    if (m_sessionStorage)
+        m_sessionStorage->disconnectFrame();
+    m_sessionStorage = 0;
+#endif
 }
 
 Screen* DOMWindow::screen() const
@@ -247,6 +259,32 @@ Location* DOMWindow::location() const
     return m_location.get();
 }
 
+#if ENABLE(DOM_STORAGE)
+Storage* DOMWindow::sessionStorage() const
+{
+    if (m_sessionStorage)
+        return m_sessionStorage.get();
+        
+    Page* page = m_frame->page();
+    if (!page)
+        return 0;
+
+    Document* document = m_frame->document();
+    if (!document)
+        return 0;
+
+    RefPtr<OriginStorage> originStorage = page->sessionStorage()->originStorage(document->securityOrigin());
+    m_sessionStorage = Storage::create(m_frame, originStorage.release());
+    return m_sessionStorage.get();
+}
+
+Storage* DOMWindow::localStorage() const
+{
+    // FIXME: When implementing LocalStorage, return appropriate object from a centralized "LocalStorage repository"
+    return 0;
+}
+#endif
+
 #if ENABLE(CROSS_DOCUMENT_MESSAGING)
 void DOMWindow::postMessage(const String& message, const String& domain, const String& uri, DOMWindow* source) const
 {
index 7c17992..8d190a9 100644 (file)
@@ -48,6 +48,11 @@ namespace WebCore {
     class Navigator;
     class Screen;
 
+#if ENABLE(DOM_STORAGE)
+    class SessionStorage;
+    class Storage;
+#endif
+
     typedef int ExceptionCode;
 
     class DOMWindow : public RefCounted<DOMWindow> {
@@ -145,6 +150,12 @@ namespace WebCore {
         PassRefPtr<Database> openDatabase(const String& name, const String& version, const String& displayName, unsigned long estimatedSize, ExceptionCode&);
 #endif
 
+#if ENABLE(DOM_STORAGE)
+        // HTML 5 key/value storage
+        Storage* sessionStorage() const;
+        Storage* localStorage() const;
+#endif
+
         Console* console() const;
         
 #if ENABLE(CROSS_DOCUMENT_MESSAGING)
@@ -173,6 +184,9 @@ namespace WebCore {
         Console* optionalConsole() const { return m_console.get(); }
         Navigator* optionalNavigator() const { return m_navigator.get(); }
         Location* optionalLocation() const { return m_location.get(); }
+#if ENABLE(DOM_STORAGE)
+        Storage* optionalSessionStorage() const { return m_sessionStorage.get(); }
+#endif
 
     private:
         DOMWindow(Frame*);
@@ -190,6 +204,9 @@ namespace WebCore {
         mutable RefPtr<Console> m_console;
         mutable RefPtr<Navigator> m_navigator;
         mutable RefPtr<Location> m_location;
+#if ENABLE(DOM_STORAGE)
+        mutable RefPtr<Storage> m_sessionStorage;
+#endif
     };
 
 } // namespace WebCore
index ed5cce7..a6786ed 100644 (file)
@@ -135,6 +135,10 @@ module window {
         Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize)
             raises(DOMException);
 #endif
+#if defined(ENABLE_DOM_STORAGE)
+        readonly attribute Storage sessionStorage;
+        readonly attribute Storage localStorage;
+#endif
 
                  attribute [Replaceable] Console console;
 
@@ -289,6 +293,11 @@ module window {
         attribute MessageEventConstructor MessageEvent;
 #endif
 
+#if defined(ENABLE_DOM_STORAGE)
+        attribute StorageConstructor Storage;
+        attribute StorageEventConstructor StorageEvent;
+#endif
+
 #if defined(ENABLE_VIDEO)
         attribute HTMLAudioElementConstructor HTMLAudioElement;
         attribute HTMLMediaElementConstructor HTMLMediaElement;
index 129d297..4cf5b3b 100644 (file)
 #include <kjs/JSLock.h>
 #include <wtf/HashMap.h>
 
+#if ENABLE(DOM_STORAGE)
+#include "OriginStorage.h"
+#include "SessionStorage.h"
+#endif
+
 namespace WebCore {
 
 typedef HashMap<String, PageGroup*> PageGroupMap;
@@ -474,4 +479,20 @@ void Page::setDebugger(KJS::Debugger* debugger)
         frame->scriptProxy()->attachDebugger(m_debugger);
 }
 
+#if ENABLE(DOM_STORAGE)
+SessionStorage* Page::sessionStorage(bool optionalCreate)
+{
+    if (!m_sessionStorage && optionalCreate)
+        m_sessionStorage = SessionStorage::create(this);
+
+    return m_sessionStorage.get();
+}
+
+void Page::setSessionStorage(PassRefPtr<SessionStorage> newStorage)
+{
+    ASSERT(newStorage->page() == this);
+    m_sessionStorage = newStorage;
+}
+#endif
+
 } // namespace WebCore
index e153707..761573e 100644 (file)
@@ -60,6 +60,9 @@ namespace WebCore {
     class ProgressTracker;
     class Selection;
     class SelectionController;
+#if ENABLE(DOM_STORAGE)
+    class SessionStorage;
+#endif
     class Settings;
     class KURL;
 
@@ -156,6 +159,11 @@ namespace WebCore {
         static void allVisitedStateChanged(PageGroup*);
         static void visitedStateChanged(PageGroup*, unsigned visitedHash);
 
+#if ENABLE(DOM_STORAGE)
+        SessionStorage* sessionStorage(bool optionalCreate = true);
+        void setSessionStorage(PassRefPtr<SessionStorage>);
+#endif
+
     private:
         void initGroup();
 
@@ -196,6 +204,9 @@ namespace WebCore {
 
         KJS::Debugger* m_debugger;
 
+#if ENABLE(DOM_STORAGE)
+        RefPtr<SessionStorage> m_sessionStorage;
+#endif
 #if PLATFORM(WIN) || (PLATFORM(WX) && defined(__WXMSW__))
         static HINSTANCE s_instanceHandle;
 #endif
index 71978de..238d498 100644 (file)
 #include "config.h"
 #include "OriginStorage.h"
 
-// FIXME: Code will go here
+#include "CString.h"
+#include "EventNames.h"
+#include "ExceptionCode.h"
+#include "Frame.h"
+#include "FrameTree.h"
+#include "Page.h"
+#include "PlatformString.h"
+#include "SecurityOrigin.h"
+#include "StorageMap.h"
 
+namespace WebCore {
+
+PassRefPtr<OriginStorage> OriginStorage::create(Page* page, SecurityOrigin* origin)
+{
+    return adoptRef(new OriginStorage(page, origin));
+}
+
+OriginStorage::OriginStorage(Page* page, SecurityOrigin* origin)
+    : m_page(page)
+    , m_securityOrigin(origin)
+    , m_storageMap(StorageMap::create())
+{
+}
+
+OriginStorage::OriginStorage(Page* page, SecurityOrigin* origin, PassRefPtr<StorageMap> map)
+    : m_page(page)
+    , m_securityOrigin(origin)
+    , m_storageMap(map)
+{
+}
+
+OriginStorage::~OriginStorage()
+{
+}
+
+PassRefPtr<OriginStorage> OriginStorage::copy(Page* newPage, SecurityOrigin* origin)
+{
+    return adoptRef(new OriginStorage(newPage, origin, m_storageMap));
+}
+
+unsigned OriginStorage::length() const
+{
+    return m_storageMap->length();
+}
+
+String OriginStorage::key(unsigned index, ExceptionCode& ec) const
+{
+    String key;
+    
+    if (!m_storageMap->key(index, key)) {
+        ec = INDEX_SIZE_ERR;
+        return String();
+    }
+        
+    return key;
+}
+
+String OriginStorage::getItem(const String& key) const
+{
+    return m_storageMap->getItem(key);
+}
+
+void OriginStorage::setItem(const String& key, const String& value, ExceptionCode& ec, Frame* frame)
+{
+    // Per the spec, inserting a NULL value into the map is the same as removing the key altogether
+    if (value.isNull()) {
+        removeItem(key, frame);
+        return;
+    }
+    
+    // FIXME: For LocalStorage where a disk quota will be enforced, here is where we need to do quota checking.
+    //        If we decide to enforce a memory quota for SessionStorage, this is where we'd do that, also.
+    // if (<over quota>) {
+    //     ec = INVALID_ACCESS_ERR;
+    //     return;
+    // }
+    
+    String oldValue;   
+    RefPtr<StorageMap> newMap = m_storageMap->setItem(key, value, oldValue);
+    
+    if (newMap)
+        m_storageMap = newMap.release();
+    
+    dispatchStorageEvent(key, oldValue, value, frame);
+}
+
+void OriginStorage::removeItem(const String& key, Frame* frame)
+{    
+    String oldValue;
+    RefPtr<StorageMap> newMap = m_storageMap->removeItem(key, oldValue);
+    if (newMap)
+        m_storageMap = newMap.release();
+    
+    // Fire a StorageEvent only if an item was actually removed
+    if (!oldValue.isNull())
+        dispatchStorageEvent(key, oldValue, String(), frame);
+}
+
+bool OriginStorage::contains(const String& key) const
+{
+    return m_storageMap->contains(key);
+}
+
+void OriginStorage::dispatchStorageEvent(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame)
+{
+    // For SessionStorage events, each frame in the page's frametree with the same origin as this Storage needs to be notified of the change
+    for (Frame* frame = m_page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
+        if (frame->document()->securityOrigin()->equal(m_securityOrigin.get())) {
+            if (HTMLElement* body = frame->document()->body())
+                body->dispatchStorageEvent(EventNames::storageEvent, key, oldValue, newValue, sourceFrame);        
+        }
+    }
+}
+
+}
index dc0e48e..54aa2a1 100644 (file)
 #ifndef OriginStorage_h
 #define OriginStorage_h
 
-// FIXME: Code will go here
+#include <wtf/Forward.h>
+#include <wtf/RefCounted.h>
+#include <wtf/RefPtr.h>
 
-#endif // StorageEvent_h
+namespace WebCore {
+
+    class Frame;
+    class Page;
+    class SecurityOrigin;
+    class StorageMap;
+    class String;
+    typedef int ExceptionCode;
+
+    class OriginStorage : public RefCounted<OriginStorage> {
+    public:
+        virtual ~OriginStorage();
+        
+        static PassRefPtr<OriginStorage> create(Page*, SecurityOrigin*);
+        PassRefPtr<OriginStorage> copy(Page*, SecurityOrigin*);
+        
+        unsigned length() const;
+        String key(unsigned index, ExceptionCode&) const;
+        String getItem(const String&) const;
+        void setItem(const String& key, const String& value, ExceptionCode&, Frame* sourceFrame);
+        void removeItem(const String&, Frame* sourceFrame);
+
+        bool contains(const String& key) const;
+
+    private:
+        OriginStorage(Page*, SecurityOrigin*);
+        OriginStorage(Page*, SecurityOrigin*, PassRefPtr<StorageMap>);
+        
+        void dispatchStorageEvent(const String& key, const String& oldValue, const String& newValue, Frame* sourceFrame);
+        
+        Page* m_page;
+        RefPtr<SecurityOrigin> m_securityOrigin;
+        RefPtr<StorageMap> m_storageMap;
+    };
+
+} // namespace WebCore
+
+#endif // OriginStorage_h
index 9e29eac..81d5a09 100644 (file)
 #include "config.h"
 #include "SessionStorage.h"
 
-// FIXME: Code will go here
+#include "OriginStorage.h"
+#include "StorageMap.h"
+
+namespace WebCore {
+
+PassRefPtr<SessionStorage> SessionStorage::create(Page* page)
+{
+    return adoptRef(new SessionStorage(page));
+}
+
+SessionStorage::SessionStorage(Page* page)
+    : m_page(page)
+{
+}
+
+PassRefPtr<SessionStorage> SessionStorage::copy(Page* newPage)
+{
+    RefPtr<SessionStorage> newSession = SessionStorage::create(newPage);
+    
+    OriginStorageMap::iterator end = m_originStorageMap.end();
+    for (OriginStorageMap::iterator i = m_originStorageMap.begin(); i != end; ++i)
+        newSession->m_originStorageMap.set(i->first, i->second->copy(newPage, i->first.get()));
+        
+    return newSession.release();
+}
+
+PassRefPtr<OriginStorage> SessionStorage::originStorage(SecurityOrigin* origin)
+{
+    RefPtr<OriginStorage> originStorage;
+    if (originStorage = m_originStorageMap.get(origin))
+        return originStorage.release();
+        
+    originStorage = OriginStorage::create(m_page, origin);
+    m_originStorageMap.set(origin, originStorage);
+    return originStorage.release();
+}
+
+}
index 5a4225b..349e47b 100644 (file)
 #ifndef SessionStorage_h
 #define SessionStorage_h
 
-// FIXME: Code will go here
+#include "SecurityOriginHash.h"
 
-#endif // StorageEvent_h
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
+#include <wtf/RefCounted.h>
+
+namespace WebCore {
+
+    class OriginStorage;
+    class Page;
+
+    class SessionStorage : public RefCounted<SessionStorage> {
+    public:
+        static PassRefPtr<SessionStorage> create(Page*);
+        PassRefPtr<SessionStorage> copy(Page*);
+        
+        PassRefPtr<OriginStorage> originStorage(SecurityOrigin*);
+
+#ifndef NDEBUG
+        Page* page() { return m_page; }
+#endif
+
+    private:
+        SessionStorage(Page*);
+
+        Page* m_page;
+        
+        typedef HashMap<RefPtr<SecurityOrigin>, RefPtr<OriginStorage>, SecurityOriginHash, SecurityOriginTraits> OriginStorageMap;
+        OriginStorageMap m_originStorageMap;
+    };
+
+} // namespace WebCore
+
+#endif // SessionStorage_h
index f95db85..360c542 100644 (file)
 #include "config.h"
 #include "Storage.h"
 
+#include "OriginStorage.h"
 #include "PlatformString.h"
-
-// FIXME: More code will go here
+#include <wtf/PassRefPtr.h>
 
 namespace WebCore {
 
+PassRefPtr<Storage> Storage::create(Frame* frame, PassRefPtr<OriginStorage> originStorage)
+{
+    return adoptRef(new Storage(frame, originStorage));
+}
+
+Storage::Storage(Frame* frame, PassRefPtr<OriginStorage> originStorage)
+    : m_frame(frame)
+    , m_originStorage(originStorage)
+{
+    ASSERT(m_frame);
+    ASSERT(m_originStorage);
+}
+
 unsigned Storage::length() const
 {
-    return 0;
+    if (!m_frame)
+        return 0;
+
+    return m_originStorage->length();
+}
+
+String Storage::key(unsigned index, ExceptionCode& ec) const
+{
+    ec = 0;
+    if (!m_frame)
+        return String();
+
+    return m_originStorage->key(index, ec);
 }
 
-String Storage::key(unsigned index, ExceptionCode&) const
+String Storage::getItem(const String& key) const
 {
-    return String();
+    if (!m_frame)
+        return String();
+
+    return m_originStorage->getItem(key);
 }
 
-String Storage::getItem(const String&) const
+void Storage::setItem(const String& key, const String& value, ExceptionCode& ec)
 {
-    return String();
+    ec = 0;
+    if (!m_frame)
+        return;
+
+    m_originStorage->setItem(key, value, ec, m_frame);
 }
 
-void Storage::setItem(const String& key, const String& value, ExceptionCode&)
+void Storage::removeItem(const String& key)
 {
+    if (!m_frame)
+        return;
+
+    m_originStorage->removeItem(key, m_frame);
 }
 
-void Storage::removeItem(const String&)
+bool Storage::contains(const String& key) const
 {
+    if (!m_frame)
+        return false;
+
+    return m_originStorage->contains(key);
 }
 
 }
index 70fbc90..0e9ac37 100644 (file)
 #ifndef Storage_h
 #define Storage_h
 
-// FIXME: More code will go here
+#include "OriginStorage.h"
 
+#include <wtf/Forward.h>
 #include <wtf/RefCounted.h>
+#include <wtf/RefPtr.h>
 
 namespace WebCore {
 
+    class Frame;
     class String;
     typedef int ExceptionCode;
 
     class Storage : public RefCounted<Storage> {
     public:
+        static PassRefPtr<Storage> create(Frame*, PassRefPtr<OriginStorage>);
         
         unsigned length() const;
         String key(unsigned index, ExceptionCode&) const;
         String getItem(const String&) const;
         void setItem(const String& key, const String& value, ExceptionCode&);
         void removeItem(const String&);
+        
+        bool contains(const String& key) const;
+
+        void disconnectFrame() { m_frame = 0; }
+
+    private:
+        Storage(Frame*, PassRefPtr<OriginStorage>);
+            
+        Frame* m_frame;
+        RefPtr<OriginStorage> m_originStorage;
     };
 
 } // namespace WebCore
 
-#endif // StorageEvent_h
+#endif // Storage_h
index b9d4bd9..b74bbf6 100644 (file)
 
 module storage {
 
-    interface Storage {
+    interface [
+        GenerateConstructor,
+        HasNameGetter,
+        CustomPutFunction
+    ] Storage {
         readonly attribute unsigned long length;
         DOMString key(in unsigned long index) 
             raises (DOMException);
index 60b5994..68e9332 100644 (file)
 #include "config.h"
 #include "StorageEvent.h"
 
-// FIXME: Code will go here
+#include "DOMWindow.h"
+
+namespace WebCore {
+
+StorageEvent::StorageEvent()
+{
+}
+
+StorageEvent::StorageEvent(const AtomicString& type, const String& key, const String& oldValue, const String& newValue, const String& uri, DOMWindow* source)
+    : Event(type, false, true)
+    , m_key(key)
+    , m_oldValue(oldValue)
+    , m_newValue(newValue)
+    , m_uri(uri)
+    , m_source(source)
+{
+}
+
+void StorageEvent::initStorageEvent(const AtomicString& type, bool canBubble, bool cancelable, const String& key, const String& oldValue, const String& newValue, const String& uri, DOMWindow* source)
+{
+    if (dispatched())
+        return;
+
+    initEvent(type, canBubble, cancelable);
+
+    m_key = key;
+    m_oldValue = oldValue;
+    m_newValue = newValue;
+    m_uri = uri;
+    m_source = source;
+}
+
+}
index adb85ed..aeef09f 100644 (file)
@@ -26,8 +26,6 @@
 #ifndef StorageEvent_h
 #define StorageEvent_h
 
-// FIXME: More code will go here
-
 #include "Event.h"
 #include <wtf/RefPtr.h>
 
@@ -36,15 +34,23 @@ namespace WebCore {
     class DOMWindow;
 
     class StorageEvent : public Event {
-    public:   
+    public:
+        StorageEvent();
+        StorageEvent(const AtomicString& type, const String& key, const String& oldValue, const String& newValue, const String& uri, DOMWindow* source);
+        
         const String& key() const { return m_key; }
         const String& oldValue() const { return m_oldValue; }
         const String& newValue() const { return m_newValue; }
         const String& uri() const { return m_uri; }
         DOMWindow* source() const { return m_source.get(); }
         
-        void initStorageEvent(const AtomicString& type, bool canBubble, bool cancelable, const String& key, const String& oldValue, const String& newValue, const String& uri, DOMWindow* source) { }
+        void initStorageEvent(const AtomicString& type, bool canBubble, bool cancelable, const String& key, const String& oldValue, const String& newValue, const String& uri, DOMWindow* source);
+        
+        // Needed once we support init<blank>EventNS
+        // void initStorageEventNS(in DOMString namespaceURI, in DOMString typeArg, in boolean canBubbleArg, in boolean cancelableArg, in DOMString keyArg, in DOMString oldValueArg, in DOMString newValueArg, in DOMString uriArg, in Window sourceArg);
 
+        virtual bool isStorageEvent() const { return true; }
+        
     private:    
         String m_key;
         String m_oldValue;
index 94a3482..3e3dc65 100644 (file)
 #include "config.h"
 #include "StorageMap.h"
 
-// FIXME: Code will go here
+namespace WebCore {
+
+PassRefPtr<StorageMap> StorageMap::create()
+{
+    return adoptRef(new StorageMap);
+}
+    
+StorageMap::StorageMap()
+    : m_iterator(m_map.end())
+    , m_iteratorIndex(UINT_MAX)
+{
+}
+
+PassRefPtr<StorageMap> StorageMap::copy()
+{
+    RefPtr<StorageMap> newMap = create();
+    newMap->m_map = m_map;
+    return newMap.release();
+}
+
+void StorageMap::invalidateIterator()
+{
+    m_iterator = m_map.end();
+    m_iteratorIndex = UINT_MAX;
+}
+
+void StorageMap::setIteratorToIndex(unsigned index) const
+{
+    // FIXME: Once we have bidirectional iterators for HashMap we can be more intelligent about this.
+    // The requested index will be closest to begin(), our current iterator, or end(), and we 
+    // can take the shortest route.
+    // Until that mechanism is available, we'll always increment our iterator from begin() or current.
+    
+    if (m_iteratorIndex == index)
+        return;
+    
+    if (index < m_iteratorIndex) {
+        m_iteratorIndex = 0;
+        m_iterator = m_map.begin();
+        ASSERT(m_iterator != m_map.end());
+    }
+    
+    while (m_iteratorIndex < index) {
+        ++m_iteratorIndex;
+        ++m_iterator;
+        ASSERT(m_iterator != m_map.end());
+    }
+}
+
+unsigned StorageMap::length() const
+{
+    return m_map.size();
+}
+
+bool StorageMap::key(unsigned index, String& key) const
+{
+    if (index >= length())
+        return false;
+    
+    setIteratorToIndex(index);
+    
+    key = m_iterator->first;
+    return true;
+}
+
+String StorageMap::getItem(const String& key) const
+{
+    return m_map.get(key);
+}
+
+PassRefPtr<StorageMap> StorageMap::setItem(const String& key, const String& value, String& oldValue)
+{
+    ASSERT(!value.isNull());
+    
+    // Implement copy-on-write semantics here.  We're guaranteed that the only refs of StorageMaps belong to Storage objects
+    // so if more than one Storage object refs this map, copy it before mutating it.
+    if (refCount() > 1) {
+        RefPtr<StorageMap> newStorageMap = copy();
+        newStorageMap->setItem(key, value, oldValue);
+        return newStorageMap.release();
+    }
+
+    pair<HashMap<String, String>::iterator, bool> addResult = m_map.add(key, value);
+
+    if (addResult.second) {
+        // If the add succeeded, the map has been mutated and the iterator needs to be invalidated
+        invalidateIterator();
+        
+        // Plus, there was no "oldValue" so null it out.
+        oldValue = String();
+    } else {
+        oldValue = addResult.first->second;
+        m_map.set(key, value);
+    }
+
+    return 0;
+}
+
+PassRefPtr<StorageMap> StorageMap::removeItem(const String& key, String& oldValue)
+{
+    // Implement copy-on-write semantics here.  We're guaranteed that the only refs of StorageMaps belong to Storage objects
+    // so if more than one Storage object refs this map, copy it before mutating it.
+    if (refCount() > 1) {
+        RefPtr<StorageMap> newStorage = copy();
+        newStorage->removeItem(key, oldValue);
+        return newStorage.release();
+    }
+
+    oldValue = m_map.take(key);
+    if (!oldValue.isNull())
+        invalidateIterator();
+
+    return 0;
+}
+
+bool StorageMap::contains(const String& key) const
+{
+    return m_map.contains(key);
+}
+
+}
index 5126ab6..5cf2834 100644 (file)
 #ifndef StorageMap_h
 #define StorageMap_h
 
-// FIXME: Code will go here
+#include "PlatformString.h"
+#include "StringHash.h"
 
-#endif // StorageEvent_h
+#include <wtf/HashMap.h>
+#include <wtf/PassRefPtr.h>
+#include <wtf/RefCounted.h>
+
+namespace WebCore {
+
+    class StorageMap : public RefCounted<StorageMap> {
+    public:
+        static PassRefPtr<StorageMap> create();
+
+        unsigned length() const;
+        bool key(unsigned index, String& key) const;
+        String getItem(const String&) const;
+        PassRefPtr<StorageMap> setItem(const String& key, const String& value, String& oldValue);
+        PassRefPtr<StorageMap> removeItem(const String&, String& oldValue);
+
+        bool contains(const String& key) const;
+
+    private:
+        StorageMap();
+        PassRefPtr<StorageMap> copy();
+        void invalidateIterator();
+        void setIteratorToIndex(unsigned) const;
+
+        mutable HashMap<String, String> m_map;
+        mutable HashMap<String, String>::iterator m_iterator;
+        mutable unsigned m_iteratorIndex;
+    };
+
+} // namespace WebCore
+
+#endif // StorageMap_h