diff options
Diffstat (limited to 'dom/base/nsJSTimeoutHandler.cpp')
-rw-r--r-- | dom/base/nsJSTimeoutHandler.cpp | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/dom/base/nsJSTimeoutHandler.cpp b/dom/base/nsJSTimeoutHandler.cpp new file mode 100644 index 0000000000..8736cd1ddd --- /dev/null +++ b/dom/base/nsJSTimeoutHandler.cpp @@ -0,0 +1,391 @@ +/* -*- 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 <algorithm> + +#include "mozilla/Attributes.h" +#include "mozilla/Function.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/dom/FunctionBinding.h" +#include "nsAXPCNativeCallContext.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsError.h" +#include "nsGlobalWindow.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIDocument.h" +#include "nsIScriptTimeoutHandler.h" +#include "nsIXPConnect.h" +#include "nsJSUtils.h" +#include "WorkerPrivate.h" + +static const char kSetIntervalStr[] = "setInterval"; +static const char kSetTimeoutStr[] = "setTimeout"; + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::dom::workers; + +// Our JS nsIScriptTimeoutHandler implementation. +class nsJSScriptTimeoutHandler final : public nsIScriptTimeoutHandler +{ +public: + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsJSScriptTimeoutHandler) + + nsJSScriptTimeoutHandler(); + // This will call SwapElements on aArguments with an empty array. + nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow* aWindow, + Function& aFunction, + nsTArray<JS::Heap<JS::Value>>&& aArguments, + ErrorResult& aError); + nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow* aWindow, + const nsAString& aExpression, bool* aAllowEval, + ErrorResult& aError); + nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + Function& aFunction, + nsTArray<JS::Heap<JS::Value>>&& aArguments); + nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + const nsAString& aExpression); + + virtual const nsAString& GetHandlerText() override; + + virtual Function* GetCallback() override + { + return mFunction; + } + + virtual const nsTArray<JS::Value>& GetArgs() override + { + return mArgs; + } + + virtual nsresult Call() override + { + return NS_OK; + } + + virtual void GetLocation(const char** aFileName, uint32_t* aLineNo, + uint32_t* aColumn) override + { + *aFileName = mFileName.get(); + *aLineNo = mLineNo; + *aColumn = mColumn; + } + + virtual void MarkForCC() override + { + if (mFunction) { + mFunction->MarkForCC(); + } + } + + void ReleaseJSObjects(); + +private: + ~nsJSScriptTimeoutHandler(); + + void Init(JSContext* aCx, + nsTArray<JS::Heap<JS::Value>>&& aArguments); + void Init(JSContext* aCx); + + // filename, line number and JS language version string of the + // caller of setTimeout() + nsCString mFileName; + uint32_t mLineNo; + uint32_t mColumn; + nsTArray<JS::Heap<JS::Value>> mArgs; + + // The expression to evaluate or function to call. If mFunction is non-null + // it should be used, else use mExpr. + nsString mExpr; + RefPtr<Function> mFunction; +}; + + +// nsJSScriptTimeoutHandler +// QueryInterface implementation for nsJSScriptTimeoutHandler +NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSScriptTimeoutHandler) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSScriptTimeoutHandler) + tmp->ReleaseJSObjects(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSScriptTimeoutHandler) + if (MOZ_UNLIKELY(cb.WantDebugInfo())) { + nsAutoCString name("nsJSScriptTimeoutHandler"); + if (tmp->mFunction) { + JSObject* obj = tmp->mFunction->CallablePreserveColor(); + JSFunction* fun = JS_GetObjectFunction(js::UncheckedUnwrap(obj)); + if (fun && JS_GetFunctionId(fun)) { + JSFlatString *funId = JS_ASSERT_STRING_IS_FLAT(JS_GetFunctionId(fun)); + size_t size = 1 + JS_PutEscapedFlatString(nullptr, 0, funId, 0); + char *funIdName = new char[size]; + if (funIdName) { + JS_PutEscapedFlatString(funIdName, size, funId, 0); + name.AppendLiteral(" ["); + name.Append(funIdName); + delete[] funIdName; + name.Append(']'); + } + } + } else { + name.AppendLiteral(" ["); + name.Append(tmp->mFileName); + name.Append(':'); + name.AppendInt(tmp->mLineNo); + name.Append(':'); + name.AppendInt(tmp->mColumn); + name.Append(']'); + } + cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get()); + } + else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsJSScriptTimeoutHandler, + tmp->mRefCnt.get()) + } + + if (tmp->mFunction) { + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFunction) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSScriptTimeoutHandler) + for (uint32_t i = 0; i < tmp->mArgs.Length(); ++i) { + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgs[i]) + } +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSScriptTimeoutHandler) + NS_INTERFACE_MAP_ENTRY(nsIScriptTimeoutHandler) + NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler) + +static bool +CheckCSPForEval(JSContext* aCx, nsGlobalWindow* aWindow, ErrorResult& aError) +{ + // if CSP is enabled, and setTimeout/setInterval was called with a string, + // disable the registration and log an error + nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc(); + if (!doc) { + // if there's no document, we don't have to do anything. + return true; + } + + nsCOMPtr<nsIContentSecurityPolicy> csp; + aError = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp)); + if (aError.Failed()) { + return false; + } + + if (!csp) { + return true; + } + + bool allowsEval = true; + bool reportViolation = false; + aError = csp->GetAllowsEval(&reportViolation, &allowsEval); + if (aError.Failed()) { + return false; + } + + if (reportViolation) { + // TODO : need actual script sample in violation report. + NS_NAMED_LITERAL_STRING(scriptSample, + "call to eval() or related function blocked by CSP"); + + // Get the calling location. + uint32_t lineNum = 0; + nsAutoString fileNameString; + if (!nsJSUtils::GetCallingLocation(aCx, fileNameString, &lineNum)) { + fileNameString.AssignLiteral("unknown"); + } + + csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, + fileNameString, scriptSample, lineNum, + EmptyString(), EmptyString()); + } + + return allowsEval; +} + +nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler() + : mLineNo(0) + , mColumn(0) +{ +} + +nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, + nsGlobalWindow *aWindow, + Function& aFunction, + nsTArray<JS::Heap<JS::Value>>&& aArguments, + ErrorResult& aError) + : mLineNo(0) + , mColumn(0) + , mFunction(&aFunction) +{ + if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) { + // This window was already closed, or never properly initialized, + // don't let a timer be scheduled on such a window. + aError.Throw(NS_ERROR_NOT_INITIALIZED); + return; + } + + Init(aCx, Move(aArguments)); +} + +nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, + nsGlobalWindow *aWindow, + const nsAString& aExpression, + bool* aAllowEval, + ErrorResult& aError) + : mLineNo(0) + , mColumn(0) + , mExpr(aExpression) +{ + if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) { + // This window was already closed, or never properly initialized, + // don't let a timer be scheduled on such a window. + aError.Throw(NS_ERROR_NOT_INITIALIZED); + return; + } + + *aAllowEval = CheckCSPForEval(aCx, aWindow, aError); + if (aError.Failed() || !*aAllowEval) { + return; + } + + Init(aCx); +} + +nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, + WorkerPrivate* aWorkerPrivate, + Function& aFunction, + nsTArray<JS::Heap<JS::Value>>&& aArguments) + : mLineNo(0) + , mColumn(0) + , mFunction(&aFunction) +{ + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + Init(aCx, Move(aArguments)); +} + +nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, + WorkerPrivate* aWorkerPrivate, + const nsAString& aExpression) + : mLineNo(0) + , mColumn(0) + , mExpr(aExpression) +{ + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + Init(aCx); +} + +nsJSScriptTimeoutHandler::~nsJSScriptTimeoutHandler() +{ + ReleaseJSObjects(); +} + +void +nsJSScriptTimeoutHandler::Init(JSContext* aCx, + nsTArray<JS::Heap<JS::Value>>&& aArguments) +{ + mozilla::HoldJSObjects(this); + mArgs = Move(aArguments); + + Init(aCx); +} + +void +nsJSScriptTimeoutHandler::Init(JSContext* aCx) +{ + // Get the calling location. + nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn); +} + +void +nsJSScriptTimeoutHandler::ReleaseJSObjects() +{ + if (mFunction) { + mFunction = nullptr; + mArgs.Clear(); + mozilla::DropJSObjects(this); + } +} + +const nsAString& +nsJSScriptTimeoutHandler::GetHandlerText() +{ + NS_ASSERTION(!mFunction, "No expression, so no handler text!"); + return mExpr; +} + +already_AddRefed<nsIScriptTimeoutHandler> +NS_CreateJSTimeoutHandler(JSContext *aCx, nsGlobalWindow *aWindow, + Function& aFunction, + const Sequence<JS::Value>& aArguments, + ErrorResult& aError) +{ + nsTArray<JS::Heap<JS::Value>> args; + if (!args.AppendElements(aArguments, fallible)) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + RefPtr<nsJSScriptTimeoutHandler> handler = + new nsJSScriptTimeoutHandler(aCx, aWindow, aFunction, Move(args), aError); + return aError.Failed() ? nullptr : handler.forget(); +} + +already_AddRefed<nsIScriptTimeoutHandler> +NS_CreateJSTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow, + const nsAString& aExpression, ErrorResult& aError) +{ + bool allowEval = false; + RefPtr<nsJSScriptTimeoutHandler> handler = + new nsJSScriptTimeoutHandler(aCx, aWindow, aExpression, &allowEval, aError); + if (aError.Failed() || !allowEval) { + return nullptr; + } + + return handler.forget(); +} + +already_AddRefed<nsIScriptTimeoutHandler> +NS_CreateJSTimeoutHandler(JSContext *aCx, WorkerPrivate* aWorkerPrivate, + Function& aFunction, + const Sequence<JS::Value>& aArguments, + ErrorResult& aError) +{ + nsTArray<JS::Heap<JS::Value>> args; + if (!args.AppendElements(aArguments, fallible)) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + RefPtr<nsJSScriptTimeoutHandler> handler = + new nsJSScriptTimeoutHandler(aCx, aWorkerPrivate, aFunction, Move(args)); + return handler.forget(); +} + +already_AddRefed<nsIScriptTimeoutHandler> +NS_CreateJSTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + const nsAString& aExpression) +{ + RefPtr<nsJSScriptTimeoutHandler> handler = + new nsJSScriptTimeoutHandler(aCx, aWorkerPrivate, aExpression); + return handler.forget(); +} |