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

// Local Includes
#include "nsWebBrowser.h"

// Helper Classes
#include "nsGfxCIID.h"
#include "nsWidgetsCID.h"

#include "gfxUtils.h"
#include "mozilla/gfx/2D.h"

// Interfaces Needed
#include "nsReadableUtils.h"
#include "nsIComponentManager.h"
#include "nsIDOMDocument.h"
#include "nsIDOMWindow.h"
#include "nsIDOMElement.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIWebBrowserChrome.h"
#include "nsPIDOMWindow.h"
#include "nsIWebProgress.h"
#include "nsIWebProgressListener.h"
#include "nsIWebBrowserFocus.h"
#include "nsIWebBrowserStream.h"
#include "nsIPresShell.h"
#include "nsIURIContentListener.h"
#include "nsISHistoryListener.h"
#include "nsIURI.h"
#include "nsIWebBrowserPersist.h"
#include "nsCWebBrowserPersist.h"
#include "nsIServiceManager.h"
#include "nsFocusManager.h"
#include "Layers.h"
#include "gfxContext.h"
#include "nsILoadContext.h"
#include "nsDocShell.h"

// for painting the background window
#include "mozilla/LookAndFeel.h"

// Printing Includes
#ifdef NS_PRINTING
#include "nsIWebBrowserPrint.h"
#include "nsIContentViewer.h"
#endif

// PSM2 includes
#include "nsISecureBrowserUI.h"
#include "nsXULAppAPI.h"

using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;

static NS_DEFINE_CID(kChildCID, NS_CHILD_CID);

nsWebBrowser::nsWebBrowser()
  : mInitInfo(new nsWebBrowserInitInfo())
  , mContentType(typeContentWrapper)
  , mActivating(false)
  , mShouldEnableHistory(true)
  , mIsActive(true)
  , mParentNativeWindow(nullptr)
  , mProgressListener(nullptr)
  , mWidgetListenerDelegate(this)
  , mBackgroundColor(0)
  , mPersistCurrentState(nsIWebBrowserPersist::PERSIST_STATE_READY)
  , mPersistResult(NS_OK)
  , mPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_NONE)
  , mParentWidget(nullptr)
{
  mWWatch = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
  NS_ASSERTION(mWWatch, "failed to get WindowWatcher");
}

nsWebBrowser::~nsWebBrowser()
{
  InternalDestroy();
}

NS_IMETHODIMP
nsWebBrowser::InternalDestroy()
{
  if (mInternalWidget) {
    mInternalWidget->SetWidgetListener(nullptr);
    mInternalWidget->Destroy();
    mInternalWidget = nullptr; // Force release here.
  }

  SetDocShell(nullptr);

  if (mDocShellTreeOwner) {
    mDocShellTreeOwner->WebBrowser(nullptr);
    mDocShellTreeOwner = nullptr;
  }

  mInitInfo = nullptr;

  mListenerArray = nullptr;

  return NS_OK;
}

NS_IMPL_ADDREF(nsWebBrowser)
NS_IMPL_RELEASE(nsWebBrowser)

NS_INTERFACE_MAP_BEGIN(nsWebBrowser)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowser)
  NS_INTERFACE_MAP_ENTRY(nsIWebBrowser)
  NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
  NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
  NS_INTERFACE_MAP_ENTRY(nsIScrollable)
  NS_INTERFACE_MAP_ENTRY(nsITextScroll)
  NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem)
  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
  NS_INTERFACE_MAP_ENTRY(nsIWebBrowserSetup)
  NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist)
  NS_INTERFACE_MAP_ENTRY(nsICancelable)
  NS_INTERFACE_MAP_ENTRY(nsIWebBrowserFocus)
  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
  NS_INTERFACE_MAP_ENTRY(nsIWebBrowserStream)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END

///*****************************************************************************
// nsWebBrowser::nsIInterfaceRequestor
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::GetInterface(const nsIID& aIID, void** aSink)
{
  NS_ENSURE_ARG_POINTER(aSink);

  if (NS_SUCCEEDED(QueryInterface(aIID, aSink))) {
    return NS_OK;
  }

  if (mDocShell) {
#ifdef NS_PRINTING
    if (aIID.Equals(NS_GET_IID(nsIWebBrowserPrint))) {
      nsCOMPtr<nsIContentViewer> viewer;
      mDocShell->GetContentViewer(getter_AddRefs(viewer));
      if (!viewer) {
        return NS_NOINTERFACE;
      }

      nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint(do_QueryInterface(viewer));
      nsIWebBrowserPrint* print = (nsIWebBrowserPrint*)webBrowserPrint.get();
      NS_ASSERTION(print, "This MUST support this interface!");
      NS_ADDREF(print);
      *aSink = print;
      return NS_OK;
    }
#endif
    return mDocShellAsReq->GetInterface(aIID, aSink);
  }

  return NS_NOINTERFACE;
}

//*****************************************************************************
// nsWebBrowser::nsIWebBrowser
//*****************************************************************************

// listeners that currently support registration through AddWebBrowserListener:
//  - nsIWebProgressListener
NS_IMETHODIMP
nsWebBrowser::AddWebBrowserListener(nsIWeakReference* aListener,
                                    const nsIID& aIID)
{
  NS_ENSURE_ARG_POINTER(aListener);

  nsresult rv = NS_OK;
  if (!mWebProgress) {
    // The window hasn't been created yet, so queue up the listener. They'll be
    // registered when the window gets created.
    if (!mListenerArray) {
      mListenerArray = new nsTArray<nsWebBrowserListenerState>();
    }

    nsWebBrowserListenerState* state = mListenerArray->AppendElement();
    state->mWeakPtr = aListener;
    state->mID = aIID;
  } else {
    nsCOMPtr<nsISupports> supports(do_QueryReferent(aListener));
    if (!supports) {
      return NS_ERROR_INVALID_ARG;
    }
    rv = BindListener(supports, aIID);
  }

  return rv;
}

NS_IMETHODIMP
nsWebBrowser::BindListener(nsISupports* aListener, const nsIID& aIID)
{
  NS_ENSURE_ARG_POINTER(aListener);
  NS_ASSERTION(mWebProgress,
               "this should only be called after we've retrieved a progress iface");
  nsresult rv = NS_OK;

  // register this listener for the specified interface id
  if (aIID.Equals(NS_GET_IID(nsIWebProgressListener))) {
    nsCOMPtr<nsIWebProgressListener> listener = do_QueryInterface(aListener, &rv);
    if (NS_FAILED(rv)) {
      return rv;
    }
    NS_ENSURE_STATE(mWebProgress);
    rv = mWebProgress->AddProgressListener(listener, nsIWebProgress::NOTIFY_ALL);
  } else if (aIID.Equals(NS_GET_IID(nsISHistoryListener))) {
    nsCOMPtr<nsISHistory> shistory(do_GetInterface(mDocShell, &rv));
    if (NS_FAILED(rv)) {
      return rv;
    }
    nsCOMPtr<nsISHistoryListener> listener(do_QueryInterface(aListener, &rv));
    if (NS_FAILED(rv)) {
      return rv;
    }
    rv = shistory->AddSHistoryListener(listener);
  }
  return rv;
}

