Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/fetch/FetchStream.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 "FetchStream.h"
8
#include "mozilla/dom/DOMException.h"
9
#include "mozilla/dom/WorkerCommon.h"
10
#include "mozilla/dom/WorkerPrivate.h"
11
#include "nsProxyRelease.h"
12
#include "nsStreamUtils.h"
13
14
0
#define FETCH_STREAM_FLAG 0
15
16
static NS_DEFINE_CID(kStreamTransportServiceCID,
17
                     NS_STREAMTRANSPORTSERVICE_CID);
18
19
namespace mozilla {
20
namespace dom {
21
22
class FetchStream::WorkerShutdown final : public WorkerControlRunnable
23
{
24
public:
25
  WorkerShutdown(WorkerPrivate* aWorkerPrivate, RefPtr<FetchStream> aStream)
26
    : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
27
    , mStream(aStream)
28
0
  {}
29
30
  bool
31
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
32
0
  {
33
0
    mStream->ReleaseObjects();
34
0
    return true;
35
0
  }
36
37
  // This runnable starts from a JS Thread. We need to disable a couple of
38
  // assertions overring the following methods.
39
40
  bool
41
  PreDispatch(WorkerPrivate* aWorkerPrivate) override
42
0
  {
43
0
    return true;
44
0
  }
45
46
  void
47
  PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
48
0
  {}
49
50
private:
51
  RefPtr<FetchStream> mStream;
52
};
53
54
NS_IMPL_ISUPPORTS(FetchStream, nsIInputStreamCallback, nsIObserver,
55
                  nsISupportsWeakReference)
56
57
/* static */ void
58
FetchStream::Create(JSContext* aCx, FetchStreamHolder* aStreamHolder,
59
                    nsIGlobalObject* aGlobal, nsIInputStream* aInputStream,
60
                    JS::MutableHandle<JSObject*> aStream, ErrorResult& aRv)
61
0
{
62
0
  MOZ_DIAGNOSTIC_ASSERT(aCx);
63
0
  MOZ_DIAGNOSTIC_ASSERT(aStreamHolder);
64
0
  MOZ_DIAGNOSTIC_ASSERT(aInputStream);
65
0
66
0
  RefPtr<FetchStream> stream =
67
0
    new FetchStream(aGlobal, aStreamHolder, aInputStream);
68
0
69
0
  if (NS_IsMainThread()) {
70
0
    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
71
0
    if (NS_WARN_IF(!os)) {
72
0
      aRv.Throw(NS_ERROR_FAILURE);
73
0
      return;
74
0
    }
75
0
76
0
    aRv = os->AddObserver(stream, DOM_WINDOW_DESTROYED_TOPIC, true);
77
0
    if (NS_WARN_IF(aRv.Failed())) {
78
0
      return;
79
0
    }
80
0
81
0
  } else {
82
0
    WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
83
0
    MOZ_ASSERT(workerPrivate);
84
0
85
0
    RefPtr<WeakWorkerRef> workerRef =
86
0
      WeakWorkerRef::Create(workerPrivate, [stream]() {
87
0
        stream->Close();
88
0
      });
89
0
90
0
    if (NS_WARN_IF(!workerRef)) {
91
0
      aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
92
0
      return;
93
0
    }
94
0
95
0
    // Note, this will create a ref-cycle between the holder and the stream.
96
0
    // The cycle is broken when the stream is closed or the worker begins
97
0
    // shutting down.
98
0
    stream->mWorkerRef = workerRef.forget();
99
0
  }
100
0
101
0
  if (!JS::HasReadableStreamCallbacks(aCx)) {
102
0
    JS::SetReadableStreamCallbacks(aCx,
103
0
                                   &FetchStream::RequestDataCallback,
104
0
                                   &FetchStream::WriteIntoReadRequestCallback,
105
0
                                   &FetchStream::CancelCallback,
106
0
                                   &FetchStream::ClosedCallback,
107
0
                                   &FetchStream::ErroredCallback,
108
0
                                   &FetchStream::FinalizeCallback);
109
0
  }
110
0
111
0
  JS::Rooted<JSObject*> body(aCx,
112
0
    JS::NewReadableExternalSourceStreamObject(aCx, stream, FETCH_STREAM_FLAG));
113
0
  if (!body) {
114
0
    aRv.StealExceptionFromJSContext(aCx);
115
0
    return;
116
0
  }
117
0
118
0
  // This will be released in FetchStream::FinalizeCallback().  We are
119
0
  // guaranteed the jsapi will call FinalizeCallback when ReadableStream
120
0
  // js object is finalized.
121
0
  NS_ADDREF(stream.get());
122
0
123
0
  aStream.set(body);
124
0
}
125
126
/* static */ void
127
FetchStream::RequestDataCallback(JSContext* aCx,
128
                                 JS::HandleObject aStream,
129
                                 void* aUnderlyingSource,
130
                                 uint8_t aFlags,
131
                                 size_t aDesiredSize)
132
0
{
133
0
  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
134
0
  MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
135
0
  MOZ_DIAGNOSTIC_ASSERT(JS::ReadableStreamIsDisturbed(aStream));
136
0
137
0
  RefPtr<FetchStream> stream = static_cast<FetchStream*>(aUnderlyingSource);
138
0
  stream->AssertIsOnOwningThread();
139
0
140
0
  MutexAutoLock lock(stream->mMutex);
141
0
142
0
  MOZ_DIAGNOSTIC_ASSERT(stream->mState == eInitializing ||
143
0
                        stream->mState == eWaiting ||
144
0
                        stream->mState == eChecking ||
145
0
                        stream->mState == eReading);
146
0
147
0
  if (stream->mState == eReading) {
148
0
    // We are already reading data.
149
0
    return;
150
0
  }
151
0
152
0
  if (stream->mState == eChecking) {
153
0
    // If we are looking for more data, there is nothing else we should do:
154
0
    // let's move this checking operation in a reading.
155
0
    MOZ_ASSERT(stream->mInputStream);
156
0
    stream->mState = eReading;
157
0
    return;
158
0
  }
159
0
160
0
  if (stream->mState == eInitializing) {
161
0
    // The stream has been used for the first time.
162
0
    stream->mStreamHolder->MarkAsRead();
163
0
  }
164
0
165
0
  stream->mState = eReading;
166
0
167
0
  if (!stream->mInputStream) {
168
0
    // This is the first use of the stream. Let's convert the
169
0
    // mOriginalInputStream into an nsIAsyncInputStream.
170
0
    MOZ_ASSERT(stream->mOriginalInputStream);
171
0
172
0
    nsCOMPtr<nsIAsyncInputStream> asyncStream;
173
0
    nsresult rv =
174
0
      NS_MakeAsyncNonBlockingInputStream(stream->mOriginalInputStream.forget(),
175
0
                                         getter_AddRefs(asyncStream));
176
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
177
0
      stream->ErrorPropagation(aCx, lock, aStream, rv);
178
0
      return;
179
0
    }
180
0
181
0
    stream->mInputStream = asyncStream;
182
0
    stream->mOriginalInputStream = nullptr;
183
0
  }
184
0
185
0
  MOZ_DIAGNOSTIC_ASSERT(stream->mInputStream);
186
0
  MOZ_DIAGNOSTIC_ASSERT(!stream->mOriginalInputStream);
187
0
188
0
  nsresult rv =
189
0
    stream->mInputStream->AsyncWait(stream, 0, 0,
190
0
                                    stream->mOwningEventTarget);
191
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
192
0
    stream->ErrorPropagation(aCx, lock, aStream, rv);
193
0
    return;
194
0
  }
195
0
196
0
  // All good.
197
0
}
198
199
/* static */ void
200
FetchStream::WriteIntoReadRequestCallback(JSContext* aCx,
201
                                          JS::HandleObject aStream,
202
                                          void* aUnderlyingSource,
203
                                          uint8_t aFlags, void* aBuffer,
204
                                          size_t aLength, size_t* aByteWritten)
