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() MOZ_OVERRIDE
;
46 virtual Function
* GetCallback() MOZ_OVERRIDE
50 virtual void GetLocation(const char** aFileName
, uint32_t* aLineNo
) MOZ_OVERRIDE
52 *aFileName
= mFileName
.get();
56 virtual const nsTArray
<JS::Value
>& GetArgs() MOZ_OVERRIDE
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 nsAutoString fileNameString
;
177 if (!nsJSUtils::GetCallingLocation(aCx
, fileNameString
, &lineNum
)) {
178 fileNameString
.AssignLiteral("unknown");
181 csp
->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL
,
182 fileNameString
, scriptSample
, lineNum
,
183 EmptyString(), EmptyString());
189 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler() :
194 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(nsGlobalWindow
*aWindow
,
196 FallibleTArray
<JS::Heap
<JS::Value
> >& aArguments
,
197 ErrorResult
& aError
) :
199 mFunction(&aFunction
)
201 if (!aWindow
->GetContextInternal() || !aWindow
->FastGetGlobalJSObject()) {
202 // This window was already closed, or never properly initialized,
203 // don't let a timer be scheduled on such a window.
204 aError
.Throw(NS_ERROR_NOT_INITIALIZED
);
208 mozilla::HoldJSObjects(this);
209 mArgs
.SwapElements(aArguments
);
212 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext
* aCx
,
213 nsGlobalWindow
*aWindow
,
214 const nsAString
& aExpression
,
216 ErrorResult
& aError
) :
220 if (!aWindow
->GetContextInternal() || !aWindow
->FastGetGlobalJSObject()) {
221 // This window was already closed, or never properly initialized,
222 // don't let a timer be scheduled on such a window.
223 aError
.Throw(NS_ERROR_NOT_INITIALIZED
);
227 *aAllowEval
= CheckCSPForEval(aCx
, aWindow
, aError
);
228 if (aError
.Failed() || !*aAllowEval
) {
232 // Get the calling location.
233 nsJSUtils::GetCallingLocation(aCx
, mFileName
, &mLineNo
);
236 nsJSScriptTimeoutHandler::~nsJSScriptTimeoutHandler()
242 nsJSScriptTimeoutHandler::ReleaseJSObjects()
247 mozilla::DropJSObjects(this);
252 nsJSScriptTimeoutHandler::Init(nsGlobalWindow
*aWindow
, bool *aIsInterval
,
253 int32_t *aInterval
, bool *aAllowEval
)
255 if (!aWindow
->GetContextInternal() || !aWindow
->FastGetGlobalJSObject()) {
256 // This window was already closed, or never properly initialized,
257 // don't let a timer be scheduled on such a window.
259 return NS_ERROR_NOT_INITIALIZED
;
262 nsAXPCNativeCallContext
*ncc
= nullptr;
263 nsresult rv
= nsContentUtils::XPConnect()->
264 GetCurrentNativeCallContext(&ncc
);
265 NS_ENSURE_SUCCESS(rv
, rv
);
268 return NS_ERROR_NOT_AVAILABLE
;
270 JSContext
*cx
= nullptr;
272 rv
= ncc
->GetJSContext(&cx
);
273 NS_ENSURE_SUCCESS(rv
, rv
);
276 JS::Value
*argv
= nullptr;
279 ncc
->GetArgvPtr(&argv
);
281 JS::Rooted
<JSFlatString
*> expr(cx
);
282 JS::Rooted
<JSObject
*> funobj(cx
);
285 ::JS_ReportError(cx
, "Function %s requires at least 2 parameter",
286 *aIsInterval
? kSetIntervalStr
: kSetTimeoutStr
);
287 return NS_ERROR_DOM_TYPE_ERR
;
290 int32_t interval
= 0;
292 JS::Rooted
<JS::Value
> arg(cx
, argv
[1]);
294 if (!JS::ToInt32(cx
, arg
, &interval
)) {
296 "Second argument to %s must be a millisecond interval",
297 aIsInterval
? kSetIntervalStr
: kSetTimeoutStr
);
298 return NS_ERROR_DOM_TYPE_ERR
;
303 // If no interval was specified, treat this like a timeout, to avoid
304 // setting an interval of 0 milliseconds.
305 *aIsInterval
= false;
308 JS::Rooted
<JS::Value
> arg(cx
, argv
[0]);
309 switch (::JS_TypeOfValue(cx
, arg
)) {
310 case JSTYPE_FUNCTION
:
311 funobj
= &arg
.toObject();
317 JSString
*str
= JS::ToString(cx
, arg
);
319 return NS_ERROR_OUT_OF_MEMORY
;
321 expr
= ::JS_FlattenString(cx
, str
);
323 return NS_ERROR_OUT_OF_MEMORY
;
325 argv
[0] = JS::StringValue(str
);
330 ::JS_ReportError(cx
, "useless %s call (missing quotes around argument?)",
331 *aIsInterval
? kSetIntervalStr
: kSetTimeoutStr
);
333 // Return an error that nsGlobalWindow can recognize and turn into NS_OK.
334 return NS_ERROR_DOM_TYPE_ERR
;
338 // if CSP is enabled, and setTimeout/setInterval was called with a string,
339 // disable the registration and log an error
341 *aAllowEval
= CheckCSPForEval(cx
, aWindow
, error
);
342 if (error
.Failed() || !*aAllowEval
) {
343 return error
.ErrorCode();
346 MOZ_ASSERT(mExpr
.IsEmpty());
347 AssignJSFlatString(mExpr
, expr
);
349 // Get the calling location.
350 nsJSUtils::GetCallingLocation(cx
, mFileName
, &mLineNo
);
354 mozilla::HoldJSObjects(this);
356 mFunction
= new Function(funobj
, GetIncumbentGlobal());
358 // Create our arg array. argc is the number of arguments passed
359 // to setTimeout or setInterval; the first two are our callback
360 // and the delay, so only arguments after that need to go in our
362 // std::max(argc - 2, 0) wouldn't work right because argc is unsigned.
363 uint32_t argCount
= std::max(argc
, 2u) - 2;
365 FallibleTArray
<JS::Heap
<JS::Value
> > args
;
366 if (!args
.SetCapacity(argCount
)) {
367 // No need to drop here, since we already have a non-null mFunction
368 return NS_ERROR_OUT_OF_MEMORY
;
370 for (uint32_t idx
= 0; idx
< argCount
; ++idx
) {
371 *args
.AppendElement() = argv
[idx
+ 2];
373 args
.SwapElements(mArgs
);
375 NS_WARNING("No func and no expr - why are we here?");
377 *aInterval
= interval
;
382 nsJSScriptTimeoutHandler::GetHandlerText()
384 NS_ASSERTION(!mFunction
, "No expression, so no handler text!");
388 nsresult
NS_CreateJSTimeoutHandler(nsGlobalWindow
*aWindow
,
391 nsIScriptTimeoutHandler
**aRet
)
394 nsRefPtr
<nsJSScriptTimeoutHandler
> handler
= new nsJSScriptTimeoutHandler();
396 nsresult rv
= handler
->Init(aWindow
, aIsInterval
, aInterval
, &allowEval
);
397 if (NS_FAILED(rv
) || !allowEval
) {
401 handler
.forget(aRet
);
406 already_AddRefed
<nsIScriptTimeoutHandler
>
407 NS_CreateJSTimeoutHandler(nsGlobalWindow
*aWindow
, Function
& aFunction
,
408 const Sequence
<JS::Value
>& aArguments
,
411 FallibleTArray
<JS::Heap
<JS::Value
> > args
;
412 if (!args
.AppendElements(aArguments
)) {
413 aError
.Throw(NS_ERROR_OUT_OF_MEMORY
);
417 nsRefPtr
<nsJSScriptTimeoutHandler
> handler
=
418 new nsJSScriptTimeoutHandler(aWindow
, aFunction
, args
, aError
);
419 return aError
.Failed() ? nullptr : handler
.forget();
422 already_AddRefed
<nsIScriptTimeoutHandler
>
423 NS_CreateJSTimeoutHandler(JSContext
* aCx
, nsGlobalWindow
*aWindow
,
424 const nsAString
& aExpression
, ErrorResult
& aError
)
427 bool allowEval
= false;
428 nsRefPtr
<nsJSScriptTimeoutHandler
> handler
=
429 new nsJSScriptTimeoutHandler(aCx
, aWindow
, aExpression
, &allowEval
, rv
);
430 if (rv
.Failed() || !allowEval
) {
434 return handler
.forget();