NS_IMETHODIMP
nsWebBrowser::RemoveWebBrowserListener(nsIWeakReference* aListener,
                                       const nsIID& aIID)
{
  NS_ENSURE_ARG_POINTER(aListener);

  nsresult rv = NS_OK;
  if (!mWebProgress) {
    // if there's no-one to register the listener w/, and we don't have a queue
    // going, the the called is calling Remove before an Add which doesn't make
    // sense.
    if (!mListenerArray) {
      return NS_ERROR_FAILURE;
    }

    // iterate the array and remove the queued listener
    int32_t count = mListenerArray->Length();
    while (count > 0) {
      if (mListenerArray->ElementAt(count-1).Equals(aListener, aIID)) {
        mListenerArray->RemoveElementAt(count-1);
        break;
      }
      count--;
    }

    // if we've emptied the array, get rid of it.
    if (0 >= mListenerArray->Length()) {
      mListenerArray = nullptr;
    }

  } else {
    nsCOMPtr<nsISupports> supports(do_QueryReferent(aListener));
    if (!supports) {
      return NS_ERROR_INVALID_ARG;
    }
    rv = UnBindListener(supports, aIID);
  }

  return rv;
}

NS_IMETHODIMP
nsWebBrowser::UnBindListener(nsISupports* aListener, const nsIID& aIID)
{
  NS_ENSURE_ARG_POINTER(aListener);
  NS_ASSERTION(mWebProgress,
               "this should only be called after we've retrieved a progress iface");
  nsresult rv = NS_OK;

  // remove the listener for the specified interface id
  if (aIID.Equals(NS_GET_IID(nsIWebProgressListener))) {
    nsCOMPtr<nsIWebProgressListener> listener = do_QueryInterface(aListener, &rv);
    if (NS_FAILED(rv)) {
      return rv;
    }
    NS_ENSURE_STATE(mWebProgress);
    rv = mWebProgress->RemoveProgressListener(listener);
  } else if (aIID.Equals(NS_GET_IID(nsISHistoryListener))) {
    nsCOMPtr<nsISHistory> shistory(do_GetInterface(mDocShell, &rv));
    if (NS_FAILED(rv)) {
      return rv;
    }
    nsCOMPtr<nsISHistoryListener> listener(do_QueryInterface(aListener, &rv));
    if (NS_FAILED(rv)) {
      return rv;
    }
    rv = shistory->RemoveSHistoryListener(listener);
  }
  return rv;
}

NS_IMETHODIMP
nsWebBrowser::EnableGlobalHistory(bool aEnable)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShell->SetUseGlobalHistory(aEnable);
}