205
0
{
206
0
  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
207
0
  MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
208
0
  MOZ_DIAGNOSTIC_ASSERT(aBuffer);
209
0
  MOZ_DIAGNOSTIC_ASSERT(aByteWritten);
210
0
211
0
  RefPtr<FetchStream> stream = static_cast<FetchStream*>(aUnderlyingSource);
212
0
  stream->AssertIsOnOwningThread();
213
0
214
0
  MutexAutoLock lock(stream->mMutex);
215
0
216
0
  MOZ_DIAGNOSTIC_ASSERT(stream->mInputStream);
217
0
  MOZ_DIAGNOSTIC_ASSERT(stream->mState == eWriting);
218
0
  stream->mState = eChecking;
219
0
220
0
  uint32_t written;
221
0
  nsresult rv =
222
0
    stream->mInputStream->Read(static_cast<char*>(aBuffer), aLength, &written);
223
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
224
0
    stream->ErrorPropagation(aCx, lock, aStream, rv);
225
0
    return;
226
0
  }
227
0
228
0
  *aByteWritten = written;
229
0
230
0
  if (written == 0) {
231
0
    stream->CloseAndReleaseObjects(aCx, lock, aStream);
232
0
    return;
233
0
  }
234
0
235
0
  rv = stream->mInputStream->AsyncWait(stream, 0, 0,
236
0
                                       stream->mOwningEventTarget);
