<div><font class="Apple-style-span" face="arial, helvetica, sans-serif">I&#39;ve been researching, prototyping, and generally thinking about </font><a href="https://bugs.webkit.org/show_bug.cgi?id=25376" style="color: rgb(85, 26, 139); "><font class="Apple-style-span" face="arial, helvetica, sans-serif">https://bugs.webkit.org/show_bug.cgi?id=25376</font></a><font class="Apple-style-span" face="arial, helvetica, sans-serif"> for a while now.  I think I now know what needs to be done and the least painful way to get there.  I&#39;ve written up a design doc which is available here: </font><font class="Apple-style-span" face="arial, helvetica, sans-serif"><a href="http://docs.google.com/Doc?id=dhs4g97m_8cwths74m">http://docs.google.com/Doc?id=dhs4g97m_8cwths74m</a>  </font></div>
<div><font class="Apple-style-span" face="arial, helvetica, sans-serif"><br></font></div><div><font class="Apple-style-span" face="arial, helvetica, sans-serif">If you&#39;d like write permissions to it so you can add comments inline (via Ctrl-M), shoot me an email.  If you&#39;d rather reply inline via email, feel free to do that as well.</font></div>
<div><br></div><div>J</div><div><br></div><div>===================================</div><div><br></div><div><span class="Apple-style-span" style="font-family: &#39;Times New Roman&#39;; font-size: 16px; "><div style="margin-top: 6px; margin-right: 6px; margin-bottom: 6px; margin-left: 6px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Verdana; font-size: 10pt; background-color: rgb(255, 255, 255); min-height: 1100px; counter-reset: __goog_page__ 0; line-height: normal; ">
<h1 style="font-size: 18pt; "><font size="5">WebCore DOM Storage Refactoring Design Doc</font></h1><h2 style="font-size: 14pt; "><font size="4">Overview and The Need:</font></h2><div style="margin-top: 0px; margin-bottom: 0px; ">
See <a href="https://bugs.webkit.org/show_bug.cgi?id=25376" style="color: rgb(85, 26, 139); ">https://bugs.webkit.org/show_bug.cgi?id=25376</a> for the related bug.</div><div style="margin-top: 0px; margin-bottom: 0px; ">
<br></div><div style="margin-top: 0px; margin-bottom: 0px; ">The current design of DOM Storage (i.e. window.LocalStorage and window.SessionStorage) within WebCore is fairly incompatible with multi-process browsers like Chromium.  This can be fixed with a clean frontend/backend split within DOM Storage, allowing multi-process browsers to implement a proxy layer between the two, and having all the frontends share one backend.  The design should not assume that pages within the same origin are in the same process, that a cloned (see the SessionStorage spec) top level browsing context will remain in the same process, or that all children of a top level browsing context are in the same process.</div>
<div style="margin-top: 0px; margin-bottom: 0px; "><br></div><div style="margin-top: 0px; margin-bottom: 0px; ">Note that the current DOM Storage implementation is such that memory is rarely ever reclaimed (currently only from SessionStorage and only when tabs are closed).  The refactored backend should be designed so it&#39;s practical to reclaim resources when tabs close, child processes crash, users navigate away from pages, etc.  That said, actually reclaiming resources is considered &quot;future work&quot;.</div>
<div style="margin-top: 0px; margin-bottom: 0px; "><br></div><h2 style="font-size: 14pt; "><font size="4">High level plan:</font></h2><div style="margin-top: 0px; margin-bottom: 0px; ">There are 2 main classes that will be added to WebCore: StorageBackend and StorageEventManager.  StorageBackend will be a singleton that can be replaced with a proxy class.  The StorageEventManager is instantiated in a way that can easily be overridden by a StorageBackend proxy class.  Currently, the LocalStorage and SessionStorage classes keep track of StorageAreas and own the syncing threads/queues.  This functionality will be moved into the StorageBackend and the StorageSyncManager (which is owned by the StorageBackend).  The StorageArea classes&#39; event dispatching will be moved into the StorageEventManger class since the events may originate from another process via a proxy.</div>
<div style="margin-top: 0px; margin-bottom: 0px; "><br></div><div style="margin-top: 0px; margin-bottom: 0px; ">Since the Local and SessionStorage code is going to be more and more similar as time goes on, some of the classes (like LocalStorageArea and SessionStorageArea) will probably be combined and the behavior of the class will be determined explicitly rather than via polymorphism.  For example, the StorageArea will have a flag that says whether or not the in memory map is backed by a database.</div>
<div style="margin-top: 0px; margin-bottom: 0px; "><br></div><div style="margin-top: 0px; margin-bottom: 0px; ">The actual work will be split into as many patches as possible so the work is easy to verify as correct.  The performance of Local/SessionStorage should not be significantly affected at any point.  For example, there are no new lookup tables required except within Proxy classes.</div>
<div style="margin-top: 0px; margin-bottom: 0px; "><br></div><h3 style="font-size: 12pt; "><font size="3">Stage 1:</font></h3><div style="margin-top: 0px; margin-bottom: 0px; ">Move LocalStorage and SessionStorage logic into Backend and EventManager.</div>
<div style="margin-top: 0px; margin-bottom: 0px; "><br></div><h3 style="font-size: 12pt; "><font size="3">Stage 2:</font></h3><div style="margin-top: 0px; margin-bottom: 0px; ">Create a StorageSyncManager class that abstracts all the Synchronization work.  Combine LocalStorageArea and SessionStorageArea.  Rename LocalStorageThread/Task StorageSyncThread/Task.</div>
<div style="margin-top: 0px; margin-bottom: 0px; "><br></div><h3 style="font-size: 12pt; "><font size="3">Stage 3:</font></h3><div style="margin-top: 0px; margin-bottom: 0px; ">Add hooks for multi-process setups.</div><div style="margin-top: 0px; margin-bottom: 0px; ">
<br></div><div style="margin-top: 0px; margin-bottom: 0px; "><br></div><h2 style="font-size: 14pt; "><font size="4">Traces through the new design:</font></h2><div style="margin-top: 0px; margin-bottom: 0px; ">To help make things clear, here&#39;s what would happen in a single-process environment for a page that simply does the following: <font class="Apple-style-span" face="&#39;Courier New&#39;">window.localStorage.setItem(&#39;key&#39;, &#39;value&#39;)</font></div>
<div style="margin-top: 0px; margin-bottom: 0px; "><br></div><div style="margin-top: 0px; margin-bottom: 0px; "><i>Javascript bindings convert window.localStorage to DOMWindow.localStorage()</i></div><div style="margin-top: 0px; margin-bottom: 0px; ">
<br></div><div style="margin-top: 0px; margin-bottom: 0px; ">DOMWindow.localStorage()</div><div style="margin-top: 0px; margin-bottom: 0px; ">  <i>// StorageBackend::backend() is a singleton that returns a proxy for multi-process setups.</i></div>
<div style="margin-top: 0px; margin-bottom: 0px; ">  storage_area = StorageBackend::backend()-&gt;createLocalStorage(page_group, security_origin)</div><div style="margin-top: 0px; margin-bottom: 0px; ">  storage = Storage::create(frame, stroage_area)  <i>// normal stuff + registers the storage area with the event manager</i></div>
<div style="margin-top: 0px; margin-bottom: 0px; "></div><div style="margin-top: 0px; margin-bottom: 0px; "><br></div><div style="margin-top: 0px; margin-bottom: 0px; "><i>// In a multi-process environment, parts of this are on the backend and parts are on the frontend</i></div>
<div style="margin-top: 0px; margin-bottom: 0px; ">StorageBackend.createLocalStorage(page_group, security_origin)</div><div style="margin-top: 0px; margin-bottom: 0px; ">  id = createUniqueIdFromPageGroupAndSecurityOrigin(page_group, security)</div>
<div style="margin-top: 0px; margin-bottom: 0px; ">  if storageAreaMap.contains(id):</div><div style="margin-top: 0px; margin-bottom: 0px; ">    storageArea = storageAreaMap.get(id)</div><div style="margin-top: 0px; margin-bottom: 0px; ">
  else</div><div style="margin-top: 0px; margin-bottom: 0px; ">    storage_area = StorageArea::createLocalStorageArea(id, eventDispatcher, storageSyncManager)  <i>// constructor just initializes instance variables</i></div>
