dom/base/nsJSTimeoutHandler.cpp

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

mercurial