dom/workers/WorkerDebuggerManager.cpp
author Masayuki Nakano <masayuki@d-toybox.com>
Fri, 19 May 2017 17:49:41 +0900
changeset 407399 b75c111837a802ceb953dba50a3c5a193d53ca22
parent 333837 fcc0936b576daa150697671849a191009ca33811
child 413572 9846de3bd9545fb2c2b803a36af85568ccf2473b
permissions -rw-r--r--
Bug 1339543 part 4 Change nsIWidget::ExecuteNativeKeyBinding() to nsIWidget::GetEditCommands() which just retrieves edit commands for the type r=smaug Now, nsIWidget::ExecuteNativeKeyBinding() isn't used by anybody for executing edit commands. Instead, they need array of edit commands for the key combination. So, the method should be renamed to GetEditCommands() and just return edit commands as an array. MozReview-Commit-ID: 4G0B1lJ8Lbe

/* -*- 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 "WorkerDebuggerManager.h"

#include "nsISimpleEnumerator.h"

#include "mozilla/ClearOnShutdown.h"

#include "WorkerPrivate.h"

USING_WORKERS_NAMESPACE

namespace {

class RegisterDebuggerMainThreadRunnable final : public mozilla::Runnable
{
  WorkerPrivate* mWorkerPrivate;
  bool mNotifyListeners;

public:
  RegisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
                                     bool aNotifyListeners)
  : mWorkerPrivate(aWorkerPrivate),
    mNotifyListeners(aNotifyListeners)
  { }

private:
  ~RegisterDebuggerMainThreadRunnable()
  { }

  NS_IMETHOD
  Run() override
  {
    WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
    MOZ_ASSERT(manager);

    manager->RegisterDebuggerMainThread(mWorkerPrivate, mNotifyListeners);
    return NS_OK;
  }
};

class UnregisterDebuggerMainThreadRunnable final : public mozilla::Runnable
{
  WorkerPrivate* mWorkerPrivate;

public:
  explicit UnregisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate)
  : mWorkerPrivate(aWorkerPrivate)
  { }

private:
  ~UnregisterDebuggerMainThreadRunnable()
  { }

  NS_IMETHOD
  Run() override
  {
    WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
    MOZ_ASSERT(manager);

    manager->UnregisterDebuggerMainThread(mWorkerPrivate);
    return NS_OK;
  }
};

// Does not hold an owning reference.
static WorkerDebuggerManager* gWorkerDebuggerManager;

} /* anonymous namespace */

BEGIN_WORKERS_NAMESPACE

class WorkerDebuggerEnumerator final : public nsISimpleEnumerator
{
  nsTArray<RefPtr<WorkerDebugger>> mDebuggers;
  uint32_t mIndex;

public:
  explicit WorkerDebuggerEnumerator(
                             const nsTArray<RefPtr<WorkerDebugger>>& aDebuggers)
  : mDebuggers(aDebuggers), mIndex(0)
  {
  }

  NS_DECL_ISUPPORTS
  NS_DECL_NSISIMPLEENUMERATOR

private:
  ~WorkerDebuggerEnumerator() {}
};

NS_IMPL_ISUPPORTS(WorkerDebuggerEnumerator, nsISimpleEnumerator);

NS_IMETHODIMP
WorkerDebuggerEnumerator::HasMoreElements(bool* aResult)
{
  *aResult = mIndex < mDebuggers.Length();
  return NS_OK;
};

NS_IMETHODIMP
WorkerDebuggerEnumerator::GetNext(nsISupports** aResult)
{
  if (mIndex == mDebuggers.Length()) {
    return NS_ERROR_FAILURE;
  }

  mDebuggers.ElementAt(mIndex++).forget(aResult);
  return NS_OK;
};

WorkerDebuggerManager::WorkerDebuggerManager()
: mMutex("WorkerDebuggerManager::mMutex")
{
  AssertIsOnMainThread();
}

WorkerDebuggerManager::~WorkerDebuggerManager()
{
  AssertIsOnMainThread();
}

// static
already_AddRefed<WorkerDebuggerManager>
WorkerDebuggerManager::GetInstance()
{
  RefPtr<WorkerDebuggerManager> manager = WorkerDebuggerManager::GetOrCreate();
  return manager.forget();
}

// static
WorkerDebuggerManager*
WorkerDebuggerManager::GetOrCreate()
{
  AssertIsOnMainThread();

  if (!gWorkerDebuggerManager) {
    // The observer service now owns us until shutdown.
    gWorkerDebuggerManager = new WorkerDebuggerManager();
    if (NS_FAILED(gWorkerDebuggerManager->Init())) {
      NS_WARNING("Failed to initialize worker debugger manager!");
      gWorkerDebuggerManager = nullptr;
      return nullptr;
    }
  }

  return gWorkerDebuggerManager;
}

WorkerDebuggerManager*
WorkerDebuggerManager::Get()
{
  MOZ_ASSERT(gWorkerDebuggerManager);
  return gWorkerDebuggerManager;
}

NS_IMPL_ISUPPORTS(WorkerDebuggerManager, nsIObserver, nsIWorkerDebuggerManager);