<div style="margin-top: 0px; margin-bottom: 0px; ">    storageSyncManager.scheduleImport(storage_area)  <i>// imports are scheduled before writes to disk</i></div><div style="margin-top: 0px; margin-bottom: 0px; ">    storageAreaMap.set(id, storage_area)</div>
<div style="margin-top: 0px; margin-bottom: 0px; ">  return storage_area.release()</div><div style="margin-top: 0px; margin-bottom: 0px; "><br></div><div style="margin-top: 0px; margin-bottom: 0px; "><br></div><div style="margin-top: 0px; margin-bottom: 0px; ">
<i>Javascript bindings call setItem on the returned object</i></div><div style="margin-top: 0px; margin-bottom: 0px; "><br></div><div style="margin-top: 0px; margin-bottom: 0px; ">Storage.setItem(key, value, exception_code_out)</div>
<div style="margin-top: 0px; margin-bottom: 0px; ">  return m_storageArea.setItem(m_frame, key, value, exception_code_out)</div><div style="margin-top: 0px; margin-bottom: 0px; "><br></div><div style="margin-top: 0px; margin-bottom: 0px; ">
<i>// In a multi-process environment, DONE ON THE BACKEND</i></div><div style="margin-top: 0px; margin-bottom: 0px; ">StorageArea.setItem(frame, key, value, exception_code_out)</div><div style="margin-top: 0px; margin-bottom: 0px; ">
  <i>// Abstracting out the manager allows implementations like Chromium freedom to use its existing threads/queues</i></div><div style="margin-top: 0px; margin-bottom: 0px; ">  storageSyncManager.blockUntilImportComplete()  </div>
