dom/workers/WorkerDebugger.cpp
author Dave Townsend <dtownsend@oxymoronical.com>
Wed, 06 Feb 2019 11:09:06 -0800
changeset 520643 fbef638149b4426032f58903a9abf480341a3ff8
parent 519513 7ba97389b18333d03d4c07e101d1b156ec9b692f
child 520649 73824b8dd85567364b7e852b418bdf2312815e6a
permissions -rw-r--r--
Bug 1518639: Add boilerplate support for a windows remote client and server. r=jimm,froydnj Adds build config and stubs for a windows implementation of the remote client and server. Differential Revision: https://phabricator.services.mozilla.com/D19074

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

#include "mozilla/dom/MessageEvent.h"
#include "mozilla/dom/MessageEventBinding.h"
#include "mozilla/PerformanceUtils.h"
#include "nsProxyRelease.h"
#include "nsQueryObject.h"
#include "nsThreadUtils.h"
#include "ScriptLoader.h"
#include "WorkerCommon.h"
#include "WorkerError.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "WorkerScope.h"
#if defined(XP_WIN)
#  include <processthreadsapi.h>  // for GetCurrentProcessId()
#else
#  include <unistd.h>  // for getpid()
#endif                 // defined(XP_WIN)

namespace mozilla {
namespace dom {

namespace {

class DebuggerMessageEventRunnable : public WorkerDebuggerRunnable {
  nsString mMessage;

 public:
  DebuggerMessageEventRunnable(WorkerPrivate* aWorkerPrivate,
                               const nsAString& aMessage)
      : WorkerDebuggerRunnable(aWorkerPrivate), mMessage(aMessage) {}

 private:
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    WorkerDebuggerGlobalScope* globalScope =
        aWorkerPrivate->DebuggerGlobalScope();
    MOZ_ASSERT(globalScope);

    JS::Rooted<JSString*> message(
        aCx, JS_NewUCStringCopyN(aCx, mMessage.get(), mMessage.Length()));
    if (!message) {
      return false;
    }
    JS::Rooted<JS::Value> data(aCx, JS::StringValue(message));

    RefPtr<MessageEvent> event =
        new MessageEvent(globalScope, nullptr, nullptr);
    event->InitMessageEvent(nullptr, NS_LITERAL_STRING("message"),
                            CanBubble::eNo, Cancelable::eYes, data,
                            EmptyString(), EmptyString(), nullptr,
                            Sequence<OwningNonNull<MessagePort>>());
    event->SetTrusted(true);

    globalScope->DispatchEvent(*event);
    return true;
  }
};

class CompileDebuggerScriptRunnable final : public WorkerDebuggerRunnable {
  nsString mScriptURL;

 public:
  CompileDebuggerScriptRunnable(WorkerPrivate* aWorkerPrivate,
                                const nsAString& aScriptURL)
      : WorkerDebuggerRunnable(aWorkerPrivate), mScriptURL(aScriptURL) {}

 private:
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->AssertIsOnWorkerThread();

    WorkerDebuggerGlobalScope* globalScope =
        aWorkerPrivate->CreateDebuggerGlobalScope(aCx);
    if (!globalScope) {
      NS_WARNING("Failed to make global!");
      return false;
    }

    if (NS_WARN_IF(!aWorkerPrivate->EnsureClientSource())) {
      return false;
    }

    if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) {
      return false;
    }

    // Initialize performance state which might be used on the main thread, as
    // in CompileScriptRunnable. This runnable might execute first.
    aWorkerPrivate->EnsurePerformanceStorage();
    if (mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()) {
      aWorkerPrivate->EnsurePerformanceCounter();
    }

    JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());

    ErrorResult rv;
    JSAutoRealm ar(aCx, global);
    workerinternals::LoadMainScript(aWorkerPrivate, mScriptURL, DebuggerScript,
                                    rv);
    rv.WouldReportJSException();
    // Explicitly ignore NS_BINDING_ABORTED on rv.  Or more precisely, still
    // return false and don't SetWorkerScriptExecutedSuccessfully() in that
    // case, but don't throw anything on aCx.  The idea is to not dispatch error
    // events if our load is canceled with that error code.
    if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
      rv.SuppressException();
      return false;
    }
    // Make sure to propagate exceptions from rv onto aCx, so that they will get
    // reported after we return.  We do this for all failures on rv, because now
    // we're using rv to track all the state we care about.
    if (rv.MaybeSetPendingException(aCx)) {
      return false;
    }

    return true;
  }
};

}  // namespace

