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/. */
8 #include "nsIDocument.h"
9 #include "nsIScriptTimeoutHandler.h"
10 #include "nsIXPConnect.h"
11 #include "nsJSUtils.h"
12 #include "nsContentUtils.h"
14 #include "nsGlobalWindow.h"
15 #include "nsIContentSecurityPolicy.h"
16 #include "mozilla/Attributes.h"
17 #include "mozilla/Likely.h"
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
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
,
41 nsJSScriptTimeoutHandler(JSContext
* aCx
, nsGlobalWindow
*aWindow
,
42 const nsAString
& aExpression
, bool* aAllowEval
,
45 virtual const char16_t
*GetHandlerText();
46 virtual Function
* GetCallback()
50 virtual void GetLocation(const char **aFileName
, uint32_t *aLineNo
)
52 *aFileName
= mFileName
.get();
56 virtual const nsTArray
<JS::Value
>& GetArgs()
61 nsresult
Init(nsGlobalWindow
*aWindow
, bool *aIsInterval
,
62 int32_t *aInterval
, bool* aAllowEval
);
64 void ReleaseJSObjects();
67 ~nsJSScriptTimeoutHandler();
69 // filename, line number and JS language version string of the
70 // caller of setTimeout()
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.
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");
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
];
100 JS_PutEscapedFlatString(funIdName
, size
, funId
, 0);
101 name
.AppendLiteral(" [");
102 name
.Append(funIdName
);
108 name
.AppendLiteral(" [");
109 name
.Append(tmp
->mFileName
);
111 name
.AppendInt(tmp
->mLineNo
);
114 cb
.DescribeRefCountedNode(tmp
->mRefCnt
.get(), name
.get());
117 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsJSScriptTimeoutHandler
,
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
)
138 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler
)
139 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler
)
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();
148 // if there's no document, we don't have to do anything.
152 nsCOMPtr
<nsIContentSecurityPolicy
> csp
;
153 aError
= doc
->NodePrincipal()->GetCsp(getter_AddRefs(csp
));
154 if (aError
.Failed()) {
162 bool allowsEval
= true;
163 bool reportViolation
= false;
164 aError
= csp
->GetAllowsEval(&reportViolation
, &allowsEval
);
165 if (aError
.Failed()) {
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
);
181 fileNameString
.AssignLiteral("unknown");
184 csp
->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL
,
185 fileNameString
, scriptSample
, lineNum
,
186 EmptyString(), EmptyString());
192 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler() :
197 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(nsGlobalWindow
*aWindow
,
199 FallibleTArray
<JS::Heap
<JS::Value
> >& aArguments
,
200 ErrorResult
& aError
) :
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
);
211 mozilla::HoldJSObjects(this);
212 mArgs
.SwapElements(aArguments
);
215 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext
* aCx
,
216 nsGlobalWindow
*aWindow
,
217 const nsAString
& aExpression
,
219 ErrorResult
& aError
) :
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
);
230 *aAllowEval
= CheckCSPForEval(aCx
, aWindow
, aError
);
231 if (aError
.Failed() || !*aAllowEval
) {
235 // Get the calling location.
236 const char *filename
;
237 if (nsJSUtils::GetCallingLocation(aCx
, &filename
, &mLineNo
)) {
238 mFileName
.Assign(filename
);
242 nsJSScriptTimeoutHandler::~nsJSScriptTimeoutHandler()
248 nsJSScriptTimeoutHandler::ReleaseJSObjects()
253 mozilla::DropJSObjects(this);
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
);
274 return NS_ERROR_NOT_AVAILABLE
;
276 JSContext
*cx
= nullptr;
278 rv
= ncc
->GetJSContext(&cx
);
279 NS_ENSURE_SUCCESS(rv
, rv
);
282 JS::Value
*argv
= nullptr;
285 ncc
->GetArgvPtr(&argv
);
287 JS::Rooted
<JSFlatString
*> expr(cx
);
288 JS::Rooted
<JSObject
*> funobj(cx
);
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;
298 JS::Rooted
<JS::Value
> arg(cx
, argv
[1]);
300 if (!JS::ToInt32(cx
, arg
, &interval
)) {
302 "Second argument to %s must be a millisecond interval",
303 aIsInterval
? kSetIntervalStr
: kSetTimeoutStr
);
304 return NS_ERROR_DOM_TYPE_ERR
;
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();
323 JSString
*str
= JS::ToString(cx
, arg
);
325 return NS_ERROR_OUT_OF_MEMORY
;
327 expr
= ::JS_FlattenString(cx
, str
);
329 return NS_ERROR_OUT_OF_MEMORY
;
331 argv
[0] = JS::StringValue(str
);
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
;
344 // if CSP is enabled, and setTimeout/setInterval was called with a string,
345 // disable the registration and log an 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
);
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
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
);
384 NS_WARNING("No func and no expr - why are we here?");
386 *aInterval
= interval
;
391 nsJSScriptTimeoutHandler::GetHandlerText()
393 NS_ASSERTION(!mFunction
, "No expression, so no handler text!");
397 nsresult
NS_CreateJSTimeoutHandler(nsGlobalWindow
*aWindow
,
400 nsIScriptTimeoutHandler
**aRet
)
403 nsRefPtr
<nsJSScriptTimeoutHandler
> handler
= new nsJSScriptTimeoutHandler();
405 nsresult rv
= handler
->Init(aWindow
, aIsInterval
, aInterval
, &allowEval
);
406 if (NS_FAILED(rv
) || !allowEval
) {
410 handler
.forget(aRet
);
415 already_AddRefed
<nsIScriptTimeoutHandler
>
416 NS_CreateJSTimeoutHandler(nsGlobalWindow
*aWindow
, Function
& aFunction
,
417 const Sequence
<JS::Value
>& aArguments
,
420 FallibleTArray
<JS::Heap
<JS::Value
> > args
;
421 if (!args
.AppendElements(aArguments
)) {
422 aError
.Throw(NS_ERROR_OUT_OF_MEMORY
);
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
)
436 bool allowEval
= false;
437 nsRefPtr
<nsJSScriptTimeoutHandler
> handler
=
438 new nsJSScriptTimeoutHandler(aCx
, aWindow
, aExpression
, &allowEval
, rv
);
439 if (rv
.Failed() || !allowEval
) {
443 return handler
.forget();