237
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
238
0
    stream->ErrorPropagation(aCx, lock, aStream, rv);
239
0
    return;
240
0
  }
241
0
242
0
  // All good.
243
0
}
244
245
/* static */ JS::Value
246
FetchStream::CancelCallback(JSContext* aCx, JS::HandleObject aStream,
247
                            void* aUnderlyingSource, uint8_t aFlags,
248
                            JS::HandleValue aReason)
249
0
{
250
0
  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
251
0
  MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
252
0
253
0
  // This is safe because we created an extra reference in FetchStream::Create()
254
0
  // that won't be released until FetchStream::FinalizeCallback() is called.
255
0
  // We are guaranteed that won't happen until the js ReadableStream object
256
0
  // is finalized.
257
0
  FetchStream* stream = static_cast<FetchStream*>(aUnderlyingSource);
258
0
  stream->AssertIsOnOwningThread();
259
0
260
0
  if (stream->mState == eInitializing) {
261
0
    // The stream has been used for the first time.
262
0
    stream->mStreamHolder->MarkAsRead();
263
0
  }
264
0
265
0
  if (stream->mInputStream) {
266
0
    stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
267
0
  }
268
0
269
0
  // It could be that we don't have mInputStream yet, but we still have the
270
0
  // original stream. We need to close that too.
271
0
  if (stream->mOriginalInputStream) {
272
0
    MOZ_ASSERT(!stream->mInputStream);
273
0
    stream->mOriginalInputStream->Close();
274
0
  }
275
0
276
0
  stream->ReleaseObjects();
277
0
  return JS::UndefinedValue();
278
0
}
279
280
/* static */ void
281
FetchStream::ClosedCallback(JSContext* aCx, JS::HandleObject aStream,
282
                            void* aUnderlyingSource, uint8_t aFlags)
283
0
{
284
0
  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
285
0
  MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
286
0
}
287
288
/* static */ void
289
FetchStream::ErroredCallback(JSContext* aCx, JS::HandleObject aStream,
290
                             void* aUnderlyingSource, uint8_t aFlags,
291
                             JS::HandleValue aReason)
292
0
{
293
0
  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
294
0
  MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
295
0
296
0
  // This is safe because we created an extra reference in FetchStream::Create()
297
0
  // that won't be released until FetchStream::FinalizeCallback() is called.
298
0
  // We are guaranteed that won't happen until the js ReadableStream object
299
0
  // is finalized.
300
0
  FetchStream* stream = static_cast<FetchStream*>(aUnderlyingSource);
301
0
  stream->AssertIsOnOwningThread();
302
0
303
0
  if (stream->mState == eInitializing) {
304
0
    // The stream has been used for the first time.
305
0
    stream->mStreamHolder->MarkAsRead();
306
0
  }
307
0
308
0
  if (stream->mInputStream) {
309
0
    stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
310
0
  }
311
0
312
0
  stream->ReleaseObjects();
313
0
}
314
315
void
316
FetchStream::FinalizeCallback(void* aUnderlyingSource, uint8_t aFlags)
317
0
{
318
0
  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
319
0
  MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
320
0
321
0
  // This can be called in any thread.
322
0
323
0
  // This takes ownership of the ref created in FetchStream::Create().
324
0
  RefPtr<FetchStream> stream =
325
0
    dont_AddRef(static_cast<FetchStream*>(aUnderlyingSource));
326
0
327
0
  stream->ReleaseObjects();
328
0
}
329
330
FetchStream::FetchStream(nsIGlobalObject* aGlobal,
331
                         FetchStreamHolder* aStreamHolder,
332
                         nsIInputStream* aInputStream)
333
  : mMutex("FetchStream::mMutex")
334
  , mState(eInitializing)
335
  , mGlobal(aGlobal)
336
  , mStreamHolder(aStreamHolder)
337
  , mOwningEventTarget(aGlobal->EventTargetFor(TaskCategory::Other))
338
  , mOriginalInputStream(aInputStream)
339
0
{
340
0
  MOZ_DIAGNOSTIC_ASSERT(aInputStream);
341
0
  MOZ_DIAGNOSTIC_ASSERT(aStreamHolder);
342
0
}
343
344
FetchStream::~FetchStream()
345
0
{
346
0
}
347
348
void
349
FetchStream::ErrorPropagation(JSContext* aCx,
350
                              const MutexAutoLock& aProofOfLock,
351
                              JS::HandleObject aStream,
352
                              nsresult aError)
