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