<div style="margin-top: 0px; margin-bottom: 0px; ">  storageMap.set(key, value)</div><div style="margin-top: 0px; margin-bottom: 0px; ">  dirty = true</div><div style="margin-top: 0px; margin-bottom: 0px; ">  setOfKeysToSync.add(key)</div>
<div style="margin-top: 0px; margin-bottom: 0px; ">  storageSyncManager.scheduleToWrite(this)</div><div style="margin-top: 0px; margin-bottom: 0px; ">  eventDispatcher.event(type == LocalStorageType, key, old_value, new_value, url, frame)</div>
<div style="margin-top: 0px; margin-bottom: 0px; "><br></div><div style="margin-top: 0px; margin-bottom: 0px; "><i>// IMPORTANT: for implementations that don&#39;t implement window proxies per the spec, frame will be null...this is why storage instances must register themselves with the event dispatcher</i></div>
<div style="margin-top: 0px; margin-bottom: 0px; ">EventDispatcher::event(broadcast, key, old_value, new_value, url, frame)</div><div style="margin-top: 0px; margin-bottom: 0px; ">  for storage in registeredStorageInstances:</div>
<div style="margin-top: 0px; margin-bottom: 0px; ">    page = storage.getFrame().page()</div><div style="margin-top: 0px; margin-bottom: 0px; ">    // do the existing event dispatch stuff</div><div style="margin-top: 0px; margin-bottom: 0px; ">
    // an if statement based on the storage type decides if we&#39;re dispatching to all pages in this page&#39;s page group or not</div><div style="margin-top: 0px; margin-bottom: 0px; "><br></div><h3 style="font-size: 12pt; ">
<font size="3">Changes for multi-process environments:</font></h3><div style="margin-top: 0px; margin-bottom: 0px; ">There are comments in the above for the 2 bits of logic that&#39;d happen in the Backend.  Basically anything having to do with the actual storage data and syncing/importing happens in the backend process.  Anything having to do with event dispatching happens in the frontend process.  The StorageBackendProxy will keep track of frontend process objects (like StorageAreaProxies and the StorageEventManager) and will handle routing across the IPC layers.  The backend will maintain a StorageEventManager for each origin/groupName combination and will route events sent to the frontend process to the proper manager.</div>
<div style="margin-top: 0px; margin-bottom: 0px; "><br></div><h2 style="font-size: 14pt; "><font size="4">Future Work:</font></h2><div style="margin-top: 0px; margin-bottom: 0px; ">Quota support</div><div style="margin-top: 0px; margin-bottom: 0px; ">
<span style="font-family: -webkit-sans-serif; "><span style="background-color: rgb(255, 255, 255); "><a id="ik-3" href="http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-DocumentEvent-createEvent" title="DocumentEvent.createEvent">DocumentEvent.createEvent</a> </span>support</span></div>
<div style="margin-top: 0px; margin-bottom: 0px; ">Better resource reclamation:</div><div style="margin-top: 0px; margin-bottom: 0px; ">  Session storage written to disk when unused for a while (for example, early on in the history for long lived tabs)</div>
<div style="margin-top: 0px; margin-bottom: 0px; ">  evicting localStorage when low on memory and page is not active</div><div style="margin-top: 0px; margin-bottom: 0px; ">  etc</div><div style="margin-top: 0px; margin-bottom: 0px; ">
proxy classes doing caching</div><div><br></div></div></span></div>