NS_IMETHODIMP
nsWebBrowser::GetContainerWindow(nsIWebBrowserChrome** aTopWindow)
{
  NS_ENSURE_ARG_POINTER(aTopWindow);

  nsCOMPtr<nsIWebBrowserChrome> top;
  if (mDocShellTreeOwner) {
    top = mDocShellTreeOwner->GetWebBrowserChrome();
  }

  top.forget(aTopWindow);

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetContainerWindow(nsIWebBrowserChrome* aTopWindow)
{
  NS_ENSURE_SUCCESS(EnsureDocShellTreeOwner(), NS_ERROR_FAILURE);
  return mDocShellTreeOwner->SetWebBrowserChrome(aTopWindow);
}

NS_IMETHODIMP
nsWebBrowser::GetParentURIContentListener(
    nsIURIContentListener** aParentContentListener)
{
  NS_ENSURE_ARG_POINTER(aParentContentListener);
  *aParentContentListener = nullptr;

  // get the interface from the docshell
  nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(mDocShell));

  if (listener) {
    return listener->GetParentContentListener(aParentContentListener);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetParentURIContentListener(
    nsIURIContentListener* aParentContentListener)
{
  // get the interface from the docshell
  nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(mDocShell));

  if (listener) {
    return listener->SetParentContentListener(aParentContentListener);
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsWebBrowser::GetContentDOMWindow(mozIDOMWindowProxy** aResult)
{
  if (!mDocShell) {
    return NS_ERROR_UNEXPECTED;
  }

  nsCOMPtr<nsPIDOMWindowOuter> retval = mDocShell->GetWindow();
  retval.forget(aResult);
  return *aResult ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsWebBrowser::GetIsActive(bool* aResult)
{
  *aResult = mIsActive;
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetIsActive(bool aIsActive)
{
  // Set our copy of the value
  mIsActive = aIsActive;

  // If we have a docshell, pass on the request
  if (mDocShell) {
    return mDocShell->SetIsActive(aIsActive);
  }
  return NS_OK;
}

void
nsWebBrowser::SetOriginAttributes(const DocShellOriginAttributes& aAttrs)
{
  mOriginAttributes = aAttrs;
}

//*****************************************************************************
// nsWebBrowser::nsIDocShellTreeItem
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::GetName(nsAString& aName)
{
  if (mDocShell) {
    mDocShell->GetName(aName);
  } else {
    aName = mInitInfo->name;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetName(const nsAString& aName)
{
  if (mDocShell) {
    return mDocShell->SetName(aName);
  } else {
    mInitInfo->name = aName;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::NameEquals(const nsAString& aName, bool* aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  if (mDocShell) {
    return mDocShell->NameEquals(aName, aResult);
  } else {
    *aResult = mInitInfo->name.Equals(aName);
  }

  return NS_OK;
}

/* virtual */ int32_t
nsWebBrowser::ItemType()
{
  return mContentType;
}

NS_IMETHODIMP
nsWebBrowser::GetItemType(int32_t* aItemType)
{
  NS_ENSURE_ARG_POINTER(aItemType);

  *aItemType = ItemType();
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetItemType(int32_t aItemType)
{
  NS_ENSURE_TRUE(
    aItemType == typeContentWrapper || aItemType == typeChromeWrapper,
    NS_ERROR_FAILURE);
  mContentType = aItemType;
  if (mDocShell) {
    mDocShell->SetItemType(mContentType == typeChromeWrapper ?
                             static_cast<int32_t>(typeChrome) :
                             static_cast<int32_t>(typeContent));
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetParent(nsIDocShellTreeItem** aParent)
{
  *aParent = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetSameTypeParent(nsIDocShellTreeItem** aParent)
{
  *aParent = nullptr;

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetRootTreeItem(nsIDocShellTreeItem** aRootTreeItem)
{
  NS_ENSURE_ARG_POINTER(aRootTreeItem);
  *aRootTreeItem = static_cast<nsIDocShellTreeItem*>(this);

  nsCOMPtr<nsIDocShellTreeItem> parent;
  NS_ENSURE_SUCCESS(GetParent(getter_AddRefs(parent)), NS_ERROR_FAILURE);
  while (parent) {
    *aRootTreeItem = parent;
    NS_ENSURE_SUCCESS((*aRootTreeItem)->GetParent(getter_AddRefs(parent)),
                      NS_ERROR_FAILURE);
  }
  NS_ADDREF(*aRootTreeItem);
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetSameTypeRootTreeItem(nsIDocShellTreeItem** aRootTreeItem)
{
  NS_ENSURE_ARG_POINTER(aRootTreeItem);
  *aRootTreeItem = static_cast<nsIDocShellTreeItem*>(this);

  nsCOMPtr<nsIDocShellTreeItem> parent;
  NS_ENSURE_SUCCESS(GetSameTypeParent(getter_AddRefs(parent)),
                    NS_ERROR_FAILURE);
  while (parent) {
    *aRootTreeItem = parent;
    NS_ENSURE_SUCCESS((*aRootTreeItem)->GetSameTypeParent(getter_AddRefs(parent)),
                      NS_ERROR_FAILURE);
  }
  NS_ADDREF(*aRootTreeItem);
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::FindItemWithName(const nsAString& aName,
                               nsISupports* aRequestor,
                               nsIDocShellTreeItem* aOriginalRequestor,
                               nsIDocShellTreeItem** aResult)
{
  NS_ENSURE_STATE(mDocShell);
  NS_ASSERTION(mDocShellTreeOwner,
               "This should always be set when in this situation");

  return mDocShell->FindItemWithName(
    aName, static_cast<nsIDocShellTreeOwner*>(mDocShellTreeOwner),
    aOriginalRequestor, aResult);
}

nsIDocument*
nsWebBrowser::GetDocument()
{
  return mDocShell ? mDocShell->GetDocument() : nullptr;
}

nsPIDOMWindowOuter*
nsWebBrowser::GetWindow()
{
  return mDocShell ? mDocShell->GetWindow() : nullptr;
}

NS_IMETHODIMP
nsWebBrowser::GetTreeOwner(nsIDocShellTreeOwner** aTreeOwner)
{
  NS_ENSURE_ARG_POINTER(aTreeOwner);
  *aTreeOwner = nullptr;
  if (mDocShellTreeOwner) {
    if (mDocShellTreeOwner->mTreeOwner) {
      *aTreeOwner = mDocShellTreeOwner->mTreeOwner;
    } else {
      *aTreeOwner = mDocShellTreeOwner;
    }
  }
  NS_IF_ADDREF(*aTreeOwner);
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner)
{
  NS_ENSURE_SUCCESS(EnsureDocShellTreeOwner(), NS_ERROR_FAILURE);
  return mDocShellTreeOwner->SetTreeOwner(aTreeOwner);
}

//*****************************************************************************
// nsWebBrowser::nsIDocShellTreeItem
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::GetChildCount(int32_t* aChildCount)
{
  NS_ENSURE_ARG_POINTER(aChildCount);
  *aChildCount = 0;
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::AddChild(nsIDocShellTreeItem* aChild)
{
  return NS_ERROR_UNEXPECTED;
}

NS_IMETHODIMP
nsWebBrowser::RemoveChild(nsIDocShellTreeItem* aChild)
{
  return NS_ERROR_UNEXPECTED;
}

NS_IMETHODIMP
nsWebBrowser::GetChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild)
{
  return NS_ERROR_UNEXPECTED;
}

NS_IMETHODIMP
nsWebBrowser::FindChildWithName(const nsAString& aName,
                                bool aRecurse,
                                bool aSameType,
                                nsIDocShellTreeItem* aRequestor,
                                nsIDocShellTreeItem* aOriginalRequestor,
                                nsIDocShellTreeItem** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  *aResult = nullptr;
  return NS_OK;
}

//*****************************************************************************
// nsWebBrowser::nsIWebNavigation
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::GetCanGoBack(bool* aCanGoBack)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->GetCanGoBack(aCanGoBack);
}

NS_IMETHODIMP
nsWebBrowser::GetCanGoForward(bool* aCanGoForward)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->GetCanGoForward(aCanGoForward);
}

NS_IMETHODIMP
nsWebBrowser::GoBack()
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->GoBack();
}

NS_IMETHODIMP
nsWebBrowser::GoForward()
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->GoForward();
}

NS_IMETHODIMP
nsWebBrowser::LoadURIWithOptions(const char16_t* aURI, uint32_t aLoadFlags,
                                 nsIURI* aReferringURI,
                                 uint32_t aReferrerPolicy,
                                 nsIInputStream* aPostDataStream,
                                 nsIInputStream* aExtraHeaderStream,
                                 nsIURI* aBaseURI,
                                 nsIPrincipal* aTriggeringPrincipal)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->LoadURIWithOptions(
    aURI, aLoadFlags, aReferringURI, aReferrerPolicy, aPostDataStream,
    aExtraHeaderStream, aBaseURI, aTriggeringPrincipal);
}

NS_IMETHODIMP
nsWebBrowser::SetOriginAttributesBeforeLoading(JS::Handle<JS::Value> aOriginAttributes)
{
  return mDocShellAsNav->SetOriginAttributesBeforeLoading(aOriginAttributes);
}

NS_IMETHODIMP
nsWebBrowser::LoadURI(const char16_t* aURI, uint32_t aLoadFlags,
                      nsIURI* aReferringURI,
                      nsIInputStream* aPostDataStream,
                      nsIInputStream* aExtraHeaderStream)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->LoadURI(
    aURI, aLoadFlags, aReferringURI, aPostDataStream, aExtraHeaderStream);
}

NS_IMETHODIMP
nsWebBrowser::Reload(uint32_t aReloadFlags)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->Reload(aReloadFlags);
}

NS_IMETHODIMP
nsWebBrowser::GotoIndex(int32_t aIndex)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->GotoIndex(aIndex);
}

NS_IMETHODIMP
nsWebBrowser::Stop(uint32_t aStopFlags)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->Stop(aStopFlags);
}

NS_IMETHODIMP
nsWebBrowser::GetCurrentURI(nsIURI** aURI)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->GetCurrentURI(aURI);
}

NS_IMETHODIMP
nsWebBrowser::GetReferringURI(nsIURI** aURI)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->GetReferringURI(aURI);
}

NS_IMETHODIMP
nsWebBrowser::SetSessionHistory(nsISHistory* aSessionHistory)
{
  if (mDocShell) {
    return mDocShellAsNav->SetSessionHistory(aSessionHistory);
  } else {
    mInitInfo->sessionHistory = aSessionHistory;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetSessionHistory(nsISHistory** aSessionHistory)
{
  NS_ENSURE_ARG_POINTER(aSessionHistory);
  if (mDocShell) {
    return mDocShellAsNav->GetSessionHistory(aSessionHistory);
  } else {
    *aSessionHistory = mInitInfo->sessionHistory;
  }

  NS_IF_ADDREF(*aSessionHistory);

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetDocument(nsIDOMDocument** aDocument)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsNav->GetDocument(aDocument);
}

//*****************************************************************************
// nsWebBrowser::nsIWebBrowserSetup
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::SetProperty(uint32_t aId, uint32_t aValue)
{
  nsresult rv = NS_OK;

  switch (aId) {
    case nsIWebBrowserSetup::SETUP_ALLOW_PLUGINS: {
      NS_ENSURE_STATE(mDocShell);
      NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
                      aValue == static_cast<uint32_t>(false)),
                     NS_ERROR_INVALID_ARG);
      mDocShell->SetAllowPlugins(!!aValue);
      break;
    }
    case nsIWebBrowserSetup::SETUP_ALLOW_JAVASCRIPT: {
      NS_ENSURE_STATE(mDocShell);
      NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
                      aValue == static_cast<uint32_t>(false)),
                     NS_ERROR_INVALID_ARG);
      mDocShell->SetAllowJavascript(!!aValue);
      break;
    }
    case nsIWebBrowserSetup::SETUP_ALLOW_META_REDIRECTS: {
      NS_ENSURE_STATE(mDocShell);
      NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
                      aValue == static_cast<uint32_t>(false)),
                     NS_ERROR_INVALID_ARG);
      mDocShell->SetAllowMetaRedirects(!!aValue);
      break;
    }
    case nsIWebBrowserSetup::SETUP_ALLOW_SUBFRAMES: {
      NS_ENSURE_STATE(mDocShell);
      NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
                      aValue == static_cast<uint32_t>(false)),
                     NS_ERROR_INVALID_ARG);
      mDocShell->SetAllowSubframes(!!aValue);
      break;
    }
    case nsIWebBrowserSetup::SETUP_ALLOW_IMAGES: {
      NS_ENSURE_STATE(mDocShell);
      NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
                      aValue == static_cast<uint32_t>(false)),
                     NS_ERROR_INVALID_ARG);
      mDocShell->SetAllowImages(!!aValue);
      break;
    }
    case nsIWebBrowserSetup::SETUP_ALLOW_DNS_PREFETCH: {
      NS_ENSURE_STATE(mDocShell);
      NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
                      aValue == static_cast<uint32_t>(false)),
                     NS_ERROR_INVALID_ARG);
      mDocShell->SetAllowDNSPrefetch(!!aValue);
      break;
    }
    case nsIWebBrowserSetup::SETUP_USE_GLOBAL_HISTORY: {
      NS_ENSURE_STATE(mDocShell);
      NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
                      aValue == static_cast<uint32_t>(false)),
                     NS_ERROR_INVALID_ARG);
      rv = EnableGlobalHistory(!!aValue);
      mShouldEnableHistory = aValue;
      break;
    }
    case nsIWebBrowserSetup::SETUP_FOCUS_DOC_BEFORE_CONTENT: {
      // obsolete
      break;
    }
    case nsIWebBrowserSetup::SETUP_IS_CHROME_WRAPPER: {
      NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
                      aValue == static_cast<uint32_t>(false)),
                     NS_ERROR_INVALID_ARG);
      SetItemType(aValue ? static_cast<int32_t>(typeChromeWrapper) :
                           static_cast<int32_t>(typeContentWrapper));
      break;
    }
    default:
      rv = NS_ERROR_INVALID_ARG;
  }
  return rv;
}