NS_IMETHODIMP
WorkerDebuggerManager::Observe(nsISupports* aSubject, const char* aTopic,
                               const char16_t* aData)
{
  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
    Shutdown();
    return NS_OK;
  }

  NS_NOTREACHED("Unknown observer topic!");
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebuggerManager::GetWorkerDebuggerEnumerator(
                                                  nsISimpleEnumerator** aResult)
{
  AssertIsOnMainThread();

  RefPtr<WorkerDebuggerEnumerator> enumerator =
    new WorkerDebuggerEnumerator(mDebuggers);
  enumerator.forget(aResult);
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebuggerManager::AddListener(nsIWorkerDebuggerManagerListener* aListener)
{
  AssertIsOnMainThread();

  MutexAutoLock lock(mMutex);

  if (mListeners.Contains(aListener)) {
    return NS_ERROR_INVALID_ARG;
  }

  mListeners.AppendElement(aListener);
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebuggerManager::RemoveListener(
                                    nsIWorkerDebuggerManagerListener* aListener)
{
  AssertIsOnMainThread();

  MutexAutoLock lock(mMutex);

  if (!mListeners.Contains(aListener)) {
    return NS_OK;
  }

  mListeners.RemoveElement(aListener);
  return NS_OK;
}

nsresult
WorkerDebuggerManager::Init()
{
  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);

  nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

void
WorkerDebuggerManager::Shutdown()
{
  AssertIsOnMainThread();

  MutexAutoLock lock(mMutex);

  mListeners.Clear();
}

void
WorkerDebuggerManager::RegisterDebugger(WorkerPrivate* aWorkerPrivate)
{
  aWorkerPrivate->AssertIsOnParentThread();

  if (NS_IsMainThread()) {
    // When the parent thread is the main thread, it will always block until all
    // register liseners have been called, since it cannot continue until the
    // call to RegisterDebuggerMainThread returns.
    //
    // In this case, it is always safe to notify all listeners on the main
    // thread, even if there were no listeners at the time this method was
    // called, so we can always pass true for the value of aNotifyListeners.
    // This avoids having to lock mMutex to check whether mListeners is empty.
    RegisterDebuggerMainThread(aWorkerPrivate, true);
  } else {
    // We guarantee that if any register listeners are called, the worker does
    // not start running until all register listeners have been called. To
    // guarantee this, the parent thread should block until all register
    // listeners have been called.
    //
    // However, to avoid overhead when the debugger is not being used, the
    // parent thread will only block if there were any listeners at the time
    // this method was called. As a result, we should not notify any listeners
    // on the main thread if there were no listeners at the time this method was
    // called, because the parent will not be blocking in that case.
    bool hasListeners = false;
    {
      MutexAutoLock lock(mMutex);

      hasListeners = !mListeners.IsEmpty();
    }

    nsCOMPtr<nsIRunnable> runnable =
      new RegisterDebuggerMainThreadRunnable(aWorkerPrivate, hasListeners);
    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL));

    if (hasListeners) {
      aWorkerPrivate->WaitForIsDebuggerRegistered(true);
    }
  }
}

void
WorkerDebuggerManager::UnregisterDebugger(WorkerPrivate* aWorkerPrivate)
{
  aWorkerPrivate->AssertIsOnParentThread();

  if (NS_IsMainThread()) {
    UnregisterDebuggerMainThread(aWorkerPrivate);
  } else {
    nsCOMPtr<nsIRunnable> runnable =
      new UnregisterDebuggerMainThreadRunnable(aWorkerPrivate);
    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL));

    aWorkerPrivate->WaitForIsDebuggerRegistered(false);
  }
}

void
WorkerDebuggerManager::RegisterDebuggerMainThread(WorkerPrivate* aWorkerPrivate,
                                                  bool aNotifyListeners)
{
  AssertIsOnMainThread();

  RefPtr<WorkerDebugger> debugger = new WorkerDebugger(aWorkerPrivate);
  mDebuggers.AppendElement(debugger);

  aWorkerPrivate->SetDebugger(debugger);

  if (aNotifyListeners) {
    nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> listeners;
    {
      MutexAutoLock lock(mMutex);

      listeners = mListeners;
    }

    for (size_t index = 0; index < listeners.Length(); ++index) {
      listeners[index]->OnRegister(debugger);
    }
  }

  aWorkerPrivate->SetIsDebuggerRegistered(true);
}

void
WorkerDebuggerManager::UnregisterDebuggerMainThread(
                                                  WorkerPrivate* aWorkerPrivate)
{
  AssertIsOnMainThread();

  // There is nothing to do here if the debugger was never succesfully
  // registered. We need to check this on the main thread because the worker
  // does not wait for the registration to complete if there were no listeners
  // installed when it started.
  if (!aWorkerPrivate->IsDebuggerRegistered()) {
    return;
  }

  RefPtr<WorkerDebugger> debugger = aWorkerPrivate->Debugger();
  mDebuggers.RemoveElement(debugger);

  aWorkerPrivate->SetDebugger(nullptr);

  nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> listeners;
  {
    MutexAutoLock lock(mMutex);

    listeners = mListeners;
  }

  for (size_t index = 0; index < listeners.Length(); ++index) {
    listeners[index]->OnUnregister(debugger);
  }

  debugger->Close();
  aWorkerPrivate->SetIsDebuggerRegistered(false);
}

END_WORKERS_NAMESPACE