353
0
{
354
0
  AssertIsOnOwningThread();
355
0
356
0
  // Nothing to do.
357
0
  if (mState == eClosed) {
358
0
    return;
359
0
  }
360
0
361
0
  // Let's close the stream.
362
0
  if (aError == NS_BASE_STREAM_CLOSED) {
363
0
    CloseAndReleaseObjects(aCx, aProofOfLock, aStream);
364
0
    return;
365
0
  }
366
0
367
0
  // Let's use a generic error.
368
0
  RefPtr<DOMException> error = DOMException::Create(NS_ERROR_DOM_TYPE_ERR);
369
0
370
0
  JS::Rooted<JS::Value> errorValue(aCx);
371
0
  if (ToJSValue(aCx, error, &errorValue)) {
372
0
    MutexAutoUnlock unlock(mMutex);
373
0
    JS::ReadableStreamError(aCx, aStream, errorValue);
374
0
  }
375
0
376
0
  ReleaseObjects(aProofOfLock);
377
0
}
378
379
NS_IMETHODIMP
380
FetchStream::OnInputStreamReady(nsIAsyncInputStream* aStream)
381
0
{
382
0
  AssertIsOnOwningThread();
383
0
  MOZ_DIAGNOSTIC_ASSERT(aStream);
384
0
385
0
  MutexAutoLock lock(mMutex);
386
0
387
0
  // Already closed. We have nothing else to do here.
388
0
  if (mState == eClosed) {
389
0
    return NS_OK;
390
0
  }
391
0
392
0
  MOZ_DIAGNOSTIC_ASSERT(mInputStream);
393
0
  MOZ_DIAGNOSTIC_ASSERT(mState == eReading || mState == eChecking);
394
0
395
0
  AutoJSAPI jsapi;
396
0
  if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
397
0
    // Without JSContext we are not able to close the stream or to propagate the
398
0
    // error.
399
0
    return NS_ERROR_FAILURE;
400
0
  }
401
0
402
0
  JSContext* cx = jsapi.cx();
403
0
  JS::Rooted<JSObject*> stream(cx, mStreamHolder->ReadableStreamBody());
404
0
405
0
  uint64_t size = 0;
406
0
  nsresult rv = mInputStream->Available(&size);
407
0
  if (NS_SUCCEEDED(rv) && size == 0) {
408
0
    // In theory this should not happen. If size is 0, the stream should be
409
0
    // considered closed.
410
0
    rv = NS_BASE_STREAM_CLOSED;
411
0
  }
412
0
413
0
  // No warning for stream closed.
414
0
  if (rv == NS_BASE_STREAM_CLOSED || NS_WARN_IF(NS_FAILED(rv))) {
415
0
    ErrorPropagation(cx, lock, stream, rv);
416
0
    return NS_OK;
417
0
  }
418
0
419
0
  // This extra checking is completed. Let's wait for the next read request.
420
0
  if (mState == eChecking) {
421
0
    mState = eWaiting;
422
0
    return NS_OK;
423
0
  }
424
0
425
0
  mState = eWriting;
426
0
427
0
  {
428
0
    MutexAutoUnlock unlock(mMutex);
429
0
    JS::ReadableStreamUpdateDataAvailableFromSource(cx, stream, size);
430
0
  }
431
0
432
0
  // The WriteInto callback changes mState to eChecking.
433
0
  MOZ_DIAGNOSTIC_ASSERT(mState == eChecking);
434
0
435
0
  return NS_OK;
436
0
}
437
438
/* static */ nsresult
439
FetchStream::RetrieveInputStream(void* aUnderlyingReadableStreamSource,
440
                                 nsIInputStream** aInputStream)
441
0
{
442
0
  MOZ_ASSERT(aUnderlyingReadableStreamSource);
443
0
  MOZ_ASSERT(aInputStream);
444
0
445
0
  RefPtr<FetchStream> stream =
446
0
    static_cast<FetchStream*>(aUnderlyingReadableStreamSource);
447
0
  stream->AssertIsOnOwningThread();
448
0
449
0
  // if mOriginalInputStream is null, the reading already started. We don't want
450
0
  // to expose the internal inputStream.
451
0
  if (NS_WARN_IF(!stream->mOriginalInputStream)) {
452
0
    return NS_ERROR_DOM_INVALID_STATE_ERR;
453
0
  }
454
0
455
0
  nsCOMPtr<nsIInputStream> inputStream = stream->mOriginalInputStream;
456
0
  inputStream.forget(aInputStream);
457
0
  return NS_OK;
458
0
}
459
460
void
461
FetchStream::Close()
462
0
{
463
0
  AssertIsOnOwningThread();
464
0
465
0
  MutexAutoLock lock(mMutex);
466
0
467
0
  if (mState == eClosed) {
468
0
    return;
469
0
  }
470
0
471
0
  AutoJSAPI jsapi;
472
0
  if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
473
0
    ReleaseObjects(lock);
474
0
    return;
475
0
  }