class WorkerDebugger::PostDebuggerMessageRunnable final : public Runnable {
  WorkerDebugger* mDebugger;
  nsString mMessage;

 public:
  PostDebuggerMessageRunnable(WorkerDebugger* aDebugger,
                              const nsAString& aMessage)
      : mozilla::Runnable("PostDebuggerMessageRunnable"),
        mDebugger(aDebugger),
        mMessage(aMessage) {}

 private:
  ~PostDebuggerMessageRunnable() {}

  NS_IMETHOD
  Run() override {
    mDebugger->PostMessageToDebuggerOnMainThread(mMessage);

    return NS_OK;
  }
};

class WorkerDebugger::ReportDebuggerErrorRunnable final : public Runnable {
  WorkerDebugger* mDebugger;
  nsString mFilename;
  uint32_t mLineno;
  nsString mMessage;

 public:
  ReportDebuggerErrorRunnable(WorkerDebugger* aDebugger,
                              const nsAString& aFilename, uint32_t aLineno,
                              const nsAString& aMessage)
      : Runnable("ReportDebuggerErrorRunnable"),
        mDebugger(aDebugger),
        mFilename(aFilename),
        mLineno(aLineno),
        mMessage(aMessage) {}

 private:
  ~ReportDebuggerErrorRunnable() {}

  NS_IMETHOD
  Run() override {
    mDebugger->ReportErrorToDebuggerOnMainThread(mFilename, mLineno, mMessage);

    return NS_OK;
  }
};

WorkerDebugger::WorkerDebugger(WorkerPrivate* aWorkerPrivate)
    : mWorkerPrivate(aWorkerPrivate), mIsInitialized(false) {
  AssertIsOnMainThread();
}

WorkerDebugger::~WorkerDebugger() {
  MOZ_ASSERT(!mWorkerPrivate);

  if (!NS_IsMainThread()) {
    for (size_t index = 0; index < mListeners.Length(); ++index) {
      NS_ReleaseOnMainThreadSystemGroup("WorkerDebugger::mListeners",
                                        mListeners[index].forget());
    }
  }
}

NS_IMPL_ISUPPORTS(WorkerDebugger, nsIWorkerDebugger)

