1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/base/nsJSTimeoutHandler.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,443 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 sw=2 et tw=78: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "nsCOMPtr.h" 1.11 +#include "nsIDocument.h" 1.12 +#include "nsIScriptTimeoutHandler.h" 1.13 +#include "nsIXPConnect.h" 1.14 +#include "nsJSUtils.h" 1.15 +#include "nsContentUtils.h" 1.16 +#include "nsError.h" 1.17 +#include "nsGlobalWindow.h" 1.18 +#include "nsIContentSecurityPolicy.h" 1.19 +#include "mozilla/Attributes.h" 1.20 +#include "mozilla/Likely.h" 1.21 +#include <algorithm> 1.22 +#include "mozilla/dom/FunctionBinding.h" 1.23 +#include "nsAXPCNativeCallContext.h" 1.24 + 1.25 +static const char kSetIntervalStr[] = "setInterval"; 1.26 +static const char kSetTimeoutStr[] = "setTimeout"; 1.27 + 1.28 +using namespace mozilla; 1.29 +using namespace mozilla::dom; 1.30 + 1.31 +// Our JS nsIScriptTimeoutHandler implementation. 1.32 +class nsJSScriptTimeoutHandler MOZ_FINAL : public nsIScriptTimeoutHandler 1.33 +{ 1.34 +public: 1.35 + // nsISupports 1.36 + NS_DECL_CYCLE_COLLECTING_ISUPPORTS 1.37 + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsJSScriptTimeoutHandler) 1.38 + 1.39 + nsJSScriptTimeoutHandler(); 1.40 + // This will call SwapElements on aArguments with an empty array. 1.41 + nsJSScriptTimeoutHandler(nsGlobalWindow *aWindow, Function& aFunction, 1.42 + FallibleTArray<JS::Heap<JS::Value> >& aArguments, 1.43 + ErrorResult& aError); 1.44 + nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow, 1.45 + const nsAString& aExpression, bool* aAllowEval, 1.46 + ErrorResult& aError); 1.47 + ~nsJSScriptTimeoutHandler(); 1.48 + 1.49 + virtual const char16_t *GetHandlerText(); 1.50 + virtual Function* GetCallback() 1.51 + { 1.52 + return mFunction; 1.53 + } 1.54 + virtual void GetLocation(const char **aFileName, uint32_t *aLineNo) 1.55 + { 1.56 + *aFileName = mFileName.get(); 1.57 + *aLineNo = mLineNo; 1.58 + } 1.59 + 1.60 + virtual const nsTArray<JS::Value>& GetArgs() 1.61 + { 1.62 + return mArgs; 1.63 + } 1.64 + 1.65 + nsresult Init(nsGlobalWindow *aWindow, bool *aIsInterval, 1.66 + int32_t *aInterval, bool* aAllowEval); 1.67 + 1.68 + void ReleaseJSObjects(); 1.69 + 1.70 +private: 1.71 + // filename, line number and JS language version string of the 1.72 + // caller of setTimeout() 1.73 + nsCString mFileName; 1.74 + uint32_t mLineNo; 1.75 + nsTArray<JS::Heap<JS::Value> > mArgs; 1.76 + 1.77 + // The expression to evaluate or function to call. If mFunction is non-null 1.78 + // it should be used, else use mExpr. 1.79 + nsString mExpr; 1.80 + nsRefPtr<Function> mFunction; 1.81 +}; 1.82 + 1.83 + 1.84 +// nsJSScriptTimeoutHandler 1.85 +// QueryInterface implementation for nsJSScriptTimeoutHandler 1.86 +NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSScriptTimeoutHandler) 1.87 + 1.88 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSScriptTimeoutHandler) 1.89 + tmp->ReleaseJSObjects(); 1.90 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.91 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSScriptTimeoutHandler) 1.92 + if (MOZ_UNLIKELY(cb.WantDebugInfo())) { 1.93 + nsAutoCString name("nsJSScriptTimeoutHandler"); 1.94 + if (tmp->mFunction) { 1.95 + JSFunction* fun = 1.96 + JS_GetObjectFunction(js::UncheckedUnwrap(tmp->mFunction->Callable())); 1.97 + if (fun && JS_GetFunctionId(fun)) { 1.98 + JSFlatString *funId = JS_ASSERT_STRING_IS_FLAT(JS_GetFunctionId(fun)); 1.99 + size_t size = 1 + JS_PutEscapedFlatString(nullptr, 0, funId, 0); 1.100 + char *funIdName = new char[size]; 1.101 + if (funIdName) { 1.102 + JS_PutEscapedFlatString(funIdName, size, funId, 0); 1.103 + name.AppendLiteral(" ["); 1.104 + name.Append(funIdName); 1.105 + delete[] funIdName; 1.106 + name.AppendLiteral("]"); 1.107 + } 1.108 + } 1.109 + } else { 1.110 + name.AppendLiteral(" ["); 1.111 + name.Append(tmp->mFileName); 1.112 + name.AppendLiteral(":"); 1.113 + name.AppendInt(tmp->mLineNo); 1.114 + name.AppendLiteral("]"); 1.115 + } 1.116 + cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get()); 1.117 + } 1.118 + else { 1.119 + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsJSScriptTimeoutHandler, 1.120 + tmp->mRefCnt.get()) 1.121 + } 1.122 + 1.123 + if (tmp->mFunction) { 1.124 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFunction) 1.125 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS 1.126 + } 1.127 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.128 + 1.129 +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSScriptTimeoutHandler) 1.130 + for (uint32_t i = 0; i < tmp->mArgs.Length(); ++i) { 1.131 + NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mArgs[i]) 1.132 + } 1.133 +NS_IMPL_CYCLE_COLLECTION_TRACE_END 1.134 + 1.135 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSScriptTimeoutHandler) 1.136 + NS_INTERFACE_MAP_ENTRY(nsIScriptTimeoutHandler) 1.137 + NS_INTERFACE_MAP_ENTRY(nsISupports) 1.138 +NS_INTERFACE_MAP_END 1.139 + 1.140 +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler) 1.141 +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler) 1.142 + 1.143 +static bool 1.144 +CheckCSPForEval(JSContext* aCx, nsGlobalWindow* aWindow, ErrorResult& aError) 1.145 +{ 1.146 + // if CSP is enabled, and setTimeout/setInterval was called with a string, 1.147 + // disable the registration and log an error 1.148 + nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc(); 1.149 + if (!doc) { 1.150 + // if there's no document, we don't have to do anything. 1.151 + return true; 1.152 + } 1.153 + 1.154 + nsCOMPtr<nsIContentSecurityPolicy> csp; 1.155 + aError = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp)); 1.156 + if (aError.Failed()) { 1.157 + return false; 1.158 + } 1.159 + 1.160 + if (!csp) { 1.161 + return true; 1.162 + } 1.163 + 1.164 + bool allowsEval = true; 1.165 + bool reportViolation = false; 1.166 + aError = csp->GetAllowsEval(&reportViolation, &allowsEval); 1.167 + if (aError.Failed()) { 1.168 + return false; 1.169 + } 1.170 + 1.171 + if (reportViolation) { 1.172 + // TODO : need actual script sample in violation report. 1.173 + NS_NAMED_LITERAL_STRING(scriptSample, 1.174 + "call to eval() or related function blocked by CSP"); 1.175 + 1.176 + // Get the calling location. 1.177 + uint32_t lineNum = 0; 1.178 + const char *fileName; 1.179 + nsAutoString fileNameString; 1.180 + if (nsJSUtils::GetCallingLocation(aCx, &fileName, &lineNum)) { 1.181 + AppendUTF8toUTF16(fileName, fileNameString); 1.182 + } else { 1.183 + fileNameString.AssignLiteral("unknown"); 1.184 + } 1.185 + 1.186 + csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, 1.187 + fileNameString, scriptSample, lineNum, 1.188 + EmptyString(), EmptyString()); 1.189 + } 1.190 + 1.191 + return allowsEval; 1.192 +} 1.193 + 1.194 +nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler() : 1.195 + mLineNo(0) 1.196 +{ 1.197 +} 1.198 + 1.199 +nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(nsGlobalWindow *aWindow, 1.200 + Function& aFunction, 1.201 + FallibleTArray<JS::Heap<JS::Value> >& aArguments, 1.202 + ErrorResult& aError) : 1.203 + mLineNo(0), 1.204 + mFunction(&aFunction) 1.205 +{ 1.206 + if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) { 1.207 + // This window was already closed, or never properly initialized, 1.208 + // don't let a timer be scheduled on such a window. 1.209 + aError.Throw(NS_ERROR_NOT_INITIALIZED); 1.210 + return; 1.211 + } 1.212 + 1.213 + mozilla::HoldJSObjects(this); 1.214 + mArgs.SwapElements(aArguments); 1.215 +} 1.216 + 1.217 +nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, 1.218 + nsGlobalWindow *aWindow, 1.219 + const nsAString& aExpression, 1.220 + bool* aAllowEval, 1.221 + ErrorResult& aError) : 1.222 + mLineNo(0), 1.223 + mExpr(aExpression) 1.224 +{ 1.225 + if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) { 1.226 + // This window was already closed, or never properly initialized, 1.227 + // don't let a timer be scheduled on such a window. 1.228 + aError.Throw(NS_ERROR_NOT_INITIALIZED); 1.229 + return; 1.230 + } 1.231 + 1.232 + *aAllowEval = CheckCSPForEval(aCx, aWindow, aError); 1.233 + if (aError.Failed() || !*aAllowEval) { 1.234 + return; 1.235 + } 1.236 + 1.237 + // Get the calling location. 1.238 + const char *filename; 1.239 + if (nsJSUtils::GetCallingLocation(aCx, &filename, &mLineNo)) { 1.240 + mFileName.Assign(filename); 1.241 + } 1.242 +} 1.243 + 1.244 +nsJSScriptTimeoutHandler::~nsJSScriptTimeoutHandler() 1.245 +{ 1.246 + ReleaseJSObjects(); 1.247 +} 1.248 + 1.249 +void 1.250 +nsJSScriptTimeoutHandler::ReleaseJSObjects() 1.251 +{ 1.252 + if (mFunction) { 1.253 + mFunction = nullptr; 1.254 + mArgs.Clear(); 1.255 + mozilla::DropJSObjects(this); 1.256 + } 1.257 +} 1.258 + 1.259 +nsresult 1.260 +nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, bool *aIsInterval, 1.261 + int32_t *aInterval, bool *aAllowEval) 1.262 +{ 1.263 + if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) { 1.264 + // This window was already closed, or never properly initialized, 1.265 + // don't let a timer be scheduled on such a window. 1.266 + 1.267 + return NS_ERROR_NOT_INITIALIZED; 1.268 + } 1.269 + 1.270 + nsAXPCNativeCallContext *ncc = nullptr; 1.271 + nsresult rv = nsContentUtils::XPConnect()-> 1.272 + GetCurrentNativeCallContext(&ncc); 1.273 + NS_ENSURE_SUCCESS(rv, rv); 1.274 + 1.275 + if (!ncc) 1.276 + return NS_ERROR_NOT_AVAILABLE; 1.277 + 1.278 + JSContext *cx = nullptr; 1.279 + 1.280 + rv = ncc->GetJSContext(&cx); 1.281 + NS_ENSURE_SUCCESS(rv, rv); 1.282 + 1.283 + uint32_t argc; 1.284 + JS::Value *argv = nullptr; 1.285 + 1.286 + ncc->GetArgc(&argc); 1.287 + ncc->GetArgvPtr(&argv); 1.288 + 1.289 + JS::Rooted<JSFlatString*> expr(cx); 1.290 + JS::Rooted<JSObject*> funobj(cx); 1.291 + 1.292 + if (argc < 1) { 1.293 + ::JS_ReportError(cx, "Function %s requires at least 2 parameter", 1.294 + *aIsInterval ? kSetIntervalStr : kSetTimeoutStr); 1.295 + return NS_ERROR_DOM_TYPE_ERR; 1.296 + } 1.297 + 1.298 + int32_t interval = 0; 1.299 + if (argc > 1) { 1.300 + JS::Rooted<JS::Value> arg(cx, argv[1]); 1.301 + 1.302 + if (!JS::ToInt32(cx, arg, &interval)) { 1.303 + ::JS_ReportError(cx, 1.304 + "Second argument to %s must be a millisecond interval", 1.305 + aIsInterval ? kSetIntervalStr : kSetTimeoutStr); 1.306 + return NS_ERROR_DOM_TYPE_ERR; 1.307 + } 1.308 + } 1.309 + 1.310 + if (argc == 1) { 1.311 + // If no interval was specified, treat this like a timeout, to avoid 1.312 + // setting an interval of 0 milliseconds. 1.313 + *aIsInterval = false; 1.314 + } 1.315 + 1.316 + JS::Rooted<JS::Value> arg(cx, argv[0]); 1.317 + switch (::JS_TypeOfValue(cx, arg)) { 1.318 + case JSTYPE_FUNCTION: 1.319 + funobj = &arg.toObject(); 1.320 + break; 1.321 + 1.322 + case JSTYPE_STRING: 1.323 + case JSTYPE_OBJECT: 1.324 + { 1.325 + JSString *str = JS::ToString(cx, arg); 1.326 + if (!str) 1.327 + return NS_ERROR_OUT_OF_MEMORY; 1.328 + 1.329 + expr = ::JS_FlattenString(cx, str); 1.330 + if (!expr) 1.331 + return NS_ERROR_OUT_OF_MEMORY; 1.332 + 1.333 + argv[0] = JS::StringValue(str); 1.334 + } 1.335 + break; 1.336 + 1.337 + default: 1.338 + ::JS_ReportError(cx, "useless %s call (missing quotes around argument?)", 1.339 + *aIsInterval ? kSetIntervalStr : kSetTimeoutStr); 1.340 + 1.341 + // Return an error that nsGlobalWindow can recognize and turn into NS_OK. 1.342 + return NS_ERROR_DOM_TYPE_ERR; 1.343 + } 1.344 + 1.345 + if (expr) { 1.346 + // if CSP is enabled, and setTimeout/setInterval was called with a string, 1.347 + // disable the registration and log an error 1.348 + ErrorResult error; 1.349 + *aAllowEval = CheckCSPForEval(cx, aWindow, error); 1.350 + if (error.Failed() || !*aAllowEval) { 1.351 + return error.ErrorCode(); 1.352 + } 1.353 + 1.354 + mExpr.Append(JS_GetFlatStringChars(expr), 1.355 + JS_GetStringLength(JS_FORGET_STRING_FLATNESS(expr))); 1.356 + 1.357 + // Get the calling location. 1.358 + const char *filename; 1.359 + if (nsJSUtils::GetCallingLocation(cx, &filename, &mLineNo)) { 1.360 + mFileName.Assign(filename); 1.361 + } 1.362 + } else if (funobj) { 1.363 + *aAllowEval = true; 1.364 + 1.365 + mozilla::HoldJSObjects(this); 1.366 + 1.367 + mFunction = new Function(funobj, GetIncumbentGlobal()); 1.368 + 1.369 + // Create our arg array. argc is the number of arguments passed 1.370 + // to setTimeout or setInterval; the first two are our callback 1.371 + // and the delay, so only arguments after that need to go in our 1.372 + // array. 1.373 + // std::max(argc - 2, 0) wouldn't work right because argc is unsigned. 1.374 + uint32_t argCount = std::max(argc, 2u) - 2; 1.375 + 1.376 + FallibleTArray<JS::Heap<JS::Value> > args; 1.377 + if (!args.SetCapacity(argCount)) { 1.378 + // No need to drop here, since we already have a non-null mFunction 1.379 + return NS_ERROR_OUT_OF_MEMORY; 1.380 + } 1.381 + for (uint32_t idx = 0; idx < argCount; ++idx) { 1.382 + *args.AppendElement() = argv[idx + 2]; 1.383 + } 1.384 + args.SwapElements(mArgs); 1.385 + } else { 1.386 + NS_WARNING("No func and no expr - why are we here?"); 1.387 + } 1.388 + *aInterval = interval; 1.389 + return NS_OK; 1.390 +} 1.391 + 1.392 +const char16_t * 1.393 +nsJSScriptTimeoutHandler::GetHandlerText() 1.394 +{ 1.395 + NS_ASSERTION(!mFunction, "No expression, so no handler text!"); 1.396 + return mExpr.get(); 1.397 +} 1.398 + 1.399 +nsresult NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow, 1.400 + bool *aIsInterval, 1.401 + int32_t *aInterval, 1.402 + nsIScriptTimeoutHandler **aRet) 1.403 +{ 1.404 + *aRet = nullptr; 1.405 + nsRefPtr<nsJSScriptTimeoutHandler> handler = new nsJSScriptTimeoutHandler(); 1.406 + bool allowEval; 1.407 + nsresult rv = handler->Init(aWindow, aIsInterval, aInterval, &allowEval); 1.408 + if (NS_FAILED(rv) || !allowEval) { 1.409 + return rv; 1.410 + } 1.411 + 1.412 + handler.forget(aRet); 1.413 + 1.414 + return NS_OK; 1.415 +} 1.416 + 1.417 +already_AddRefed<nsIScriptTimeoutHandler> 1.418 +NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow, Function& aFunction, 1.419 + const Sequence<JS::Value>& aArguments, 1.420 + ErrorResult& aError) 1.421 +{ 1.422 + FallibleTArray<JS::Heap<JS::Value> > args; 1.423 + if (!args.AppendElements(aArguments)) { 1.424 + aError.Throw(NS_ERROR_OUT_OF_MEMORY); 1.425 + return 0; 1.426 + } 1.427 + 1.428 + nsRefPtr<nsJSScriptTimeoutHandler> handler = 1.429 + new nsJSScriptTimeoutHandler(aWindow, aFunction, args, aError); 1.430 + return aError.Failed() ? nullptr : handler.forget(); 1.431 +} 1.432 + 1.433 +already_AddRefed<nsIScriptTimeoutHandler> 1.434 +NS_CreateJSTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow, 1.435 + const nsAString& aExpression, ErrorResult& aError) 1.436 +{ 1.437 + ErrorResult rv; 1.438 + bool allowEval = false; 1.439 + nsRefPtr<nsJSScriptTimeoutHandler> handler = 1.440 + new nsJSScriptTimeoutHandler(aCx, aWindow, aExpression, &allowEval, rv); 1.441 + if (rv.Failed() || !allowEval) { 1.442 + return nullptr; 1.443 + } 1.444 + 1.445 + return handler.forget(); 1.446 +}