/* -*- 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/. */

#include "LocalStorage.h"
#include "LocalStorageCache.h"
#include "LocalStorageManager.h"
#include "StorageUtils.h"

#include "nsIObserverService.h"
#include "nsIScriptSecurityManager.h"
#include "nsIPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsICookiePermission.h"

#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/StorageBinding.h"
#include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/Services.h"
#include "mozilla/Preferences.h"
#include "mozilla/EnumSet.h"
#include "nsThreadUtils.h"
#include "nsContentUtils.h"
#include "nsServiceManagerUtils.h"

namespace mozilla {

using namespace ipc;

namespace dom {

NS_IMPL_CYCLE_COLLECTION_INHERITED(LocalStorage, Storage, mManager);

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LocalStorage)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END_INHERITING(Storage)

NS_IMPL_ADDREF_INHERITED(LocalStorage, Storage)
NS_IMPL_RELEASE_INHERITED(LocalStorage, Storage)

LocalStorage::LocalStorage(nsPIDOMWindowInner* aWindow,
                           LocalStorageManager* aManager,
                           LocalStorageCache* aCache,
                           const nsAString& aDocumentURI,
                           nsIPrincipal* aPrincipal,
                           bool aIsPrivate)
  : Storage(aWindow, aPrincipal)
  , mManager(aManager)
  , mCache(aCache)
  , mDocumentURI(aDocumentURI)
  , mIsPrivate(aIsPrivate)
{
  mCache->Preload();
}

LocalStorage::~LocalStorage()
{
}

int64_t
LocalStorage::GetOriginQuotaUsage() const
{
  return mCache->GetOriginQuotaUsage(this);
}

uint32_t
LocalStorage::GetLength(nsIPrincipal& aSubjectPrincipal,
                        ErrorResult& aRv)
{
  if (!CanUseStorage(aSubjectPrincipal)) {
    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    return 0;
  }

  uint32_t length;
  aRv = mCache->GetLength(this, &length);
  return length;
}

void
LocalStorage::Key(uint32_t aIndex, nsAString& aResult,
                  nsIPrincipal& aSubjectPrincipal,
                  ErrorResult& aRv)
{
  if (!CanUseStorage(aSubjectPrincipal)) {
    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    return;
  }

  aRv = mCache->GetKey(this, aIndex, aResult);
}

void
LocalStorage::GetItem(const nsAString& aKey, nsAString& aResult,
                      nsIPrincipal& aSubjectPrincipal,
                      ErrorResult& aRv)
{
  if (!CanUseStorage(aSubjectPrincipal)) {
    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    return;
  }

  aRv = mCache->GetItem(this, aKey, aResult);
}

void
LocalStorage::SetItem(const nsAString& aKey, const nsAString& aData,
                      nsIPrincipal& aSubjectPrincipal,
                      ErrorResult& aRv)
{
  if (!CanUseStorage(aSubjectPrincipal)) {
    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    return;
  }

  nsString data;
  bool ok = data.Assign(aData, fallible);
  if (!ok) {
    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    return;
  }

  nsString old;
  aRv = mCache->SetItem(this, aKey, data, old);
  if (aRv.Failed()) {
    return;
  }

  if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
    BroadcastChangeNotification(aKey, old, aData);
  }
}

void
LocalStorage::RemoveItem(const nsAString& aKey, nsIPrincipal& aSubjectPrincipal,
                         ErrorResult& aRv)
{
  if (!CanUseStorage(aSubjectPrincipal)) {
    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    return;
  }

  nsAutoString old;
  aRv = mCache->RemoveItem(this, aKey, old);
  if (aRv.Failed()) {
    return;
  }

  if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
    BroadcastChangeNotification(aKey, old, VoidString());
  }
}

void
LocalStorage::Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
{
  if (!CanUseStorage(aSubjectPrincipal)) {
    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    return;
  }

  aRv = mCache->Clear(this);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
    BroadcastChangeNotification(VoidString(), VoidString(), VoidString());
  }
}

void
LocalStorage::BroadcastChangeNotification(const nsAString& aKey,
                                          const nsAString& aOldValue,
                                          const nsAString& aNewValue)
{
  if (Principal()) {
    // We want to send a message to the parent in order to broadcast the
    // StorageEvent correctly to any child process.

    PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
    MOZ_ASSERT(actor);

    PrincipalInfo principalInfo;
    nsresult rv = PrincipalToPrincipalInfo(Principal(), &principalInfo);
    if (!NS_WARN_IF(NS_FAILED(rv))) {
      Unused << NS_WARN_IF(!actor->SendBroadcastLocalStorageChange(
        mDocumentURI, nsString(aKey), nsString(aOldValue), nsString(aNewValue),
        principalInfo, mIsPrivate));
    }
  }

  DispatchStorageEvent(mDocumentURI, aKey, aOldValue, aNewValue,
                       Principal(), mIsPrivate, this, false);
}

/* static */ void
LocalStorage::DispatchStorageEvent(const nsAString& aDocumentURI,
                                   const nsAString& aKey,
                                   const nsAString& aOldValue,
                                   const nsAString& aNewValue,
                                   nsIPrincipal* aPrincipal,
                                   bool aIsPrivate,
                                   Storage* aStorage,
                                   bool aImmediateDispatch)
{
  NotifyChange(aStorage, aPrincipal, aKey, aOldValue, aNewValue,
               u"localStorage", aDocumentURI, aIsPrivate, aImmediateDispatch);
}

void
LocalStorage::ApplyEvent(StorageEvent* aStorageEvent)
{
  MOZ_ASSERT(aStorageEvent);

  nsAutoString key;
  nsAutoString old;
  nsAutoString value;

  aStorageEvent->GetKey(key);
  aStorageEvent->GetNewValue(value);

  // No key means clearing the full storage.
  if (key.IsVoid()) {
    MOZ_ASSERT(value.IsVoid());
    mCache->Clear(this, LocalStorageCache::E10sPropagated);
    return;
  }

  // No new value means removing the key.
  if (value.IsVoid()) {
    mCache->RemoveItem(this, key, old, LocalStorageCache::E10sPropagated);
    return;
  }

  // Otherwise, we set the new value.
  mCache->SetItem(this, key, value, old, LocalStorageCache::E10sPropagated);
}

bool
LocalStorage::PrincipalEquals(nsIPrincipal* aPrincipal)
{
  return StorageUtils::PrincipalsEqual(mPrincipal, aPrincipal);
}

void
LocalStorage::GetSupportedNames(nsTArray<nsString>& aKeys)
{
  if (!CanUseStorage(*nsContentUtils::SubjectPrincipal())) {
    // return just an empty array
    aKeys.Clear();
    return;
  }

  mCache->GetKeys(this, aKeys);
}

bool
LocalStorage::IsForkOf(const Storage* aOther) const
{
  MOZ_ASSERT(aOther);
  if (aOther->Type() != eLocalStorage) {
    return false;
  }

  return mCache == static_cast<const LocalStorage*>(aOther)->mCache;
}

} // namespace dom
} // namespace mozilla