NS_IMETHODIMP
WorkerDebugger::GetIsClosed(bool* aResult) {
  AssertIsOnMainThread();

  *aResult = !mWorkerPrivate;
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::GetIsChrome(bool* aResult) {
  AssertIsOnMainThread();

  if (!mWorkerPrivate) {
    return NS_ERROR_UNEXPECTED;
  }

  *aResult = mWorkerPrivate->IsChromeWorker();
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::GetIsInitialized(bool* aResult) {
  AssertIsOnMainThread();

  if (!mWorkerPrivate) {
    return NS_ERROR_UNEXPECTED;
  }

  *aResult = mIsInitialized;
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::GetParent(nsIWorkerDebugger** aResult) {
  AssertIsOnMainThread();

  if (!mWorkerPrivate) {
    return NS_ERROR_UNEXPECTED;
  }

  WorkerPrivate* parent = mWorkerPrivate->GetParent();
  if (!parent) {
    *aResult = nullptr;
    return NS_OK;
  }

  MOZ_ASSERT(mWorkerPrivate->IsDedicatedWorker());

  nsCOMPtr<nsIWorkerDebugger> debugger = parent->Debugger();
  debugger.forget(aResult);
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::GetType(uint32_t* aResult) {
  AssertIsOnMainThread();

  if (!mWorkerPrivate) {
    return NS_ERROR_UNEXPECTED;
  }

  *aResult = mWorkerPrivate->Type();
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::GetUrl(nsAString& aResult) {
  AssertIsOnMainThread();

  if (!mWorkerPrivate) {
    return NS_ERROR_UNEXPECTED;
  }

  aResult = mWorkerPrivate->ScriptURL();
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::GetWindow(mozIDOMWindow** aResult) {
  AssertIsOnMainThread();

  if (!mWorkerPrivate) {
    return NS_ERROR_UNEXPECTED;
  }

  if (mWorkerPrivate->GetParent() || !mWorkerPrivate->IsDedicatedWorker()) {
    *aResult = nullptr;
    return NS_OK;
  }

  nsCOMPtr<nsPIDOMWindowInner> window = mWorkerPrivate->GetWindow();
  window.forget(aResult);
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::GetPrincipal(nsIPrincipal** aResult) {
  AssertIsOnMainThread();
  MOZ_ASSERT(aResult);

  if (!mWorkerPrivate) {
    return NS_ERROR_UNEXPECTED;
  }

  nsCOMPtr<nsIPrincipal> prin = mWorkerPrivate->GetPrincipal();
  prin.forget(aResult);

  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::GetServiceWorkerID(uint32_t* aResult) {
  AssertIsOnMainThread();
  MOZ_ASSERT(aResult);

  if (!mWorkerPrivate || !mWorkerPrivate->IsServiceWorker()) {
    return NS_ERROR_UNEXPECTED;
  }

  *aResult = mWorkerPrivate->ServiceWorkerID();
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::Initialize(const nsAString& aURL) {
  AssertIsOnMainThread();

  if (!mWorkerPrivate) {
    return NS_ERROR_UNEXPECTED;
  }

  if (!mIsInitialized) {
    RefPtr<CompileDebuggerScriptRunnable> runnable =
        new CompileDebuggerScriptRunnable(mWorkerPrivate, aURL);
    if (!runnable->Dispatch()) {
      return NS_ERROR_FAILURE;
    }

    mIsInitialized = true;
  }

  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::PostMessageMoz(const nsAString& aMessage) {
  AssertIsOnMainThread();

  if (!mWorkerPrivate || !mIsInitialized) {
    return NS_ERROR_UNEXPECTED;
  }

  RefPtr<DebuggerMessageEventRunnable> runnable =
      new DebuggerMessageEventRunnable(mWorkerPrivate, aMessage);
  if (!runnable->Dispatch()) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::AddListener(nsIWorkerDebuggerListener* aListener) {
  AssertIsOnMainThread();

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

  mListeners.AppendElement(aListener);
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener* aListener) {
  AssertIsOnMainThread();

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

  mListeners.RemoveElement(aListener);
  return NS_OK;
}

NS_IMETHODIMP
WorkerDebugger::SetDebuggerReady(bool aReady)
{
  return mWorkerPrivate->SetIsDebuggerReady(aReady);
}

void WorkerDebugger::Close() {
  MOZ_ASSERT(mWorkerPrivate);
  mWorkerPrivate = nullptr;

  nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
  for (size_t index = 0; index < listeners.Length(); ++index) {
    listeners[index]->OnClose();
  }
}

void WorkerDebugger::PostMessageToDebugger(const nsAString& aMessage) {
  mWorkerPrivate->AssertIsOnWorkerThread();

  RefPtr<PostDebuggerMessageRunnable> runnable =
      new PostDebuggerMessageRunnable(this, aMessage);
  if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
    NS_WARNING("Failed to post message to debugger on main thread!");
  }
}

void WorkerDebugger::PostMessageToDebuggerOnMainThread(
    const nsAString& aMessage) {
  AssertIsOnMainThread();

  nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
  for (size_t index = 0; index < listeners.Length(); ++index) {
    listeners[index]->OnMessage(aMessage);
  }
}

void WorkerDebugger::ReportErrorToDebugger(const nsAString& aFilename,
                                           uint32_t aLineno,
                                           const nsAString& aMessage) {
  mWorkerPrivate->AssertIsOnWorkerThread();

  RefPtr<ReportDebuggerErrorRunnable> runnable =
      new ReportDebuggerErrorRunnable(this, aFilename, aLineno, aMessage);
  if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
    NS_WARNING("Failed to report error to debugger on main thread!");
  }
}

void WorkerDebugger::ReportErrorToDebuggerOnMainThread(
    const nsAString& aFilename, uint32_t aLineno, const nsAString& aMessage) {
  AssertIsOnMainThread();

  nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
  for (size_t index = 0; index < listeners.Length(); ++index) {
    listeners[index]->OnError(aFilename, aLineno, aMessage);
  }

  WorkerErrorReport report;
  report.mMessage = aMessage;
  report.mFilename = aFilename;
  WorkerErrorReport::LogErrorToConsole(report, 0);
}

RefPtr<PerformanceInfoPromise> WorkerDebugger::ReportPerformanceInfo() {
  AssertIsOnMainThread();
  nsCOMPtr<nsPIDOMWindowOuter> top;
  RefPtr<WorkerDebugger> self = this;

#if defined(XP_WIN)
  uint32_t pid = GetCurrentProcessId();
#else
  uint32_t pid = getpid();
#endif
  bool isTopLevel = false;
  uint64_t windowID = mWorkerPrivate->WindowID();
  PerformanceMemoryInfo memoryInfo;

  // Walk up to our containing page and its window
  WorkerPrivate* wp = mWorkerPrivate;
  while (wp->GetParent()) {
    wp = wp->GetParent();
  }
  nsPIDOMWindowInner* win = wp->GetWindow();
  if (win) {
    nsPIDOMWindowOuter* outer = win->GetOuterWindow();
    if (outer) {
      top = outer->GetTop();
      if (top) {
        windowID = top->WindowID();
        isTopLevel = outer->IsTopLevelWindow();
      }
    }
  }

  // getting the worker URL
  RefPtr<nsIURI> scriptURI = mWorkerPrivate->GetResolvedScriptURI();
  if (NS_WARN_IF(!scriptURI)) {
    // This can happen at shutdown, let's stop here.
    return PerformanceInfoPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
  }
  nsCString url = scriptURI->GetSpecOrDefault();

  // Workers only produce metrics for a single category -
  // DispatchCategory::Worker. We still return an array of CategoryDispatch so
  // the PerformanceInfo struct is common to all performance counters throughout
  // Firefox.
  FallibleTArray<CategoryDispatch> items;
  uint64_t duration = 0;
  uint16_t count = 0;
  uint64_t perfId = 0;

  RefPtr<PerformanceCounter> perf = mWorkerPrivate->GetPerformanceCounter();
  if (perf) {
    perfId = perf->GetID();
    count = perf->GetTotalDispatchCount();
    duration = perf->GetExecutionDuration();
    CategoryDispatch item =
        CategoryDispatch(DispatchCategory::Worker.GetValue(), count);
    if (!items.AppendElement(item, fallible)) {
      NS_ERROR("Could not complete the operation");
    }
  }

  if (!isTopLevel) {
    return PerformanceInfoPromise::CreateAndResolve(
        PerformanceInfo(url, pid, windowID, duration, perfId, true, isTopLevel,
                        memoryInfo, items),
        __func__);
  }

  // We need to keep a ref on workerPrivate, passed to the promise,
  // to make sure it's still aloive when collecting the info.
  RefPtr<WorkerPrivate> workerRef = mWorkerPrivate;
  RefPtr<AbstractThread> mainThread =
      SystemGroup::AbstractMainThreadFor(TaskCategory::Performance);

  return CollectMemoryInfo(top, mainThread)
      ->Then(mainThread, __func__,
             [workerRef, url, pid, perfId, windowID, duration, isTopLevel,
              items](const PerformanceMemoryInfo& aMemoryInfo) {
               return PerformanceInfoPromise::CreateAndResolve(
                   PerformanceInfo(url, pid, windowID, duration, perfId, true,
                                   isTopLevel, aMemoryInfo, items),
                   __func__);
             },
             [workerRef]() {
               return PerformanceInfoPromise::CreateAndReject(NS_ERROR_FAILURE,
                                                              __func__);
             });
}

}  // namespace dom
}  // namespace mozilla