476
0
477
0
  JSContext* cx = jsapi.cx();
478
0
  JS::Rooted<JSObject*> stream(cx, mStreamHolder->ReadableStreamBody());
479
0
  CloseAndReleaseObjects(cx, lock, stream);
480
0
}
481
482
void
483
FetchStream::CloseAndReleaseObjects(JSContext* aCx,
484
                                    const MutexAutoLock& aProofOfLock,
485
                                    JS::HandleObject aStream)
486
0
{
487
0
  AssertIsOnOwningThread();
488
0
  MOZ_DIAGNOSTIC_ASSERT(mState != eClosed);
489
0
490
0
  ReleaseObjects(aProofOfLock);
491
0
492
0
  MutexAutoUnlock unlock(mMutex);
493
0
  if (JS::ReadableStreamIsReadable(aStream)) {
494
0
    JS::ReadableStreamClose(aCx, aStream);
495
0
  }
496
0
}
497
498
void
499
FetchStream::ReleaseObjects()
500
0
{
501
0
  MutexAutoLock lock(mMutex);
502
0
  ReleaseObjects(lock);
503
0
}
504
505
void
506
FetchStream::ReleaseObjects(const MutexAutoLock& aProofOfLock)
507
0
{
508
0
  // This method can be called on 2 possible threads: the owning one and a JS
509
0
  // thread used to release resources. If we are on the JS thread, we need to
510
0
  // dispatch a runnable to go back to the owning thread in order to release
511
0
  // resources correctly.
512
0
513
0
  if (mState == eClosed) {
514
0
    // Already gone. Nothing to do.
515
0
    return;
516
0
  }
517
0
518
0
  mState = eClosed;
519
0
520
0
  if (!NS_IsMainThread() && !IsCurrentThreadRunningWorker()) {
521
0
    // Let's dispatch a WorkerControlRunnable if the owning thread is a worker.
522
0
    if (mWorkerRef) {
523
0
      RefPtr<WorkerShutdown> r =
524
0
        new WorkerShutdown(mWorkerRef->GetUnsafePrivate(), this);
525
0
      Unused << NS_WARN_IF(!r->Dispatch());
526
0
      return;
527
0
    }
528
0
529
0
    // A normal runnable of the owning thread is the main-thread.
530
0
    RefPtr<FetchStream> self = this;
531
0
    RefPtr<Runnable> r =
532
0
      NS_NewRunnableFunction("FetchStream::ReleaseObjects",
533
0
                             [self] () { self->ReleaseObjects(); });
534
0
    mOwningEventTarget->Dispatch(r.forget());
535
0
    return;
536
0
  }
537
0
538
0
  AssertIsOnOwningThread();
539
0
540
0
  if (NS_IsMainThread()) {
541
0
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
542
0
    if (obs) {
543
0
      obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
544
0
    }
545
0
  }
546
0
547
0
  mWorkerRef = nullptr;
548
0
  mGlobal = nullptr;
549
0
550
0
  mStreamHolder->NullifyStream();
551
0
  mStreamHolder = nullptr;
552
0
}
553
554
#ifdef DEBUG
555
void
556
FetchStream::AssertIsOnOwningThread()
557
{
558
  NS_ASSERT_OWNINGTHREAD(FetchStream);
559
}
560
#endif
561
562
// nsIObserver
563
// -----------
564
565
NS_IMETHODIMP
566
FetchStream::Observe(nsISupports* aSubject, const char* aTopic,
567
                     const char16_t* aData)
568
0
{
569
0
  AssertIsOnMainThread();
570
0
  AssertIsOnOwningThread();
571
0
572
0
  MOZ_ASSERT(strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0);
573
0
574
0
  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
575
0
  if (SameCOMIdentity(aSubject, window)) {
576
0
    Close();
577
0
  }
578
0
579
0
  return NS_OK;
580
0
}
581
582
} // dom namespace
583
} // mozilla namespace