/src/mozilla-central/dom/workers/WorkerDebugger.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
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 "WorkerDebugger.h" |
8 | | |
9 | | #include "mozilla/dom/MessageEvent.h" |
10 | | #include "mozilla/dom/MessageEventBinding.h" |
11 | | #include "nsProxyRelease.h" |
12 | | #include "nsQueryObject.h" |
13 | | #include "nsThreadUtils.h" |
14 | | #include "ScriptLoader.h" |
15 | | #include "WorkerCommon.h" |
16 | | #include "WorkerError.h" |
17 | | #include "WorkerPrivate.h" |
18 | | #include "WorkerRunnable.h" |
19 | | #include "WorkerScope.h" |
20 | | #if defined(XP_WIN) |
21 | | #include <processthreadsapi.h> // for GetCurrentProcessId() |
22 | | #else |
23 | | #include <unistd.h> // for getpid() |
24 | | #endif // defined(XP_WIN) |
25 | | |
26 | | namespace mozilla { |
27 | | namespace dom { |
28 | | |
29 | | namespace { |
30 | | |
31 | | class DebuggerMessageEventRunnable : public WorkerDebuggerRunnable |
32 | | { |
33 | | nsString mMessage; |
34 | | |
35 | | public: |
36 | | DebuggerMessageEventRunnable(WorkerPrivate* aWorkerPrivate, |
37 | | const nsAString& aMessage) |
38 | | : WorkerDebuggerRunnable(aWorkerPrivate), |
39 | | mMessage(aMessage) |
40 | 0 | { |
41 | 0 | } |
42 | | |
43 | | private: |
44 | | virtual bool |
45 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
46 | 0 | { |
47 | 0 | WorkerDebuggerGlobalScope* globalScope = aWorkerPrivate->DebuggerGlobalScope(); |
48 | 0 | MOZ_ASSERT(globalScope); |
49 | 0 |
|
50 | 0 | JS::Rooted<JSString*> message(aCx, JS_NewUCStringCopyN(aCx, mMessage.get(), |
51 | 0 | mMessage.Length())); |
52 | 0 | if (!message) { |
53 | 0 | return false; |
54 | 0 | } |
55 | 0 | JS::Rooted<JS::Value> data(aCx, JS::StringValue(message)); |
56 | 0 |
|
57 | 0 | RefPtr<MessageEvent> event = new MessageEvent(globalScope, nullptr, |
58 | 0 | nullptr); |
59 | 0 | event->InitMessageEvent(nullptr, |
60 | 0 | NS_LITERAL_STRING("message"), |
61 | 0 | CanBubble::eNo, |
62 | 0 | Cancelable::eYes, |
63 | 0 | data, |
64 | 0 | EmptyString(), |
65 | 0 | EmptyString(), |
66 | 0 | nullptr, |
67 | 0 | Sequence<OwningNonNull<MessagePort>>()); |
68 | 0 | event->SetTrusted(true); |
69 | 0 |
|
70 | 0 | globalScope->DispatchEvent(*event); |
71 | 0 | return true; |
72 | 0 | } |
73 | | }; |
74 | | |
75 | | class CompileDebuggerScriptRunnable final : public WorkerDebuggerRunnable |
76 | | { |
77 | | nsString mScriptURL; |
78 | | |
79 | | public: |
80 | | CompileDebuggerScriptRunnable(WorkerPrivate* aWorkerPrivate, |
81 | | const nsAString& aScriptURL) |
82 | | : WorkerDebuggerRunnable(aWorkerPrivate), |
83 | | mScriptURL(aScriptURL) |
84 | 0 | { } |
85 | | |
86 | | private: |
87 | | virtual bool |
88 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
89 | 0 | { |
90 | 0 | aWorkerPrivate->AssertIsOnWorkerThread(); |
91 | 0 |
|
92 | 0 | WorkerDebuggerGlobalScope* globalScope = |
93 | 0 | aWorkerPrivate->CreateDebuggerGlobalScope(aCx); |
94 | 0 | if (!globalScope) { |
95 | 0 | NS_WARNING("Failed to make global!"); |
96 | 0 | return false; |
97 | 0 | } |
98 | 0 |
|
99 | 0 | if (NS_WARN_IF(!aWorkerPrivate->EnsureClientSource())) { |
100 | 0 | return false; |
101 | 0 | } |
102 | 0 | |
103 | 0 | JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper()); |
104 | 0 |
|
105 | 0 | ErrorResult rv; |
106 | 0 | JSAutoRealm ar(aCx, global); |
107 | 0 | workerinternals::LoadMainScript(aWorkerPrivate, mScriptURL, |
108 | 0 | DebuggerScript, rv); |
109 | 0 | rv.WouldReportJSException(); |
110 | 0 | // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still |
111 | 0 | // return false and don't SetWorkerScriptExecutedSuccessfully() in that |
112 | 0 | // case, but don't throw anything on aCx. The idea is to not dispatch error |
113 | 0 | // events if our load is canceled with that error code. |
114 | 0 | if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) { |
115 | 0 | rv.SuppressException(); |
116 | 0 | return false; |
117 | 0 | } |
118 | 0 | // Make sure to propagate exceptions from rv onto aCx, so that they will get |
119 | 0 | // reported after we return. We do this for all failures on rv, because now |
120 | 0 | // we're using rv to track all the state we care about. |
121 | 0 | if (rv.MaybeSetPendingException(aCx)) { |
122 | 0 | return false; |
123 | 0 | } |
124 | 0 | |
125 | 0 | return true; |
126 | 0 | } |
127 | | }; |
128 | | |
129 | | } // anonymous |
130 | | |
131 | | class WorkerDebugger::PostDebuggerMessageRunnable final : public Runnable |
132 | | { |
133 | | WorkerDebugger *mDebugger; |
134 | | nsString mMessage; |
135 | | |
136 | | public: |
137 | | PostDebuggerMessageRunnable(WorkerDebugger* aDebugger, |
138 | | const nsAString& aMessage) |
139 | | : mozilla::Runnable("PostDebuggerMessageRunnable") |
140 | | , mDebugger(aDebugger) |
141 | | , mMessage(aMessage) |
142 | 0 | { |
143 | 0 | } |
144 | | |
145 | | private: |
146 | | ~PostDebuggerMessageRunnable() |
147 | 0 | { } |
148 | | |
149 | | NS_IMETHOD |
150 | | Run() override |
151 | 0 | { |
152 | 0 | mDebugger->PostMessageToDebuggerOnMainThread(mMessage); |
153 | 0 |
|
154 | 0 | return NS_OK; |
155 | 0 | } |
156 | | }; |
157 | | |
158 | | class WorkerDebugger::ReportDebuggerErrorRunnable final : public Runnable |
159 | | { |
160 | | WorkerDebugger *mDebugger; |
161 | | nsString mFilename; |
162 | | uint32_t mLineno; |
163 | | nsString mMessage; |
164 | | |
165 | | public: |
166 | | ReportDebuggerErrorRunnable(WorkerDebugger* aDebugger, |
167 | | const nsAString& aFilename, |
168 | | uint32_t aLineno, |
169 | | const nsAString& aMessage) |
170 | | : Runnable("ReportDebuggerErrorRunnable") |
171 | | , mDebugger(aDebugger) |
172 | | , mFilename(aFilename) |
173 | | , mLineno(aLineno) |
174 | | , mMessage(aMessage) |
175 | 0 | { |
176 | 0 | } |
177 | | |
178 | | private: |
179 | | ~ReportDebuggerErrorRunnable() |
180 | 0 | { } |
181 | | |
182 | | NS_IMETHOD |
183 | | Run() override |
184 | 0 | { |
185 | 0 | mDebugger->ReportErrorToDebuggerOnMainThread(mFilename, mLineno, mMessage); |
186 | 0 |
|
187 | 0 | return NS_OK; |
188 | 0 | } |
189 | | }; |
190 | | |
191 | | WorkerDebugger::WorkerDebugger(WorkerPrivate* aWorkerPrivate) |
192 | | : mWorkerPrivate(aWorkerPrivate), |
193 | | mIsInitialized(false) |
194 | 0 | { |
195 | 0 | AssertIsOnMainThread(); |
196 | 0 | } |
197 | | |
198 | | WorkerDebugger::~WorkerDebugger() |
199 | 0 | { |
200 | 0 | MOZ_ASSERT(!mWorkerPrivate); |
201 | 0 |
|
202 | 0 | if (!NS_IsMainThread()) { |
203 | 0 | for (size_t index = 0; index < mListeners.Length(); ++index) { |
204 | 0 | NS_ReleaseOnMainThreadSystemGroup( |
205 | 0 | "WorkerDebugger::mListeners", mListeners[index].forget()); |
206 | 0 | } |
207 | 0 | } |
208 | 0 | } |
209 | | |
210 | | NS_IMPL_ISUPPORTS(WorkerDebugger, nsIWorkerDebugger) |
211 | | |
212 | | NS_IMETHODIMP |
213 | | WorkerDebugger::GetIsClosed(bool* aResult) |
214 | 0 | { |
215 | 0 | AssertIsOnMainThread(); |
216 | 0 |
|
217 | 0 | *aResult = !mWorkerPrivate; |
218 | 0 | return NS_OK; |
219 | 0 | } |
220 | | |
221 | | NS_IMETHODIMP |
222 | | WorkerDebugger::GetIsChrome(bool* aResult) |
223 | 0 | { |
224 | 0 | AssertIsOnMainThread(); |
225 | 0 |
|
226 | 0 | if (!mWorkerPrivate) { |
227 | 0 | return NS_ERROR_UNEXPECTED; |
228 | 0 | } |
229 | 0 | |
230 | 0 | *aResult = mWorkerPrivate->IsChromeWorker(); |
231 | 0 | return NS_OK; |
232 | 0 | } |
233 | | |
234 | | NS_IMETHODIMP |
235 | | WorkerDebugger::GetIsInitialized(bool* aResult) |
236 | 0 | { |
237 | 0 | AssertIsOnMainThread(); |
238 | 0 |
|
239 | 0 | if (!mWorkerPrivate) { |
240 | 0 | return NS_ERROR_UNEXPECTED; |
241 | 0 | } |
242 | 0 | |
243 | 0 | *aResult = mIsInitialized; |
244 | 0 | return NS_OK; |
245 | 0 | } |
246 | | |
247 | | NS_IMETHODIMP |
248 | | WorkerDebugger::GetParent(nsIWorkerDebugger** aResult) |
249 | 0 | { |
250 | 0 | AssertIsOnMainThread(); |
251 | 0 |
|
252 | 0 | if (!mWorkerPrivate) { |
253 | 0 | return NS_ERROR_UNEXPECTED; |
254 | 0 | } |
255 | 0 | |
256 | 0 | WorkerPrivate* parent = mWorkerPrivate->GetParent(); |
257 | 0 | if (!parent) { |
258 | 0 | *aResult = nullptr; |
259 | 0 | return NS_OK; |
260 | 0 | } |
261 | 0 | |
262 | 0 | MOZ_ASSERT(mWorkerPrivate->IsDedicatedWorker()); |
263 | 0 |
|
264 | 0 | nsCOMPtr<nsIWorkerDebugger> debugger = parent->Debugger(); |
265 | 0 | debugger.forget(aResult); |
266 | 0 | return NS_OK; |
267 | 0 | } |
268 | | |
269 | | NS_IMETHODIMP |
270 | | WorkerDebugger::GetType(uint32_t* aResult) |
271 | 0 | { |
272 | 0 | AssertIsOnMainThread(); |
273 | 0 |
|
274 | 0 | if (!mWorkerPrivate) { |
275 | 0 | return NS_ERROR_UNEXPECTED; |
276 | 0 | } |
277 | 0 | |
278 | 0 | *aResult = mWorkerPrivate->Type(); |
279 | 0 | return NS_OK; |
280 | 0 | } |
281 | | |
282 | | NS_IMETHODIMP |
283 | | WorkerDebugger::GetUrl(nsAString& aResult) |
284 | 0 | { |
285 | 0 | AssertIsOnMainThread(); |
286 | 0 |
|
287 | 0 | if (!mWorkerPrivate) { |
288 | 0 | return NS_ERROR_UNEXPECTED; |
289 | 0 | } |
290 | 0 | |
291 | 0 | aResult = mWorkerPrivate->ScriptURL(); |
292 | 0 | return NS_OK; |
293 | 0 | } |
294 | | |
295 | | NS_IMETHODIMP |
296 | | WorkerDebugger::GetWindow(mozIDOMWindow** aResult) |
297 | 0 | { |
298 | 0 | AssertIsOnMainThread(); |
299 | 0 |
|
300 | 0 | if (!mWorkerPrivate) { |
301 | 0 | return NS_ERROR_UNEXPECTED; |
302 | 0 | } |
303 | 0 | |
304 | 0 | if (mWorkerPrivate->GetParent() || !mWorkerPrivate->IsDedicatedWorker()) { |
305 | 0 | *aResult = nullptr; |
306 | 0 | return NS_OK; |
307 | 0 | } |
308 | 0 | |
309 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = mWorkerPrivate->GetWindow(); |
310 | 0 | window.forget(aResult); |
311 | 0 | return NS_OK; |
312 | 0 | } |
313 | | |
314 | | NS_IMETHODIMP |
315 | | WorkerDebugger::GetPrincipal(nsIPrincipal** aResult) |
316 | 0 | { |
317 | 0 | AssertIsOnMainThread(); |
318 | 0 | MOZ_ASSERT(aResult); |
319 | 0 |
|
320 | 0 | if (!mWorkerPrivate) { |
321 | 0 | return NS_ERROR_UNEXPECTED; |
322 | 0 | } |
323 | 0 | |
324 | 0 | nsCOMPtr<nsIPrincipal> prin = mWorkerPrivate->GetPrincipal(); |
325 | 0 | prin.forget(aResult); |
326 | 0 |
|
327 | 0 | return NS_OK; |
328 | 0 | } |
329 | | |
330 | | NS_IMETHODIMP |
331 | | WorkerDebugger::GetServiceWorkerID(uint32_t* aResult) |
332 | 0 | { |
333 | 0 | AssertIsOnMainThread(); |
334 | 0 | MOZ_ASSERT(aResult); |
335 | 0 |
|
336 | 0 | if (!mWorkerPrivate || !mWorkerPrivate->IsServiceWorker()) { |
337 | 0 | return NS_ERROR_UNEXPECTED; |
338 | 0 | } |
339 | 0 | |
340 | 0 | *aResult = mWorkerPrivate->ServiceWorkerID(); |
341 | 0 | return NS_OK; |
342 | 0 | } |
343 | | |
344 | | NS_IMETHODIMP |
345 | | WorkerDebugger::Initialize(const nsAString& aURL) |
346 | 0 | { |
347 | 0 | AssertIsOnMainThread(); |
348 | 0 |
|
349 | 0 | if (!mWorkerPrivate) { |
350 | 0 | return NS_ERROR_UNEXPECTED; |
351 | 0 | } |
352 | 0 | |
353 | 0 | if (!mIsInitialized) { |
354 | 0 | RefPtr<CompileDebuggerScriptRunnable> runnable = |
355 | 0 | new CompileDebuggerScriptRunnable(mWorkerPrivate, aURL); |
356 | 0 | if (!runnable->Dispatch()) { |
357 | 0 | return NS_ERROR_FAILURE; |
358 | 0 | } |
359 | 0 | |
360 | 0 | mIsInitialized = true; |
361 | 0 | } |
362 | 0 |
|
363 | 0 | return NS_OK; |
364 | 0 | } |
365 | | |
366 | | NS_IMETHODIMP |
367 | | WorkerDebugger::PostMessageMoz(const nsAString& aMessage) |
368 | 0 | { |
369 | 0 | AssertIsOnMainThread(); |
370 | 0 |
|
371 | 0 | if (!mWorkerPrivate || !mIsInitialized) { |
372 | 0 | return NS_ERROR_UNEXPECTED; |
373 | 0 | } |
374 | 0 | |
375 | 0 | RefPtr<DebuggerMessageEventRunnable> runnable = |
376 | 0 | new DebuggerMessageEventRunnable(mWorkerPrivate, aMessage); |
377 | 0 | if (!runnable->Dispatch()) { |
378 | 0 | return NS_ERROR_FAILURE; |
379 | 0 | } |
380 | 0 | |
381 | 0 | return NS_OK; |
382 | 0 | } |
383 | | |
384 | | NS_IMETHODIMP |
385 | | WorkerDebugger::AddListener(nsIWorkerDebuggerListener* aListener) |
386 | 0 | { |
387 | 0 | AssertIsOnMainThread(); |
388 | 0 |
|
389 | 0 | if (mListeners.Contains(aListener)) { |
390 | 0 | return NS_ERROR_INVALID_ARG; |
391 | 0 | } |
392 | 0 | |
393 | 0 | mListeners.AppendElement(aListener); |
394 | 0 | return NS_OK; |
395 | 0 | } |
396 | | |
397 | | NS_IMETHODIMP |
398 | | WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener* aListener) |
399 | 0 | { |
400 | 0 | AssertIsOnMainThread(); |
401 | 0 |
|
402 | 0 | if (!mListeners.Contains(aListener)) { |
403 | 0 | return NS_ERROR_INVALID_ARG; |
404 | 0 | } |
405 | 0 | |
406 | 0 | mListeners.RemoveElement(aListener); |
407 | 0 | return NS_OK; |
408 | 0 | } |
409 | | |
410 | | void |
411 | | WorkerDebugger::Close() |
412 | 0 | { |
413 | 0 | MOZ_ASSERT(mWorkerPrivate); |
414 | 0 | mWorkerPrivate = nullptr; |
415 | 0 |
|
416 | 0 | nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners); |
417 | 0 | for (size_t index = 0; index < listeners.Length(); ++index) { |
418 | 0 | listeners[index]->OnClose(); |
419 | 0 | } |
420 | 0 | } |
421 | | |
422 | | void |
423 | | WorkerDebugger::PostMessageToDebugger(const nsAString& aMessage) |
424 | 0 | { |
425 | 0 | mWorkerPrivate->AssertIsOnWorkerThread(); |
426 | 0 |
|
427 | 0 | RefPtr<PostDebuggerMessageRunnable> runnable = |
428 | 0 | new PostDebuggerMessageRunnable(this, aMessage); |
429 | 0 | if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) { |
430 | 0 | NS_WARNING("Failed to post message to debugger on main thread!"); |
431 | 0 | } |
432 | 0 | } |
433 | | |
434 | | void |
435 | | WorkerDebugger::PostMessageToDebuggerOnMainThread(const nsAString& aMessage) |
436 | 0 | { |
437 | 0 | AssertIsOnMainThread(); |
438 | 0 |
|
439 | 0 | nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners); |
440 | 0 | for (size_t index = 0; index < listeners.Length(); ++index) { |
441 | 0 | listeners[index]->OnMessage(aMessage); |
442 | 0 | } |
443 | 0 | } |
444 | | |
445 | | void |
446 | | WorkerDebugger::ReportErrorToDebugger(const nsAString& aFilename, |
447 | | uint32_t aLineno, |
448 | | const nsAString& aMessage) |
449 | 0 | { |
450 | 0 | mWorkerPrivate->AssertIsOnWorkerThread(); |
451 | 0 |
|
452 | 0 | RefPtr<ReportDebuggerErrorRunnable> runnable = |
453 | 0 | new ReportDebuggerErrorRunnable(this, aFilename, aLineno, aMessage); |
454 | 0 | if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) { |
455 | 0 | NS_WARNING("Failed to report error to debugger on main thread!"); |
456 | 0 | } |
457 | 0 | } |
458 | | |
459 | | void |
460 | | WorkerDebugger::ReportErrorToDebuggerOnMainThread(const nsAString& aFilename, |
461 | | uint32_t aLineno, |
462 | | const nsAString& aMessage) |
463 | 0 | { |
464 | 0 | AssertIsOnMainThread(); |
465 | 0 |
|
466 | 0 | nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners); |
467 | 0 | for (size_t index = 0; index < listeners.Length(); ++index) { |
468 | 0 | listeners[index]->OnError(aFilename, aLineno, aMessage); |
469 | 0 | } |
470 | 0 |
|
471 | 0 | WorkerErrorReport report; |
472 | 0 | report.mMessage = aMessage; |
473 | 0 | report.mFilename = aFilename; |
474 | 0 | WorkerErrorReport::LogErrorToConsole(report, 0); |
475 | 0 | } |
476 | | |
477 | | PerformanceInfo |
478 | | WorkerDebugger::ReportPerformanceInfo() |
479 | 0 | { |
480 | 0 | AssertIsOnMainThread(); |
481 | 0 |
|
482 | | #if defined(XP_WIN) |
483 | | uint32_t pid = GetCurrentProcessId(); |
484 | | #else |
485 | | uint32_t pid = getpid(); |
486 | 0 | #endif |
487 | 0 | bool isTopLevel= false; |
488 | 0 | uint64_t windowID = mWorkerPrivate->WindowID(); |
489 | 0 |
|
490 | 0 | // Walk up to our containing page and its window |
491 | 0 | WorkerPrivate* wp = mWorkerPrivate; |
492 | 0 | while (wp->GetParent()) { |
493 | 0 | wp = wp->GetParent(); |
494 | 0 | } |
495 | 0 | nsPIDOMWindowInner* win = wp->GetWindow(); |
496 | 0 | if (win) { |
497 | 0 | nsPIDOMWindowOuter* outer = win->GetOuterWindow(); |
498 | 0 | if (outer) { |
499 | 0 | nsCOMPtr<nsPIDOMWindowOuter> top = outer->GetTop(); |
500 | 0 | if (top) { |
501 | 0 | windowID = top->WindowID(); |
502 | 0 | isTopLevel = outer->IsTopLevelWindow(); |
503 | 0 | } |
504 | 0 | } |
505 | 0 | } |
506 | 0 |
|
507 | 0 | // getting the worker URL |
508 | 0 | RefPtr<nsIURI> scriptURI = mWorkerPrivate->GetResolvedScriptURI(); |
509 | 0 | nsCString url = scriptURI->GetSpecOrDefault(); |
510 | 0 |
|
511 | 0 | // Workers only produce metrics for a single category - DispatchCategory::Worker. |
512 | 0 | // We still return an array of CategoryDispatch so the PerformanceInfo |
513 | 0 | // struct is common to all performance counters throughout Firefox. |
514 | 0 | FallibleTArray<CategoryDispatch> items; |
515 | 0 | uint64_t duration = 0; |
516 | 0 | uint16_t count = 0; |
517 | 0 | uint64_t perfId = 0; |
518 | 0 |
|
519 | 0 | RefPtr<PerformanceCounter> perf = mWorkerPrivate->GetPerformanceCounter(); |
520 | 0 | if (perf) { |
521 | 0 | perfId = perf->GetID(); |
522 | 0 | count = perf->GetTotalDispatchCount(); |
523 | 0 | duration = perf->GetExecutionDuration(); |
524 | 0 | CategoryDispatch item = CategoryDispatch(DispatchCategory::Worker.GetValue(), count); |
525 | 0 | if (!items.AppendElement(item, fallible)) { |
526 | 0 | NS_ERROR("Could not complete the operation"); |
527 | 0 | return PerformanceInfo(url, pid, windowID, duration, perfId, true, isTopLevel, items); |
528 | 0 | } |
529 | 0 | } |
530 | 0 |
|
531 | 0 | return PerformanceInfo(url, pid, windowID, duration, perfId, true, isTopLevel, items); |
532 | 0 | } |
533 | | |
534 | | } // dom namespace |
535 | | } // mozilla namespace |