Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_localstorage_LocalStorageCommon_h
#define mozilla_dom_localstorage_LocalStorageCommon_h
#include <cstdint>
#include "ErrorList.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "nsLiteralString.h"
#include "nsStringFwd.h"
/*
* Local storage
* ~~~~~~~~~~~~~
*
* Implementation overview
* ~~~~~~~~~~~~~~~~~~~~~~~
*
* The implementation is based on a per principal/origin cache (datastore)
* living in the main process and synchronous calls initiated from content
* processes.
* The IPC communication is managed by database actors which link to the
* datastore.
* The synchronous blocking of the main thread is done by using a special
* technique or by using standard synchronous IPC calls.
*
* General architecture
* ~~~~~~~~~~~~~~~~~~~~
* The current browser architecture consists of one main process and multiple
* content processes (there are other processes but for simplicity's sake, they
* are not mentioned here). The processes use the IPC communication to talk to
* each other. Local storage implementation uses the client-server model, so
* the main process manages all the data and content processes then request
* particular data from the main process. The main process is also called the
* parent or the parent side, the content process is then called the child or
* the child side.
*
* Datastores
* ~~~~~~~~~~
*
* A datastore provides a convenient way to access data for given origin. The
* data is always preloaded into memory and indexed using a hash table. This
* enables very fast access to particular stored items. There can be only one
* datastore per origin and exists solely on the parent side. It is represented
* by the "Datastore" class. A datastore instance is a ref counted object and
* lives on the PBackground thread, it is kept alive by database objects. When
* the last database object for given origin is destroyed, the associated
* datastore object is destroyed too.
*
* Databases
* ~~~~~~~~~
*
* A database allows direct access to a datastore from a content process. There
* can be multiple databases for the same origin, but they all share the same
* datastore.
* Databases use the PBackgroundLSDatabase IPDL protocol for IPC communication.
* Given the nature of local storage, most of PBackgroundLSDatabase messages
* are synchronous.
*
* On the parent side, the database is represented by the "Database" class that
* is a parent actor as well (implements the "PBackgroundLSDatabaseParent"
* interface). A database instance is a ref counted object and lives on the
* PBackground thread.
* All live database actors are tracked in an array.
*
* On the child side, the database is represented by the "LSDatabase" class
* that provides indirect access to a child actor. An LSDatabase instance is a
* ref counted object and lives on the main thread.
* The actual child actor is represented by the "LSDatabaseChild" class that
* implements the "PBackgroundLSDatabaseChild" interface. An "LSDatabaseChild"
* instance is not ref counted and lives on the main thread too.
*
* Synchronous blocking
* ~~~~~~~~~~~~~~~~~~~~
*
* Local storage is synchronous in nature which means the execution can't move
* forward until there's a reply for given method call.
* Since we have to use IPC anyway, we could just always use synchronous IPC
* messages for all local storage method calls. Well, there's a problem with
* that approach.
* If the main process needs to do some off PBackground thread stuff like
* getting info from principals on the main thread or some asynchronous stuff
* like directory locking before sending a reply to a synchronous message, then
* we would have to block the thread or spin the event loop which is usually a
* bad idea, especially in the main process.
* Instead, we can use a special thread in the content process called
* RemoteLazyInputStream thread for communication with the main process using
* asynchronous messages and synchronously block the main thread until the DOM
* File thread is done (the main thread blocking is a bit more complicated, see
* the comment in RequestHelper::StartAndReturnResponse for more details).
* Anyway, the extra hop to the RemoteLazyInputStream thread brings another
* overhead and latency. The final solution is to use a combination of the
* special thread for complex stuff like datastore preparation and synchronous
* IPC messages sent directly from the main thread for database access when data
* is already loaded from disk into memory.
*
* Requests
* ~~~~~~~~
*
* Requests are used to handle asynchronous high level datastore operations
* which are initiated in a content process and then processed in the parent
* process (for example, preparation of a datastore).
* Requests use the "PBackgroundLSRequest" IPDL protocol for IPC communication.
*
* On the parent side, the request is represented by the "LSRequestBase" class
* that is a parent actor as well (implements the "PBackgroundLSRequestParent"
* interface). It's an abstract class (contains pure virtual functions) so it
* can't be used to create instances.
* It also inherits from the "DatastoreOperationBase" class which is a generic
* base class for all datastore operations. The "DatastoreOperationsBase" class
* inherits from the "Runnable" class, so derived class instances are ref
* counted, can be dispatched to multiple threads and thus they are used on
* multiple threads. However, derived class instances can be created on the
* PBackground thread only.
*
* On the child side, the request is represented by the "RequestHelper" class
* that covers all the complexity needed to start a new request, handle
* responses and do safe main thread blocking at the same time.
* It inherits from the "Runnable" class, so instances are ref counted and
* they are internally used on multiple threads (specifically on the main
* thread and on the RemoteLazyInputStream thread). Anyway, users should create
* and use instances of this class only on the main thread.
* The actual child actor is represented by the "LSRequestChild" class that
* implements the "PBackgroundLSRequestChild" interface. An "LSRequestChild"
* instance is not ref counted and lives on the RemoteLazyInputStream thread.
* Request responses are passed using the "LSRequestChildCallback" interface.
*
* Preparation of a datastore
* ~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* The datastore preparation is needed to make sure a datastore is fully loaded
* into memory. Every datastore preparation produces a unique id (even if the
* datastore for given origin already exists).
* On the parent side, the preparation is handled by the "PrepareDatastoreOp"
* class which inherits from the "LSRequestBase" class. The preparation process
* on the parent side is quite complicated, it happens sequentially on multiple
* threads and is managed by a state machine.
* On the child side, the preparation is done in the LSObject::EnsureDatabase
* method using the "RequestHelper" class. The method starts a new preparation
* request and obtains a unique id produced by the parent (or an error code if
* the requested failed to complete).
*
* Linking databases to a datastore
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* A datastore exists only on the parent side, but it can be accessed from the
* content via database actors. Database actors are initiated on the child side
* and they need to be linked to a datastore on the parent side via an id. The
* datastore preparation process gives us the required id.
* The linking is initiated on the child side in the LSObject::EnsureDatabase
* method by calling SendPBackgroundLSDatabaseConstructor and finished in
* RecvPBackgroundLSDatabaseConstructor on the parent side.
*
* Actor migration
* ~~~~~~~~~~~~~~~
*
* In theory, the datastore preparation request could return a database actor
* directly (instead of returning an id intended for database linking to a
* datastore). However, as it was explained above, the preparation must be done
* on the RemoteLazyInputStream thread and database objects are used on the main
* thread. The returned actor would have to be migrated from the
* RemoteLazyInputStream thread to the main thread and that's something which
* our IPDL doesn't support yet.
*
* Exposing local storage
* ~~~~~~~~~~~~~~~~~~~~~~
*
* The implementation is exposed to the DOM via window.localStorage attribute.
* Local storage's sibling, session storage shares the same WebIDL interface
* for exposing it to web content, therefore there's an abstract class called
* "Storage" that handles some of the common DOM bindings stuff. Local storage
* specific functionality is defined in the "LSObject" derived class.
* The "LSObject" class is also a starting point for the datastore preparation
* and database linking.
*
* Local storage manager
* ~~~~~~~~~~~~~~~~~~~~~
*
* The local storage manager exposes some of the features that need to be
* available only in the chrome code or tests. The manager is represented by
* the "LocalStorageManager2" class that implements the "nsIDOMStorageManager"
* interface.
*/
namespace mozilla {
class LogModule;
namespace ipc {
class PrincipalInfo;
} // namespace ipc
namespace dom {
extern const char16_t* kLocalStorageType;
/**
* Convenience data-structure to make it easier to track whether a value has
* changed and what its previous value was for notification purposes. Instances
* are created on the stack by LSObject and passed to LSDatabase which in turn
* passes them onto LSSnapshot for final updating/population. LSObject then
* generates an event, if appropriate.
*/
class MOZ_STACK_CLASS LSNotifyInfo {
bool mChanged;
nsString mOldValue;
public:
LSNotifyInfo() : mChanged(false) {}
bool changed() const { return mChanged; }
bool& changed() { return mChanged; }
const nsString& oldValue() const { return mOldValue; }
nsString& oldValue() { return mOldValue; }
};
/**
* A check of LSNG being enabled, the value is latched once initialized so
* changing the preference during runtime has no effect. May be called on any
* thread in the parent process, but you should call
* CachedNextGenLocalStorageEnabled if you know that NextGenLocalStorageEnabled
* was already called because it is faster. May be called on any thread in
* content processes, but you should call CachedNextGenLocalStorageEnabled
* directly if you know you are in a content process because it is slightly
* faster.
*/
bool NextGenLocalStorageEnabled();
/**
* Called by ContentChild during content process initialization to initialize
* the global variable in the content process with the latched value in the
* parent process."
*/
void RecvInitNextGenLocalStorageEnabled(const bool aEnabled);
/**
* Cached any-thread version of NextGenLocalStorageEnabled().
*/
bool CachedNextGenLocalStorageEnabled();
/**
* Returns a success value containing a pair of origin attribute suffix and
* origin key.
*/
Result<std::pair<nsCString, nsCString>, nsresult> GenerateOriginKey2(
const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
LogModule* GetLocalStorageLogger();
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_localstorage_LocalStorageCommon_h