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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications Corporation.
20 : * Portions created by the Initial Developer are Copyright (C) 1998
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Mark Hammond <mhammond@skippinet.com.au>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsCOMPtr.h"
41 : #include "nsIScriptContext.h"
42 : #include "nsIArray.h"
43 : #include "nsIScriptTimeoutHandler.h"
44 : #include "nsIXPConnect.h"
45 : #include "nsIJSRuntimeService.h"
46 : #include "nsJSUtils.h"
47 : #include "nsDOMJSUtils.h"
48 : #include "nsContentUtils.h"
49 : #include "nsJSEnvironment.h"
50 : #include "nsServiceManagerUtils.h"
51 : #include "nsDOMError.h"
52 : #include "nsGlobalWindow.h"
53 : #include "nsIContentSecurityPolicy.h"
54 : #include "nsAlgorithm.h"
55 :
56 : static const char kSetIntervalStr[] = "setInterval";
57 : static const char kSetTimeoutStr[] = "setTimeout";
58 :
59 : // Our JS nsIScriptTimeoutHandler implementation.
60 : class nsJSScriptTimeoutHandler: public nsIScriptTimeoutHandler
61 : {
62 : public:
63 : // nsISupports
64 0 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
65 1464 : NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsJSScriptTimeoutHandler)
66 :
67 : nsJSScriptTimeoutHandler();
68 : ~nsJSScriptTimeoutHandler();
69 :
70 : virtual const PRUnichar *GetHandlerText();
71 0 : virtual JSObject *GetScriptObject() {
72 0 : return mFunObj;
73 : }
74 0 : virtual void GetLocation(const char **aFileName, PRUint32 *aLineNo) {
75 0 : *aFileName = mFileName.get();
76 0 : *aLineNo = mLineNo;
77 0 : }
78 :
79 0 : virtual PRUint32 GetScriptTypeID() {
80 0 : return nsIProgrammingLanguage::JAVASCRIPT;
81 : }
82 0 : virtual PRUint32 GetScriptVersion() {
83 0 : return mVersion;
84 : }
85 :
86 0 : virtual nsIArray *GetArgv() {
87 0 : return mArgv;
88 : }
89 :
90 : nsresult Init(nsGlobalWindow *aWindow, bool *aIsInterval,
91 : PRInt32 *aInterval);
92 :
93 : void ReleaseJSObjects();
94 :
95 : private:
96 :
97 : nsCOMPtr<nsIScriptContext> mContext;
98 :
99 : // filename, line number and JS language version string of the
100 : // caller of setTimeout()
101 : nsCString mFileName;
102 : PRUint32 mLineNo;
103 : PRUint32 mVersion;
104 : nsCOMPtr<nsIJSArgArray> mArgv;
105 :
106 : // The JS expression to evaluate or function to call, if !mExpr
107 : JSFlatString *mExpr;
108 : JSObject *mFunObj;
109 : };
110 :
111 :
112 : // nsJSScriptTimeoutHandler
113 : // QueryInterface implementation for nsJSScriptTimeoutHandler
114 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSScriptTimeoutHandler)
115 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSScriptTimeoutHandler)
116 0 : tmp->ReleaseJSObjects();
117 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
118 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSScriptTimeoutHandler)
119 0 : if (NS_UNLIKELY(cb.WantDebugInfo())) {
120 0 : nsCAutoString foo("nsJSScriptTimeoutHandler");
121 0 : if (tmp->mExpr) {
122 0 : foo.AppendLiteral(" [");
123 0 : foo.Append(tmp->mFileName);
124 0 : foo.AppendLiteral(":");
125 0 : foo.AppendInt(tmp->mLineNo);
126 0 : foo.AppendLiteral("]");
127 : }
128 0 : else if (tmp->mFunObj) {
129 0 : JSFunction* fun = JS_GetObjectFunction(tmp->mFunObj);
130 0 : if (fun && JS_GetFunctionId(fun)) {
131 0 : JSFlatString *funId = JS_ASSERT_STRING_IS_FLAT(JS_GetFunctionId(fun));
132 0 : size_t size = 1 + JS_PutEscapedFlatString(NULL, 0, funId, 0);
133 0 : char *name = new char[size];
134 0 : if (name) {
135 0 : JS_PutEscapedFlatString(name, size, funId, 0);
136 0 : foo.AppendLiteral(" [");
137 0 : foo.Append(name);
138 0 : delete[] name;
139 0 : foo.AppendLiteral("]");
140 : }
141 : }
142 : }
143 : cb.DescribeRefCountedNode(tmp->mRefCnt.get(),
144 0 : sizeof(nsJSScriptTimeoutHandler), foo.get());
145 : }
146 : else {
147 0 : NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsJSScriptTimeoutHandler,
148 : tmp->mRefCnt.get())
149 : }
150 :
151 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mContext)
152 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mArgv)
153 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
154 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
155 :
156 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSScriptTimeoutHandler)
157 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mExpr)
158 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mFunObj)
159 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
160 :
161 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSScriptTimeoutHandler)
162 0 : NS_INTERFACE_MAP_ENTRY(nsIScriptTimeoutHandler)
163 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
164 0 : NS_INTERFACE_MAP_END
165 :
166 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler)
167 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler)
168 :
169 0 : nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler() :
170 : mLineNo(0),
171 : mVersion(nsnull),
172 : mExpr(nsnull),
173 0 : mFunObj(nsnull)
174 : {
175 0 : }
176 :
177 0 : nsJSScriptTimeoutHandler::~nsJSScriptTimeoutHandler()
178 : {
179 0 : ReleaseJSObjects();
180 0 : }
181 :
182 : void
183 0 : nsJSScriptTimeoutHandler::ReleaseJSObjects()
184 : {
185 0 : if (mExpr || mFunObj) {
186 0 : if (mExpr) {
187 0 : NS_DROP_JS_OBJECTS(this, nsJSScriptTimeoutHandler);
188 0 : mExpr = nsnull;
189 0 : } else if (mFunObj) {
190 0 : NS_DROP_JS_OBJECTS(this, nsJSScriptTimeoutHandler);
191 0 : mFunObj = nsnull;
192 : } else {
193 0 : NS_WARNING("No func and no expr - roots may not have been removed");
194 : }
195 : }
196 0 : }
197 :
198 : nsresult
199 0 : nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, bool *aIsInterval,
200 : PRInt32 *aInterval)
201 : {
202 0 : mContext = aWindow->GetContextInternal();
203 0 : if (!mContext) {
204 : // This window was already closed, or never properly initialized,
205 : // don't let a timer be scheduled on such a window.
206 :
207 0 : return NS_ERROR_NOT_INITIALIZED;
208 : }
209 :
210 0 : nsAXPCNativeCallContext *ncc = nsnull;
211 0 : nsresult rv = nsContentUtils::XPConnect()->
212 0 : GetCurrentNativeCallContext(&ncc);
213 0 : NS_ENSURE_SUCCESS(rv, rv);
214 :
215 0 : if (!ncc)
216 0 : return NS_ERROR_NOT_AVAILABLE;
217 :
218 0 : JSContext *cx = nsnull;
219 :
220 0 : rv = ncc->GetJSContext(&cx);
221 0 : NS_ENSURE_SUCCESS(rv, rv);
222 :
223 : PRUint32 argc;
224 0 : jsval *argv = nsnull;
225 :
226 0 : ncc->GetArgc(&argc);
227 0 : ncc->GetArgvPtr(&argv);
228 :
229 0 : JSFlatString *expr = nsnull;
230 0 : JSObject *funobj = nsnull;
231 :
232 0 : JSAutoRequest ar(cx);
233 :
234 0 : if (argc < 1) {
235 : ::JS_ReportError(cx, "Function %s requires at least 2 parameter",
236 0 : *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
237 0 : return NS_ERROR_DOM_TYPE_ERR;
238 : }
239 :
240 0 : int32_t interval = 0;
241 0 : if (argc > 1 && !::JS_ValueToECMAInt32(cx, argv[1], &interval)) {
242 : ::JS_ReportError(cx,
243 : "Second argument to %s must be a millisecond interval",
244 0 : aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
245 0 : return NS_ERROR_DOM_TYPE_ERR;
246 : }
247 :
248 0 : if (argc == 1) {
249 : // If no interval was specified, treat this like a timeout, to avoid
250 : // setting an interval of 0 milliseconds.
251 0 : *aIsInterval = false;
252 : }
253 :
254 0 : switch (::JS_TypeOfValue(cx, argv[0])) {
255 : case JSTYPE_FUNCTION:
256 0 : funobj = JSVAL_TO_OBJECT(argv[0]);
257 0 : break;
258 :
259 : case JSTYPE_STRING:
260 : case JSTYPE_OBJECT:
261 : {
262 0 : JSString *str = ::JS_ValueToString(cx, argv[0]);
263 0 : if (!str)
264 0 : return NS_ERROR_OUT_OF_MEMORY;
265 :
266 0 : expr = ::JS_FlattenString(cx, str);
267 0 : if (!expr)
268 0 : return NS_ERROR_OUT_OF_MEMORY;
269 :
270 0 : argv[0] = STRING_TO_JSVAL(str);
271 : }
272 0 : break;
273 :
274 : default:
275 : ::JS_ReportError(cx, "useless %s call (missing quotes around argument?)",
276 0 : *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
277 :
278 : // Return an error that nsGlobalWindow can recognize and turn into NS_OK.
279 0 : return NS_ERROR_DOM_TYPE_ERR;
280 : }
281 :
282 0 : if (expr) {
283 : // if CSP is enabled, and setTimeout/setInterval was called with a string
284 : // or object, disable the registration and log an error
285 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(aWindow->GetExtantDocument());
286 :
287 0 : if (doc) {
288 0 : nsCOMPtr<nsIContentSecurityPolicy> csp;
289 0 : nsresult rv = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp));
290 0 : NS_ENSURE_SUCCESS(rv, rv);
291 :
292 0 : if (csp) {
293 : bool allowsEval;
294 : // this call will send violation reports as warranted (and return true if
295 : // reportOnly is set).
296 0 : rv = csp->GetAllowsEval(&allowsEval);
297 0 : NS_ENSURE_SUCCESS(rv, rv);
298 :
299 0 : if (!allowsEval) {
300 : ::JS_ReportError(cx, "call to %s blocked by CSP",
301 0 : *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
302 :
303 : // Note: Our only caller knows to turn NS_ERROR_DOM_TYPE_ERR into NS_OK.
304 0 : return NS_ERROR_DOM_TYPE_ERR;
305 : }
306 : }
307 : } // if there's no document, we don't have to do anything.
308 :
309 0 : rv = NS_HOLD_JS_OBJECTS(this, nsJSScriptTimeoutHandler);
310 0 : NS_ENSURE_SUCCESS(rv, rv);
311 :
312 0 : mExpr = expr;
313 :
314 : // Get the calling location.
315 : const char *filename;
316 0 : if (nsJSUtils::GetCallingLocation(cx, &filename, &mLineNo)) {
317 0 : mFileName.Assign(filename);
318 : }
319 0 : } else if (funobj) {
320 0 : rv = NS_HOLD_JS_OBJECTS(this, nsJSScriptTimeoutHandler);
321 0 : NS_ENSURE_SUCCESS(rv, rv);
322 :
323 0 : mFunObj = funobj;
324 :
325 : // Create our arg array. argc is the number of arguments passed
326 : // to setTimeout or setInterval; the first two are our callback
327 : // and the delay, so only arguments after that need to go in our
328 : // array.
329 0 : nsCOMPtr<nsIJSArgArray> array;
330 : // NS_MAX(argc - 2, 0) wouldn't work right because argc is unsigned.
331 0 : rv = NS_CreateJSArgv(cx, NS_MAX(argc, 2u) - 2, nsnull,
332 0 : getter_AddRefs(array));
333 0 : if (NS_FAILED(rv)) {
334 0 : return NS_ERROR_OUT_OF_MEMORY;
335 : }
336 :
337 : PRUint32 dummy;
338 0 : jsval *jsargv = nsnull;
339 0 : array->GetArgs(&dummy, reinterpret_cast<void **>(&jsargv));
340 :
341 : // jsargv might be null if we have argc <= 2
342 0 : if (jsargv) {
343 0 : for (PRInt32 i = 2; (PRUint32)i < argc; ++i) {
344 0 : jsargv[i - 2] = argv[i];
345 : }
346 : } else {
347 0 : NS_ASSERTION(argc <= 2, "Why do we have no jsargv when we have arguments?");
348 : }
349 0 : mArgv = array;
350 : } else {
351 0 : NS_WARNING("No func and no expr - why are we here?");
352 : }
353 0 : *aInterval = interval;
354 0 : return NS_OK;
355 : }
356 :
357 : const PRUnichar *
358 0 : nsJSScriptTimeoutHandler::GetHandlerText()
359 : {
360 0 : NS_ASSERTION(mExpr, "No expression, so no handler text!");
361 0 : return ::JS_GetFlatStringChars(mExpr);
362 : }
363 :
364 0 : nsresult NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow,
365 : bool *aIsInterval,
366 : PRInt32 *aInterval,
367 : nsIScriptTimeoutHandler **aRet)
368 : {
369 0 : *aRet = nsnull;
370 0 : nsJSScriptTimeoutHandler *handler = new nsJSScriptTimeoutHandler();
371 0 : if (!handler)
372 0 : return NS_ERROR_OUT_OF_MEMORY;
373 :
374 0 : nsresult rv = handler->Init(aWindow, aIsInterval, aInterval);
375 0 : if (NS_FAILED(rv)) {
376 0 : delete handler;
377 0 : return rv;
378 : }
379 :
380 0 : NS_ADDREF(*aRet = handler);
381 :
382 0 : return NS_OK;
383 4392 : }
|