Bumping gaia.json for 8 gaia revision(s) a=gaia-bump
[gecko.git] / dom / base / nsJSTimeoutHandler.cpp
blob91db6b705bd07495072fdfaaaf00a9a8dad58379
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() MOZ_OVERRIDE;
46 virtual Function* GetCallback() MOZ_OVERRIDE
48 return mFunction;
50 virtual void GetLocation(const char** aFileName, uint32_t* aLineNo) MOZ_OVERRIDE
52 *aFileName = mFileName.get();
53 *aLineNo = mLineNo;
56 virtual const nsTArray<JS::Value>& GetArgs() MOZ_OVERRIDE
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 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());
186 return allowsEval;
189 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler() :
190 mLineNo(0)
194 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(nsGlobalWindow *aWindow,
195 Function& aFunction,
196 FallibleTArray<JS::Heap<JS::Value> >& aArguments,
197 ErrorResult& aError) :
198 mLineNo(0),
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);
205 return;
208 mozilla::HoldJSObjects(this);
209 mArgs.SwapElements(aArguments);
212 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
213 nsGlobalWindow *aWindow,
214 const nsAString& aExpression,
215 bool* aAllowEval,
216 ErrorResult& aError) :
217 mLineNo(0),
218 mExpr(aExpression)
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);
224 return;
227 *aAllowEval = CheckCSPForEval(aCx, aWindow, aError);
228 if (aError.Failed() || !*aAllowEval) {
229 return;
232 // Get the calling location.
233 nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo);
236 nsJSScriptTimeoutHandler::~nsJSScriptTimeoutHandler()
238 ReleaseJSObjects();
241 void
242 nsJSScriptTimeoutHandler::ReleaseJSObjects()
244 if (mFunction) {
245 mFunction = nullptr;
246 mArgs.Clear();
247 mozilla::DropJSObjects(this);
251 nsresult
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);
267 if (!ncc)
268 return NS_ERROR_NOT_AVAILABLE;
270 JSContext *cx = nullptr;
272 rv = ncc->GetJSContext(&cx);
273 NS_ENSURE_SUCCESS(rv, rv);
275 uint32_t argc;
276 JS::Value *argv = nullptr;
278 ncc->GetArgc(&argc);
279 ncc->GetArgvPtr(&argv);
281 JS::Rooted<JSFlatString*> expr(cx);
282 JS::Rooted<JSObject*> funobj(cx);
284 if (argc < 1) {
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;
291 if (argc > 1) {
292 JS::Rooted<JS::Value> arg(cx, argv[1]);
294 if (!JS::ToInt32(cx, arg, &interval)) {
295 ::JS_ReportError(cx,
296 "Second argument to %s must be a millisecond interval",
297 aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
298 return NS_ERROR_DOM_TYPE_ERR;
302 if (argc == 1) {
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();
312 break;
314 case JSTYPE_STRING:
315 case JSTYPE_OBJECT:
317 JSString *str = JS::ToString(cx, arg);
318 if (!str)
319 return NS_ERROR_OUT_OF_MEMORY;
321 expr = ::JS_FlattenString(cx, str);
322 if (!expr)
323 return NS_ERROR_OUT_OF_MEMORY;
325 argv[0] = JS::StringValue(str);
327 break;
329 default:
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;
337 if (expr) {
338 // if CSP is enabled, and setTimeout/setInterval was called with a string,
339 // disable the registration and log an error
340 ErrorResult 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);
351 } else if (funobj) {
352 *aAllowEval = true;
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
361 // array.
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);
374 } else {
375 NS_WARNING("No func and no expr - why are we here?");
377 *aInterval = interval;
378 return NS_OK;
381 const char16_t *
382 nsJSScriptTimeoutHandler::GetHandlerText()
384 NS_ASSERTION(!mFunction, "No expression, so no handler text!");
385 return mExpr.get();
388 nsresult NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow,
389 bool *aIsInterval,
390 int32_t *aInterval,
391 nsIScriptTimeoutHandler **aRet)
393 *aRet = nullptr;
394 nsRefPtr<nsJSScriptTimeoutHandler> handler = new nsJSScriptTimeoutHandler();
395 bool allowEval;
396 nsresult rv = handler->Init(aWindow, aIsInterval, aInterval, &allowEval);
397 if (NS_FAILED(rv) || !allowEval) {
398 return rv;
401 handler.forget(aRet);
403 return NS_OK;
406 already_AddRefed<nsIScriptTimeoutHandler>
407 NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow, Function& aFunction,
408 const Sequence<JS::Value>& aArguments,
409 ErrorResult& aError)
411 FallibleTArray<JS::Heap<JS::Value> > args;
412 if (!args.AppendElements(aArguments)) {
413 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
414 return nullptr;
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)
426 ErrorResult rv;
427 bool allowEval = false;
428 nsRefPtr<nsJSScriptTimeoutHandler> handler =
429 new nsJSScriptTimeoutHandler(aCx, aWindow, aExpression, &allowEval, rv);
430 if (rv.Failed() || !allowEval) {
431 return nullptr;
434 return handler.forget();