dom/base/nsJSTimeoutHandler.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 sw=2 et tw=78: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "nsCOMPtr.h"
michael@0 8 #include "nsIDocument.h"
michael@0 9 #include "nsIScriptTimeoutHandler.h"
michael@0 10 #include "nsIXPConnect.h"
michael@0 11 #include "nsJSUtils.h"
michael@0 12 #include "nsContentUtils.h"
michael@0 13 #include "nsError.h"
michael@0 14 #include "nsGlobalWindow.h"
michael@0 15 #include "nsIContentSecurityPolicy.h"
michael@0 16 #include "mozilla/Attributes.h"
michael@0 17 #include "mozilla/Likely.h"
michael@0 18 #include <algorithm>
michael@0 19 #include "mozilla/dom/FunctionBinding.h"
michael@0 20 #include "nsAXPCNativeCallContext.h"
michael@0 21
michael@0 22 static const char kSetIntervalStr[] = "setInterval";
michael@0 23 static const char kSetTimeoutStr[] = "setTimeout";
michael@0 24
michael@0 25 using namespace mozilla;
michael@0 26 using namespace mozilla::dom;
michael@0 27
michael@0 28 // Our JS nsIScriptTimeoutHandler implementation.
michael@0 29 class nsJSScriptTimeoutHandler MOZ_FINAL : public nsIScriptTimeoutHandler
michael@0 30 {
michael@0 31 public:
michael@0 32 // nsISupports
michael@0 33 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
michael@0 34 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsJSScriptTimeoutHandler)
michael@0 35
michael@0 36 nsJSScriptTimeoutHandler();
michael@0 37 // This will call SwapElements on aArguments with an empty array.
michael@0 38 nsJSScriptTimeoutHandler(nsGlobalWindow *aWindow, Function& aFunction,
michael@0 39 FallibleTArray<JS::Heap<JS::Value> >& aArguments,
michael@0 40 ErrorResult& aError);
michael@0 41 nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
michael@0 42 const nsAString& aExpression, bool* aAllowEval,
michael@0 43 ErrorResult& aError);
michael@0 44 ~nsJSScriptTimeoutHandler();
michael@0 45
michael@0 46 virtual const char16_t *GetHandlerText();
michael@0 47 virtual Function* GetCallback()
michael@0 48 {
michael@0 49 return mFunction;
michael@0 50 }
michael@0 51 virtual void GetLocation(const char **aFileName, uint32_t *aLineNo)
michael@0 52 {
michael@0 53 *aFileName = mFileName.get();
michael@0 54 *aLineNo = mLineNo;
michael@0 55 }
michael@0 56
michael@0 57 virtual const nsTArray<JS::Value>& GetArgs()
michael@0 58 {
michael@0 59 return mArgs;
michael@0 60 }
michael@0 61
michael@0 62 nsresult Init(nsGlobalWindow *aWindow, bool *aIsInterval,
michael@0 63 int32_t *aInterval, bool* aAllowEval);
michael@0 64
michael@0 65 void ReleaseJSObjects();
michael@0 66
michael@0 67 private:
michael@0 68 // filename, line number and JS language version string of the
michael@0 69 // caller of setTimeout()
michael@0 70 nsCString mFileName;
michael@0 71 uint32_t mLineNo;
michael@0 72 nsTArray<JS::Heap<JS::Value> > mArgs;
michael@0 73
michael@0 74 // The expression to evaluate or function to call. If mFunction is non-null
michael@0 75 // it should be used, else use mExpr.
michael@0 76 nsString mExpr;
michael@0 77 nsRefPtr<Function> mFunction;
michael@0 78 };
michael@0 79
michael@0 80
michael@0 81 // nsJSScriptTimeoutHandler
michael@0 82 // QueryInterface implementation for nsJSScriptTimeoutHandler
michael@0 83 NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSScriptTimeoutHandler)
michael@0 84
michael@0 85 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSScriptTimeoutHandler)
michael@0 86 tmp->ReleaseJSObjects();
michael@0 87 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
michael@0 88 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSScriptTimeoutHandler)
michael@0 89 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
michael@0 90 nsAutoCString name("nsJSScriptTimeoutHandler");
michael@0 91 if (tmp->mFunction) {
michael@0 92 JSFunction* fun =
michael@0 93 JS_GetObjectFunction(js::UncheckedUnwrap(tmp->mFunction->Callable()));
michael@0 94 if (fun && JS_GetFunctionId(fun)) {
michael@0 95 JSFlatString *funId = JS_ASSERT_STRING_IS_FLAT(JS_GetFunctionId(fun));
michael@0 96 size_t size = 1 + JS_PutEscapedFlatString(nullptr, 0, funId, 0);
michael@0 97 char *funIdName = new char[size];
michael@0 98 if (funIdName) {
michael@0 99 JS_PutEscapedFlatString(funIdName, size, funId, 0);
michael@0 100 name.AppendLiteral(" [");
michael@0 101 name.Append(funIdName);
michael@0 102 delete[] funIdName;
michael@0 103 name.AppendLiteral("]");
michael@0 104 }
michael@0 105 }
michael@0 106 } else {
michael@0 107 name.AppendLiteral(" [");
michael@0 108 name.Append(tmp->mFileName);
michael@0 109 name.AppendLiteral(":");
michael@0 110 name.AppendInt(tmp->mLineNo);
michael@0 111 name.AppendLiteral("]");
michael@0 112 }
michael@0 113 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get());
michael@0 114 }
michael@0 115 else {
michael@0 116 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsJSScriptTimeoutHandler,
michael@0 117 tmp->mRefCnt.get())
michael@0 118 }
michael@0 119
michael@0 120 if (tmp->mFunction) {
michael@0 121 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFunction)
michael@0 122 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
michael@0 123 }
michael@0 124 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
michael@0 125
michael@0 126 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSScriptTimeoutHandler)
michael@0 127 for (uint32_t i = 0; i < tmp->mArgs.Length(); ++i) {
michael@0 128 NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mArgs[i])
michael@0 129 }
michael@0 130 NS_IMPL_CYCLE_COLLECTION_TRACE_END
michael@0 131
michael@0 132 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSScriptTimeoutHandler)
michael@0 133 NS_INTERFACE_MAP_ENTRY(nsIScriptTimeoutHandler)
michael@0 134 NS_INTERFACE_MAP_ENTRY(nsISupports)
michael@0 135 NS_INTERFACE_MAP_END
michael@0 136
michael@0 137 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler)
michael@0 138 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler)
michael@0 139
michael@0 140 static bool
michael@0 141 CheckCSPForEval(JSContext* aCx, nsGlobalWindow* aWindow, ErrorResult& aError)
michael@0 142 {
michael@0 143 // if CSP is enabled, and setTimeout/setInterval was called with a string,
michael@0 144 // disable the registration and log an error
michael@0 145 nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
michael@0 146 if (!doc) {
michael@0 147 // if there's no document, we don't have to do anything.
michael@0 148 return true;
michael@0 149 }
michael@0 150
michael@0 151 nsCOMPtr<nsIContentSecurityPolicy> csp;
michael@0 152 aError = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp));
michael@0 153 if (aError.Failed()) {
michael@0 154 return false;
michael@0 155 }
michael@0 156
michael@0 157 if (!csp) {
michael@0 158 return true;
michael@0 159 }
michael@0 160
michael@0 161 bool allowsEval = true;
michael@0 162 bool reportViolation = false;
michael@0 163 aError = csp->GetAllowsEval(&reportViolation, &allowsEval);
michael@0 164 if (aError.Failed()) {
michael@0 165 return false;
michael@0 166 }
michael@0 167
michael@0 168 if (reportViolation) {
michael@0 169 // TODO : need actual script sample in violation report.
michael@0 170 NS_NAMED_LITERAL_STRING(scriptSample,
michael@0 171 "call to eval() or related function blocked by CSP");
michael@0 172
michael@0 173 // Get the calling location.
michael@0 174 uint32_t lineNum = 0;
michael@0 175 const char *fileName;
michael@0 176 nsAutoString fileNameString;
michael@0 177 if (nsJSUtils::GetCallingLocation(aCx, &fileName, &lineNum)) {
michael@0 178 AppendUTF8toUTF16(fileName, fileNameString);
michael@0 179 } else {
michael@0 180 fileNameString.AssignLiteral("unknown");
michael@0 181 }
michael@0 182
michael@0 183 csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
michael@0 184 fileNameString, scriptSample, lineNum,
michael@0 185 EmptyString(), EmptyString());
michael@0 186 }
michael@0 187
michael@0 188 return allowsEval;
michael@0 189 }
michael@0 190
michael@0 191 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler() :
michael@0 192 mLineNo(0)
michael@0 193 {
michael@0 194 }
michael@0 195
michael@0 196 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(nsGlobalWindow *aWindow,
michael@0 197 Function& aFunction,
michael@0 198 FallibleTArray<JS::Heap<JS::Value> >& aArguments,
michael@0 199 ErrorResult& aError) :
michael@0 200 mLineNo(0),
michael@0 201 mFunction(&aFunction)
michael@0 202 {
michael@0 203 if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
michael@0 204 // This window was already closed, or never properly initialized,
michael@0 205 // don't let a timer be scheduled on such a window.
michael@0 206 aError.Throw(NS_ERROR_NOT_INITIALIZED);
michael@0 207 return;
michael@0 208 }
michael@0 209
michael@0 210 mozilla::HoldJSObjects(this);
michael@0 211 mArgs.SwapElements(aArguments);
michael@0 212 }
michael@0 213
michael@0 214 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
michael@0 215 nsGlobalWindow *aWindow,
michael@0 216 const nsAString& aExpression,
michael@0 217 bool* aAllowEval,
michael@0 218 ErrorResult& aError) :
michael@0 219 mLineNo(0),
michael@0 220 mExpr(aExpression)
michael@0 221 {
michael@0 222 if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
michael@0 223 // This window was already closed, or never properly initialized,
michael@0 224 // don't let a timer be scheduled on such a window.
michael@0 225 aError.Throw(NS_ERROR_NOT_INITIALIZED);
michael@0 226 return;
michael@0 227 }
michael@0 228
michael@0 229 *aAllowEval = CheckCSPForEval(aCx, aWindow, aError);
michael@0 230 if (aError.Failed() || !*aAllowEval) {
michael@0 231 return;
michael@0 232 }
michael@0 233
michael@0 234 // Get the calling location.
michael@0 235 const char *filename;
michael@0 236 if (nsJSUtils::GetCallingLocation(aCx, &filename, &mLineNo)) {
michael@0 237 mFileName.Assign(filename);
michael@0 238 }
michael@0 239 }
michael@0 240
michael@0 241 nsJSScriptTimeoutHandler::~nsJSScriptTimeoutHandler()
michael@0 242 {
michael@0 243 ReleaseJSObjects();
michael@0 244 }
michael@0 245
michael@0 246 void
michael@0 247 nsJSScriptTimeoutHandler::ReleaseJSObjects()
michael@0 248 {
michael@0 249 if (mFunction) {
michael@0 250 mFunction = nullptr;
michael@0 251 mArgs.Clear();
michael@0 252 mozilla::DropJSObjects(this);
michael@0 253 }
michael@0 254 }
michael@0 255
michael@0 256 nsresult
michael@0 257 nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, bool *aIsInterval,
michael@0 258 int32_t *aInterval, bool *aAllowEval)
michael@0 259 {
michael@0 260 if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
michael@0 261 // This window was already closed, or never properly initialized,
michael@0 262 // don't let a timer be scheduled on such a window.
michael@0 263
michael@0 264 return NS_ERROR_NOT_INITIALIZED;
michael@0 265 }
michael@0 266
michael@0 267 nsAXPCNativeCallContext *ncc = nullptr;
michael@0 268 nsresult rv = nsContentUtils::XPConnect()->
michael@0 269 GetCurrentNativeCallContext(&ncc);
michael@0 270 NS_ENSURE_SUCCESS(rv, rv);
michael@0 271
michael@0 272 if (!ncc)
michael@0 273 return NS_ERROR_NOT_AVAILABLE;
michael@0 274
michael@0 275 JSContext *cx = nullptr;
michael@0 276
michael@0 277 rv = ncc->GetJSContext(&cx);
michael@0 278 NS_ENSURE_SUCCESS(rv, rv);
michael@0 279
michael@0 280 uint32_t argc;
michael@0 281 JS::Value *argv = nullptr;
michael@0 282
michael@0 283 ncc->GetArgc(&argc);
michael@0 284 ncc->GetArgvPtr(&argv);
michael@0 285
michael@0 286 JS::Rooted<JSFlatString*> expr(cx);
michael@0 287 JS::Rooted<JSObject*> funobj(cx);
michael@0 288
michael@0 289 if (argc < 1) {
michael@0 290 ::JS_ReportError(cx, "Function %s requires at least 2 parameter",
michael@0 291 *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
michael@0 292 return NS_ERROR_DOM_TYPE_ERR;
michael@0 293 }
michael@0 294
michael@0 295 int32_t interval = 0;
michael@0 296 if (argc > 1) {
michael@0 297 JS::Rooted<JS::Value> arg(cx, argv[1]);
michael@0 298
michael@0 299 if (!JS::ToInt32(cx, arg, &interval)) {
michael@0 300 ::JS_ReportError(cx,
michael@0 301 "Second argument to %s must be a millisecond interval",
michael@0 302 aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
michael@0 303 return NS_ERROR_DOM_TYPE_ERR;
michael@0 304 }
michael@0 305 }
michael@0 306
michael@0 307 if (argc == 1) {
michael@0 308 // If no interval was specified, treat this like a timeout, to avoid
michael@0 309 // setting an interval of 0 milliseconds.
michael@0 310 *aIsInterval = false;
michael@0 311 }
michael@0 312
michael@0 313 JS::Rooted<JS::Value> arg(cx, argv[0]);
michael@0 314 switch (::JS_TypeOfValue(cx, arg)) {
michael@0 315 case JSTYPE_FUNCTION:
michael@0 316 funobj = &arg.toObject();
michael@0 317 break;
michael@0 318
michael@0 319 case JSTYPE_STRING:
michael@0 320 case JSTYPE_OBJECT:
michael@0 321 {
michael@0 322 JSString *str = JS::ToString(cx, arg);
michael@0 323 if (!str)
michael@0 324 return NS_ERROR_OUT_OF_MEMORY;
michael@0 325
michael@0 326 expr = ::JS_FlattenString(cx, str);
michael@0 327 if (!expr)
michael@0 328 return NS_ERROR_OUT_OF_MEMORY;
michael@0 329
michael@0 330 argv[0] = JS::StringValue(str);
michael@0 331 }
michael@0 332 break;
michael@0 333
michael@0 334 default:
michael@0 335 ::JS_ReportError(cx, "useless %s call (missing quotes around argument?)",
michael@0 336 *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
michael@0 337
michael@0 338 // Return an error that nsGlobalWindow can recognize and turn into NS_OK.
michael@0 339 return NS_ERROR_DOM_TYPE_ERR;
michael@0 340 }
michael@0 341
michael@0 342 if (expr) {
michael@0 343 // if CSP is enabled, and setTimeout/setInterval was called with a string,
michael@0 344 // disable the registration and log an error
michael@0 345 ErrorResult error;
michael@0 346 *aAllowEval = CheckCSPForEval(cx, aWindow, error);
michael@0 347 if (error.Failed() || !*aAllowEval) {
michael@0 348 return error.ErrorCode();
michael@0 349 }
michael@0 350
michael@0 351 mExpr.Append(JS_GetFlatStringChars(expr),
michael@0 352 JS_GetStringLength(JS_FORGET_STRING_FLATNESS(expr)));
michael@0 353
michael@0 354 // Get the calling location.
michael@0 355 const char *filename;
michael@0 356 if (nsJSUtils::GetCallingLocation(cx, &filename, &mLineNo)) {
michael@0 357 mFileName.Assign(filename);
michael@0 358 }
michael@0 359 } else if (funobj) {
michael@0 360 *aAllowEval = true;
michael@0 361
michael@0 362 mozilla::HoldJSObjects(this);
michael@0 363
michael@0 364 mFunction = new Function(funobj, GetIncumbentGlobal());
michael@0 365
michael@0 366 // Create our arg array. argc is the number of arguments passed
michael@0 367 // to setTimeout or setInterval; the first two are our callback
michael@0 368 // and the delay, so only arguments after that need to go in our
michael@0 369 // array.
michael@0 370 // std::max(argc - 2, 0) wouldn't work right because argc is unsigned.
michael@0 371 uint32_t argCount = std::max(argc, 2u) - 2;
michael@0 372
michael@0 373 FallibleTArray<JS::Heap<JS::Value> > args;
michael@0 374 if (!args.SetCapacity(argCount)) {
michael@0 375 // No need to drop here, since we already have a non-null mFunction
michael@0 376 return NS_ERROR_OUT_OF_MEMORY;
michael@0 377 }
michael@0 378 for (uint32_t idx = 0; idx < argCount; ++idx) {
michael@0 379 *args.AppendElement() = argv[idx + 2];
michael@0 380 }
michael@0 381 args.SwapElements(mArgs);
michael@0 382 } else {
michael@0 383 NS_WARNING("No func and no expr - why are we here?");
michael@0 384 }
michael@0 385 *aInterval = interval;
michael@0 386 return NS_OK;
michael@0 387 }
michael@0 388
michael@0 389 const char16_t *
michael@0 390 nsJSScriptTimeoutHandler::GetHandlerText()
michael@0 391 {
michael@0 392 NS_ASSERTION(!mFunction, "No expression, so no handler text!");
michael@0 393 return mExpr.get();
michael@0 394 }
michael@0 395
michael@0 396 nsresult NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow,
michael@0 397 bool *aIsInterval,
michael@0 398 int32_t *aInterval,
michael@0 399 nsIScriptTimeoutHandler **aRet)
michael@0 400 {
michael@0 401 *aRet = nullptr;
michael@0 402 nsRefPtr<nsJSScriptTimeoutHandler> handler = new nsJSScriptTimeoutHandler();
michael@0 403 bool allowEval;
michael@0 404 nsresult rv = handler->Init(aWindow, aIsInterval, aInterval, &allowEval);
michael@0 405 if (NS_FAILED(rv) || !allowEval) {
michael@0 406 return rv;
michael@0 407 }
michael@0 408
michael@0 409 handler.forget(aRet);
michael@0 410
michael@0 411 return NS_OK;
michael@0 412 }
michael@0 413
michael@0 414 already_AddRefed<nsIScriptTimeoutHandler>
michael@0 415 NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow, Function& aFunction,
michael@0 416 const Sequence<JS::Value>& aArguments,
michael@0 417 ErrorResult& aError)
michael@0 418 {
michael@0 419 FallibleTArray<JS::Heap<JS::Value> > args;
michael@0 420 if (!args.AppendElements(aArguments)) {
michael@0 421 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
michael@0 422 return 0;
michael@0 423 }
michael@0 424
michael@0 425 nsRefPtr<nsJSScriptTimeoutHandler> handler =
michael@0 426 new nsJSScriptTimeoutHandler(aWindow, aFunction, args, aError);
michael@0 427 return aError.Failed() ? nullptr : handler.forget();
michael@0 428 }
michael@0 429
michael@0 430 already_AddRefed<nsIScriptTimeoutHandler>
michael@0 431 NS_CreateJSTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
michael@0 432 const nsAString& aExpression, ErrorResult& aError)
michael@0 433 {
michael@0 434 ErrorResult rv;
michael@0 435 bool allowEval = false;
michael@0 436 nsRefPtr<nsJSScriptTimeoutHandler> handler =
michael@0 437 new nsJSScriptTimeoutHandler(aCx, aWindow, aExpression, &allowEval, rv);
michael@0 438 if (rv.Failed() || !allowEval) {
michael@0 439 return nullptr;
michael@0 440 }
michael@0 441
michael@0 442 return handler.forget();
michael@0 443 }

mercurial