//*****************************************************************************
// nsWebBrowser::nsIWebProgressListener
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::OnStateChange(nsIWebProgress* aWebProgress,
                            nsIRequest* aRequest,
                            uint32_t aStateFlags,
                            nsresult aStatus)
{
  if (mPersist) {
    mPersist->GetCurrentState(&mPersistCurrentState);
  }
  if (aStateFlags & STATE_IS_NETWORK && aStateFlags & STATE_STOP) {
    mPersist = nullptr;
  }
  if (mProgressListener) {
    return mProgressListener->OnStateChange(aWebProgress, aRequest, aStateFlags,
                                            aStatus);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::OnProgressChange(nsIWebProgress* aWebProgress,
                               nsIRequest* aRequest,
                               int32_t aCurSelfProgress,
                               int32_t aMaxSelfProgress,
                               int32_t aCurTotalProgress,
                               int32_t aMaxTotalProgress)
{
  if (mPersist) {
    mPersist->GetCurrentState(&mPersistCurrentState);
  }
  if (mProgressListener) {
    return mProgressListener->OnProgressChange(
      aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress,
      aCurTotalProgress, aMaxTotalProgress);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::OnLocationChange(nsIWebProgress* aWebProgress,
                               nsIRequest* aRequest,
                               nsIURI* aLocation,
                               uint32_t aFlags)
{
  if (mProgressListener) {
    return mProgressListener->OnLocationChange(aWebProgress, aRequest, aLocation,
                                               aFlags);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::OnStatusChange(nsIWebProgress* aWebProgress,
                             nsIRequest* aRequest,
                             nsresult aStatus,
                             const char16_t* aMessage)
{
  if (mProgressListener) {
    return mProgressListener->OnStatusChange(aWebProgress, aRequest, aStatus,
                                             aMessage);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::OnSecurityChange(nsIWebProgress* aWebProgress,
                               nsIRequest* aRequest,
                               uint32_t aState)
{
  if (mProgressListener) {
    return mProgressListener->OnSecurityChange(aWebProgress, aRequest, aState);
  }
  return NS_OK;
}

//*****************************************************************************
// nsWebBrowser::nsIWebBrowserPersist
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::GetPersistFlags(uint32_t* aPersistFlags)
{
  NS_ENSURE_ARG_POINTER(aPersistFlags);
  nsresult rv = NS_OK;
  if (mPersist) {
    rv = mPersist->GetPersistFlags(&mPersistFlags);
  }
  *aPersistFlags = mPersistFlags;
  return rv;
}

NS_IMETHODIMP
nsWebBrowser::SetPersistFlags(uint32_t aPersistFlags)
{
  nsresult rv = NS_OK;
  mPersistFlags = aPersistFlags;
  if (mPersist) {
    rv = mPersist->SetPersistFlags(mPersistFlags);
    mPersist->GetPersistFlags(&mPersistFlags);
  }
  return rv;
}

NS_IMETHODIMP
nsWebBrowser::GetCurrentState(uint32_t* aCurrentState)
{
  NS_ENSURE_ARG_POINTER(aCurrentState);
  if (mPersist) {
    mPersist->GetCurrentState(&mPersistCurrentState);
  }
  *aCurrentState = mPersistCurrentState;
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetResult(nsresult* aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  if (mPersist) {
    mPersist->GetResult(&mPersistResult);
  }
  *aResult = mPersistResult;
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetProgressListener(nsIWebProgressListener** aProgressListener)
{
  NS_ENSURE_ARG_POINTER(aProgressListener);
  *aProgressListener = mProgressListener;
  NS_IF_ADDREF(*aProgressListener);
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetProgressListener(nsIWebProgressListener* aProgressListener)
{
  mProgressListener = aProgressListener;
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SaveURI(nsIURI* aURI,
                      nsISupports* aCacheKey,
                      nsIURI* aReferrer,
                      uint32_t aReferrerPolicy,
                      nsIInputStream* aPostData,
                      const char* aExtraHeaders,
                      nsISupports* aFile,
                      nsILoadContext* aPrivacyContext)
{
  return SavePrivacyAwareURI(
    aURI, aCacheKey, aReferrer, aReferrerPolicy, aPostData, aExtraHeaders,
    aFile, aPrivacyContext && aPrivacyContext->UsePrivateBrowsing());
}

NS_IMETHODIMP
nsWebBrowser::SavePrivacyAwareURI(nsIURI* aURI,
                                  nsISupports* aCacheKey,
                                  nsIURI* aReferrer,
                                  uint32_t aReferrerPolicy,
                                  nsIInputStream* aPostData,
                                  const char* aExtraHeaders,
                                  nsISupports* aFile,
                                  bool aIsPrivate)
{
  if (mPersist) {
    uint32_t currentState;
    mPersist->GetCurrentState(&currentState);
    if (currentState == PERSIST_STATE_FINISHED) {
      mPersist = nullptr;
    } else {
      // You can't save again until the last save has completed
      return NS_ERROR_FAILURE;
    }
  }

  nsCOMPtr<nsIURI> uri;
  if (aURI) {
    uri = aURI;
  } else {
    nsresult rv = GetCurrentURI(getter_AddRefs(uri));
    if (NS_FAILED(rv)) {
      return NS_ERROR_FAILURE;
    }
  }

  // Create a throwaway persistence object to do the work
  nsresult rv;
  mPersist = do_CreateInstance(NS_WEBBROWSERPERSIST_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  mPersist->SetProgressListener(this);
  mPersist->SetPersistFlags(mPersistFlags);
  mPersist->GetCurrentState(&mPersistCurrentState);

  rv = mPersist->SavePrivacyAwareURI(uri, aCacheKey, aReferrer, aReferrerPolicy,
                                     aPostData, aExtraHeaders, aFile, aIsPrivate);
  if (NS_FAILED(rv)) {
    mPersist = nullptr;
  }
  return rv;
}

NS_IMETHODIMP
nsWebBrowser::SaveChannel(nsIChannel* aChannel, nsISupports* aFile)
{
  if (mPersist) {
    uint32_t currentState;
    mPersist->GetCurrentState(&currentState);
    if (currentState == PERSIST_STATE_FINISHED) {
      mPersist = nullptr;
    } else {
      // You can't save again until the last save has completed
      return NS_ERROR_FAILURE;
    }
  }

  // Create a throwaway persistence object to do the work
  nsresult rv;
  mPersist = do_CreateInstance(NS_WEBBROWSERPERSIST_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  mPersist->SetProgressListener(this);
  mPersist->SetPersistFlags(mPersistFlags);
  mPersist->GetCurrentState(&mPersistCurrentState);
  rv = mPersist->SaveChannel(aChannel, aFile);
  if (NS_FAILED(rv)) {
    mPersist = nullptr;
  }
  return rv;
}

NS_IMETHODIMP
nsWebBrowser::SaveDocument(nsISupports* aDocumentish,
                           nsISupports* aFile,
                           nsISupports* aDataPath,
                           const char* aOutputContentType,
                           uint32_t aEncodingFlags,
                           uint32_t aWrapColumn)
{
  if (mPersist) {
    uint32_t currentState;
    mPersist->GetCurrentState(&currentState);
    if (currentState == PERSIST_STATE_FINISHED) {
      mPersist = nullptr;
    } else {
      // You can't save again until the last save has completed
      return NS_ERROR_FAILURE;
    }
  }

  // Use the specified DOM document, or if none is specified, the one
  // attached to the web browser.

  nsCOMPtr<nsISupports> doc;
  if (aDocumentish) {
    doc = aDocumentish;
  } else {
    nsCOMPtr<nsIDOMDocument> domDoc;
    GetDocument(getter_AddRefs(domDoc));
    doc = domDoc.forget();
  }
  if (!doc) {
    return NS_ERROR_FAILURE;
  }

  // Create a throwaway persistence object to do the work
  nsresult rv;
  mPersist = do_CreateInstance(NS_WEBBROWSERPERSIST_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  mPersist->SetProgressListener(this);
  mPersist->SetPersistFlags(mPersistFlags);
  mPersist->GetCurrentState(&mPersistCurrentState);
  rv = mPersist->SaveDocument(doc, aFile, aDataPath, aOutputContentType,
                              aEncodingFlags, aWrapColumn);
  if (NS_FAILED(rv)) {
    mPersist = nullptr;
  }
  return rv;
}

NS_IMETHODIMP
nsWebBrowser::CancelSave()
{
  if (mPersist) {
    return mPersist->CancelSave();
  }
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::Cancel(nsresult aReason)
{
  if (mPersist) {
    return mPersist->Cancel(aReason);
  }
  return NS_OK;
}

//*****************************************************************************
// nsWebBrowser::nsIBaseWindow
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::InitWindow(nativeWindow aParentNativeWindow,
                         nsIWidget* aParentWidget,
                         int32_t aX, int32_t aY,
                         int32_t aCX, int32_t aCY)
{
  NS_ENSURE_ARG(aParentNativeWindow || aParentWidget);
  NS_ENSURE_STATE(!mDocShell || mInitInfo);

  if (aParentWidget) {
    NS_ENSURE_SUCCESS(SetParentWidget(aParentWidget), NS_ERROR_FAILURE);
  } else
    NS_ENSURE_SUCCESS(SetParentNativeWindow(aParentNativeWindow),
                      NS_ERROR_FAILURE);

  NS_ENSURE_SUCCESS(SetPositionAndSize(aX, aY, aCX, aCY, 0),
                    NS_ERROR_FAILURE);

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::Create()
{
  NS_ENSURE_STATE(!mDocShell && (mParentNativeWindow || mParentWidget));

  nsresult rv = EnsureDocShellTreeOwner();
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIWidget> docShellParentWidget(mParentWidget);
  if (!mParentWidget) {
    // Create the widget
    mInternalWidget = do_CreateInstance(kChildCID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    docShellParentWidget = mInternalWidget;
    nsWidgetInitData widgetInit;

    widgetInit.clipChildren = true;

    widgetInit.mWindowType = eWindowType_child;
    LayoutDeviceIntRect bounds(mInitInfo->x, mInitInfo->y,
                               mInitInfo->cx, mInitInfo->cy);

    mInternalWidget->SetWidgetListener(&mWidgetListenerDelegate);
    rv = mInternalWidget->Create(nullptr, mParentNativeWindow, bounds,
                                 &widgetInit);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  nsCOMPtr<nsIDocShell> docShell(
    do_CreateInstance("@mozilla.org/docshell;1", &rv));
  NS_ENSURE_SUCCESS(rv, rv);
  nsDocShell::Cast(docShell)->SetOriginAttributes(mOriginAttributes);
  rv = SetDocShell(docShell);
  NS_ENSURE_SUCCESS(rv, rv);

  // get the system default window background colour
  LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground,
                        &mBackgroundColor);

  // the docshell has been set so we now have our listener registrars.
  if (mListenerArray) {
    // we had queued up some listeners, let's register them now.
    uint32_t count = mListenerArray->Length();
    uint32_t i = 0;
    NS_ASSERTION(count > 0, "array construction problem");
    while (i < count) {
      nsWebBrowserListenerState& state = mListenerArray->ElementAt(i);
      nsCOMPtr<nsISupports> listener = do_QueryReferent(state.mWeakPtr);
      NS_ASSERTION(listener, "bad listener");
      (void)BindListener(listener, state.mID);
      i++;
    }
    mListenerArray = nullptr;
  }

  // HACK ALERT - this registration registers the nsDocShellTreeOwner as a
  // nsIWebBrowserListener so it can setup its MouseListener in one of the
  // progress callbacks. If we can register the MouseListener another way, this
  // registration can go away, and nsDocShellTreeOwner can stop implementing
  // nsIWebProgressListener.
  nsCOMPtr<nsISupports> supports = nullptr;
  (void)mDocShellTreeOwner->QueryInterface(
    NS_GET_IID(nsIWebProgressListener),
    static_cast<void**>(getter_AddRefs(supports)));
  (void)BindListener(supports, NS_GET_IID(nsIWebProgressListener));

  NS_ENSURE_SUCCESS(mDocShellAsWin->InitWindow(nullptr, docShellParentWidget,
                                               mInitInfo->x, mInitInfo->y,
                                               mInitInfo->cx, mInitInfo->cy),
                    NS_ERROR_FAILURE);

  mDocShell->SetName(mInitInfo->name);
  if (mContentType == typeChromeWrapper) {
    mDocShell->SetItemType(nsIDocShellTreeItem::typeChrome);
  } else {
    mDocShell->SetItemType(nsIDocShellTreeItem::typeContent);
  }
  mDocShell->SetTreeOwner(mDocShellTreeOwner);

  // If the webbrowser is a content docshell item then we won't hear any
  // events from subframes. To solve that we install our own chrome event
  // handler that always gets called (even for subframes) for any bubbling
  // event.

  if (!mInitInfo->sessionHistory) {
    mInitInfo->sessionHistory = do_CreateInstance(NS_SHISTORY_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  mDocShellAsNav->SetSessionHistory(mInitInfo->sessionHistory);

  if (XRE_IsParentProcess()) {
    // Hook up global history. Do not fail if we can't - just warn.
    rv = EnableGlobalHistory(mShouldEnableHistory);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EnableGlobalHistory() failed");
  }

  NS_ENSURE_SUCCESS(mDocShellAsWin->Create(), NS_ERROR_FAILURE);

  // Hook into the OnSecurityChange() notification for lock/unlock icon
  // updates
  nsCOMPtr<mozIDOMWindowProxy> domWindow;
  rv = GetContentDOMWindow(getter_AddRefs(domWindow));
  if (NS_SUCCEEDED(rv)) {
    // this works because the implementation of nsISecureBrowserUI
    // (nsSecureBrowserUIImpl) gets a docShell from the domWindow,
    // and calls docShell->SetSecurityUI(this);
    nsCOMPtr<nsISecureBrowserUI> securityUI =
      do_CreateInstance(NS_SECURE_BROWSER_UI_CONTRACTID, &rv);
    if (NS_SUCCEEDED(rv)) {
      securityUI->Init(domWindow);
    }
  }

  mDocShellTreeOwner->AddToWatcher(); // evil twin of Remove in SetDocShell(0)
  mDocShellTreeOwner->AddChromeListeners();

  mInitInfo = nullptr;

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::Destroy()
{
  InternalDestroy();

  if (!mInitInfo) {
    mInitInfo = new nsWebBrowserInitInfo();
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetUnscaledDevicePixelsPerCSSPixel(double* aScale)
{
  *aScale = mParentWidget ? mParentWidget->GetDefaultScale().scale : 1.0;
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetDevicePixelsPerDesktopPixel(double* aScale)
{
  *aScale = mParentWidget ? mParentWidget->GetDesktopToDeviceScale().scale
                          : 1.0;
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetPositionDesktopPix(int32_t aX, int32_t aY)
{
  // XXX jfkthame
  // It's not clear to me whether this will be fully correct across
  // potential multi-screen, mixed-DPI configurations for all platforms;
  // we might need to add code paths that make it possible to pass the
  // desktop-pix parameters all the way through to the native widget,
  // to avoid the risk of device-pixel coords mapping to the wrong
  // display on OS X with mixed retina/non-retina screens.
  double scale = 1.0;
  GetDevicePixelsPerDesktopPixel(&scale);
  return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale));
}

NS_IMETHODIMP
nsWebBrowser::SetPosition(int32_t aX, int32_t aY)
{
  int32_t cx = 0;
  int32_t cy = 0;

  GetSize(&cx, &cy);

  return SetPositionAndSize(aX, aY, cx, cy, 0);
}

NS_IMETHODIMP
nsWebBrowser::GetPosition(int32_t* aX, int32_t* aY)
{
  return GetPositionAndSize(aX, aY, nullptr, nullptr);
}

NS_IMETHODIMP
nsWebBrowser::SetSize(int32_t aCX, int32_t aCY, bool aRepaint)
{
  int32_t x = 0;
  int32_t y = 0;

  GetPosition(&x, &y);

  return SetPositionAndSize(x, y, aCX, aCY,
                            aRepaint ? nsIBaseWindow::eRepaint : 0);
}

NS_IMETHODIMP
nsWebBrowser::GetSize(int32_t* aCX, int32_t* aCY)
{
  return GetPositionAndSize(nullptr, nullptr, aCX, aCY);
}

NS_IMETHODIMP
nsWebBrowser::SetPositionAndSize(int32_t aX, int32_t aY,
                                 int32_t aCX, int32_t aCY, uint32_t aFlags)
{
  if (!mDocShell) {
    mInitInfo->x = aX;
    mInitInfo->y = aY;
    mInitInfo->cx = aCX;
    mInitInfo->cy = aCY;
  } else {
    int32_t doc_x = aX;
    int32_t doc_y = aY;

    // If there is an internal widget we need to make the docShell coordinates
    // relative to the internal widget rather than the calling app's parent.
    // We also need to resize our widget then.
    if (mInternalWidget) {
      doc_x = doc_y = 0;
      NS_ENSURE_SUCCESS(mInternalWidget->Resize(aX, aY, aCX, aCY,
                                                !!(aFlags & nsIBaseWindow::eRepaint)),
                        NS_ERROR_FAILURE);
    }
    // Now reposition/ resize the doc
    NS_ENSURE_SUCCESS(
      mDocShellAsWin->SetPositionAndSize(doc_x, doc_y, aCX, aCY, aFlags),
      NS_ERROR_FAILURE);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetPositionAndSize(int32_t* aX, int32_t* aY,
                                 int32_t* aCX, int32_t* aCY)
{
  if (!mDocShell) {
    if (aX) {
      *aX = mInitInfo->x;
    }
    if (aY) {
      *aY = mInitInfo->y;
    }
    if (aCX) {
      *aCX = mInitInfo->cx;
    }
    if (aCY) {
      *aCY = mInitInfo->cy;
    }
  } else if (mInternalWidget) {
    LayoutDeviceIntRect bounds = mInternalWidget->GetBounds();

    if (aX) {
      *aX = bounds.x;
    }
    if (aY) {
      *aY = bounds.y;
    }
    if (aCX) {
      *aCX = bounds.width;
    }
    if (aCY) {
      *aCY = bounds.height;
    }
    return NS_OK;
  } else {
    // Can directly return this as it is the
    // same interface, thus same returns.
    return mDocShellAsWin->GetPositionAndSize(aX, aY, aCX, aCY);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::Repaint(bool aForce)
{
  NS_ENSURE_STATE(mDocShell);
  // Can directly return this as it is the
  // same interface, thus same returns.
  return mDocShellAsWin->Repaint(aForce);
}

NS_IMETHODIMP
nsWebBrowser::GetParentWidget(nsIWidget** aParentWidget)
{
  NS_ENSURE_ARG_POINTER(aParentWidget);

  *aParentWidget = mParentWidget;

  NS_IF_ADDREF(*aParentWidget);

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetParentWidget(nsIWidget* aParentWidget)
{
  NS_ENSURE_STATE(!mDocShell);

  mParentWidget = aParentWidget;
  if (mParentWidget) {
    mParentNativeWindow = mParentWidget->GetNativeData(NS_NATIVE_WIDGET);
  } else {
    mParentNativeWindow = nullptr;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetParentNativeWindow(nativeWindow* aParentNativeWindow)
{
  NS_ENSURE_ARG_POINTER(aParentNativeWindow);

  *aParentNativeWindow = mParentNativeWindow;

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetParentNativeWindow(nativeWindow aParentNativeWindow)
{
  NS_ENSURE_STATE(!mDocShell);

  mParentNativeWindow = aParentNativeWindow;

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetNativeHandle(nsAString& aNativeHandle)
{
  // the nativeHandle should be accessed from nsIXULWindow
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsWebBrowser::GetVisibility(bool* aVisibility)
{
  NS_ENSURE_ARG_POINTER(aVisibility);

  if (!mDocShell) {
    *aVisibility = mInitInfo->visible;
  } else {
    NS_ENSURE_SUCCESS(mDocShellAsWin->GetVisibility(aVisibility),
                      NS_ERROR_FAILURE);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetVisibility(bool aVisibility)
{
  if (!mDocShell) {
    mInitInfo->visible = aVisibility;
  } else {
    NS_ENSURE_SUCCESS(mDocShellAsWin->SetVisibility(aVisibility),
                      NS_ERROR_FAILURE);
    if (mInternalWidget) {
      mInternalWidget->Show(aVisibility);
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetEnabled(bool* aEnabled)
{
  if (mInternalWidget) {
    *aEnabled = mInternalWidget->IsEnabled();
    return NS_OK;
  }

  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsWebBrowser::SetEnabled(bool aEnabled)
{
  if (mInternalWidget) {
    return mInternalWidget->Enable(aEnabled);
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsWebBrowser::GetMainWidget(nsIWidget** aMainWidget)
{
  NS_ENSURE_ARG_POINTER(aMainWidget);

  if (mInternalWidget) {
    *aMainWidget = mInternalWidget;
  } else {
    *aMainWidget = mParentWidget;
  }

  NS_IF_ADDREF(*aMainWidget);

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetFocus()
{
  nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
  NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);

  nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
  return fm ? fm->SetFocusedWindow(window) : NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetTitle(char16_t** aTitle)
{
  NS_ENSURE_ARG_POINTER(aTitle);
  NS_ENSURE_STATE(mDocShell);

  NS_ENSURE_SUCCESS(mDocShellAsWin->GetTitle(aTitle), NS_ERROR_FAILURE);

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetTitle(const char16_t* aTitle)
{
  NS_ENSURE_STATE(mDocShell);

  NS_ENSURE_SUCCESS(mDocShellAsWin->SetTitle(aTitle), NS_ERROR_FAILURE);

  return NS_OK;
}

//*****************************************************************************
// nsWebBrowser::nsIScrollable
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::GetDefaultScrollbarPreferences(int32_t aScrollOrientation,
                                             int32_t* aScrollbarPref)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsScrollable->GetDefaultScrollbarPreferences(
    aScrollOrientation, aScrollbarPref);
}

NS_IMETHODIMP
nsWebBrowser::SetDefaultScrollbarPreferences(int32_t aScrollOrientation,
                                             int32_t aScrollbarPref)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsScrollable->SetDefaultScrollbarPreferences(
    aScrollOrientation, aScrollbarPref);
}

NS_IMETHODIMP
nsWebBrowser::GetScrollbarVisibility(bool* aVerticalVisible,
                                     bool* aHorizontalVisible)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsScrollable->GetScrollbarVisibility(aVerticalVisible,
                                                       aHorizontalVisible);
}

//*****************************************************************************
// nsWebBrowser::nsITextScroll
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::ScrollByLines(int32_t aNumLines)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsTextScroll->ScrollByLines(aNumLines);
}

NS_IMETHODIMP
nsWebBrowser::ScrollByPages(int32_t aNumPages)
{
  NS_ENSURE_STATE(mDocShell);

  return mDocShellAsTextScroll->ScrollByPages(aNumPages);
}

//*****************************************************************************
// nsWebBrowser: Listener Helpers
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::SetDocShell(nsIDocShell* aDocShell)
{
  // We need to keep the docshell alive while we perform the changes, but we
  // don't need to call any methods on it.
  nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocShell);
  mozilla::Unused << kungFuDeathGrip;

  if (aDocShell) {
    NS_ENSURE_TRUE(!mDocShell, NS_ERROR_FAILURE);

    nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(aDocShell));
    nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(aDocShell));
    nsCOMPtr<nsIWebNavigation> nav(do_QueryInterface(aDocShell));
    nsCOMPtr<nsIScrollable> scrollable(do_QueryInterface(aDocShell));
    nsCOMPtr<nsITextScroll> textScroll(do_QueryInterface(aDocShell));
    nsCOMPtr<nsIWebProgress> progress(do_GetInterface(aDocShell));
    NS_ENSURE_TRUE(req && baseWin && nav && scrollable && textScroll && progress,
                   NS_ERROR_FAILURE);

    mDocShell = aDocShell;
    mDocShellAsReq = req;
    mDocShellAsWin = baseWin;
    mDocShellAsNav = nav;
    mDocShellAsScrollable = scrollable;
    mDocShellAsTextScroll = textScroll;
    mWebProgress = progress;

    // By default, do not allow DNS prefetch, so we don't break our frozen
    // API.  Embeddors who decide to enable it should do so manually.
    mDocShell->SetAllowDNSPrefetch(false);

    // It's possible to call setIsActive() on us before we have a docshell.
    // If we're getting a docshell now, pass along our desired value. The
    // default here (true) matches the default of the docshell, so this is
    // a no-op unless setIsActive(false) has been called on us.
    mDocShell->SetIsActive(mIsActive);
  } else {
    if (mDocShellTreeOwner) {
      mDocShellTreeOwner->RemoveFromWatcher(); // evil twin of Add in Create()
    }
    if (mDocShellAsWin) {
      mDocShellAsWin->Destroy();
    }

    mDocShell = nullptr;
    mDocShellAsReq = nullptr;
    mDocShellAsWin = nullptr;
    mDocShellAsNav = nullptr;
    mDocShellAsScrollable = nullptr;
    mDocShellAsTextScroll = nullptr;
    mWebProgress = nullptr;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::EnsureDocShellTreeOwner()
{
  if (mDocShellTreeOwner) {
    return NS_OK;
  }

  mDocShellTreeOwner = new nsDocShellTreeOwner();
  mDocShellTreeOwner->WebBrowser(this);

  return NS_OK;
}

static void
DrawPaintedLayer(PaintedLayer* aLayer,
                 gfxContext* aContext,
                 const nsIntRegion& aRegionToDraw,
                 const nsIntRegion& aDirtyRegion,
                 DrawRegionClip aClip,
                 const nsIntRegion& aRegionToInvalidate,
                 void* aCallbackData)
{
  DrawTarget& aDrawTarget = *aContext->GetDrawTarget();

  ColorPattern color(ToDeviceColor(*static_cast<nscolor*>(aCallbackData)));
  nsIntRect dirtyRect = aRegionToDraw.GetBounds();
  aDrawTarget.FillRect(
    Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height), color);
}

void
nsWebBrowser::WindowRaised(nsIWidget* aWidget)
{
#if defined(DEBUG_smaug)
  nsCOMPtr<nsIDocument> document = mDocShell->GetDocument();
  nsAutoString documentURI;
  document->GetDocumentURI(documentURI);
  printf("nsWebBrowser::NS_ACTIVATE %p %s\n", (void*)this,
         NS_ConvertUTF16toUTF8(documentURI).get());
#endif
  Activate();
}

void
nsWebBrowser::WindowLowered(nsIWidget* aWidget)
{
#if defined(DEBUG_smaug)
  nsCOMPtr<nsIDocument> document = mDocShell->GetDocument();
  nsAutoString documentURI;
  document->GetDocumentURI(documentURI);
  printf("nsWebBrowser::NS_DEACTIVATE %p %s\n", (void*)this,
         NS_ConvertUTF16toUTF8(documentURI).get());
#endif
  Deactivate();
}

bool
nsWebBrowser::PaintWindow(nsIWidget* aWidget, LayoutDeviceIntRegion aRegion)
{
  LayerManager* layerManager = aWidget->GetLayerManager();
  NS_ASSERTION(layerManager, "Must be in paint event");

  layerManager->BeginTransaction();
  RefPtr<PaintedLayer> root = layerManager->CreatePaintedLayer();
  if (root) {
    nsIntRect dirtyRect = aRegion.GetBounds().ToUnknownRect();
    root->SetVisibleRegion(LayerIntRegion::FromUnknownRegion(dirtyRect));
    layerManager->SetRoot(root);
  }

  layerManager->EndTransaction(DrawPaintedLayer, &mBackgroundColor);
  return true;
}
/*
NS_IMETHODIMP
nsWebBrowser::GetPrimaryContentWindow(mozIDOMWindowProxy** aDOMWindow)
{
  *aDOMWindow = nullptr;

  nsCOMPtr<nsIDocShellTreeItem> item;
  NS_ENSURE_TRUE(mDocShellTreeOwner, NS_ERROR_FAILURE);
  mDocShellTreeOwner->GetPrimaryContentShell(getter_AddRefs(item));
  NS_ENSURE_TRUE(item, NS_ERROR_FAILURE);

  nsCOMPtr<nsIDocShell> docShell;
  docShell = do_QueryInterface(item);
  NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);

  nsCOMPtr<nsPIDOMWindowOuter> domWindow = docShell->GetWindow();
  NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);

  *aDOMWindow = domWindow;
  NS_ADDREF(*aDOMWindow);
  return NS_OK;
}
*/
//*****************************************************************************
// nsWebBrowser::nsIWebBrowserFocus
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::Activate(void)
{
  nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
  nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
  if (fm && window) {
    return fm->WindowRaised(window);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::Deactivate(void)
{
  nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
  nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
  if (fm && window) {
    return fm->WindowLowered(window);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetFocusAtFirstElement(void)
{
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetFocusAtLastElement(void)
{
  return NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow)
{
  NS_ENSURE_ARG_POINTER(aFocusedWindow);
  *aFocusedWindow = nullptr;

  NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);

  nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow();
  NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);

  nsCOMPtr<nsIDOMElement> focusedElement;
  nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
  return fm ? fm->GetFocusedElementForWindow(window, true, aFocusedWindow,
                                             getter_AddRefs(focusedElement)) :
              NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetFocusedWindow(mozIDOMWindowProxy* aFocusedWindow)
{
  nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
  return fm ? fm->SetFocusedWindow(aFocusedWindow) : NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::GetFocusedElement(nsIDOMElement** aFocusedElement)
{
  NS_ENSURE_ARG_POINTER(aFocusedElement);
  NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);

  nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow();
  NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);

  nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
  return
    fm ? fm->GetFocusedElementForWindow(window, true, nullptr, aFocusedElement) :
         NS_OK;
}

NS_IMETHODIMP
nsWebBrowser::SetFocusedElement(nsIDOMElement* aFocusedElement)
{
  nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
  return fm ? fm->SetFocus(aFocusedElement, 0) : NS_OK;
}

//*****************************************************************************
// nsWebBrowser::nsIWebBrowserStream
//*****************************************************************************

NS_IMETHODIMP
nsWebBrowser::OpenStream(nsIURI* aBaseURI, const nsACString& aContentType)
{
  nsresult rv;

  if (!mStream) {
    mStream = new nsEmbedStream();
    mStream->InitOwner(this);
    rv = mStream->Init();
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  return mStream->OpenStream(aBaseURI, aContentType);
}


NS_IMETHODIMP
nsWebBrowser::AppendToStream(const uint8_t* aData, uint32_t aLen)
{
  if (!mStream) {
    return NS_ERROR_FAILURE;
  }

  return mStream->AppendToStream(aData, aLen);
}

NS_IMETHODIMP
nsWebBrowser::CloseStream()
{
  nsresult rv;

  if (!mStream) {
    return NS_ERROR_FAILURE;
  }
  rv = mStream->CloseStream();

  mStream = nullptr;

  return rv;
}

bool
nsWebBrowser::WidgetListenerDelegate::PaintWindow(
  nsIWidget* aWidget, mozilla::LayoutDeviceIntRegion aRegion)
{
  RefPtr<nsWebBrowser> holder = mWebBrowser;
  return holder->PaintWindow(aWidget, aRegion);
}