Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/DOMRequest.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 file,
5
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "DOMRequest.h"
8
9
#include "DOMException.h"
10
#include "nsThreadUtils.h"
11
#include "mozilla/ErrorResult.h"
12
#include "mozilla/dom/Event.h"
13
#include "mozilla/dom/Promise.h"
14
#include "mozilla/dom/ScriptSettings.h"
15
#include "jsfriendapi.h"
16
#include "nsContentUtils.h"
17
18
using mozilla::dom::AnyCallback;
19
using mozilla::dom::DOMException;
20
using mozilla::dom::DOMRequest;
21
using mozilla::dom::DOMRequestService;
22
using mozilla::dom::Promise;
23
using mozilla::dom::AutoJSAPI;
24
using mozilla::dom::RootingCx;
25
26
DOMRequest::DOMRequest(nsPIDOMWindowInner* aWindow)
27
  : DOMEventTargetHelper(aWindow)
28
  , mResult(JS::UndefinedValue())
29
  , mDone(false)
30
0
{
31
0
}
32
33
DOMRequest::DOMRequest(nsIGlobalObject* aGlobal)
34
  : DOMEventTargetHelper(aGlobal)
35
  , mResult(JS::UndefinedValue())
36
  , mDone(false)
37
0
{
38
0
}
39
40
DOMRequest::~DOMRequest()
41
0
{
42
0
  mResult.setUndefined();
43
0
  mozilla::DropJSObjects(this);
44
0
}
45
46
NS_IMPL_CYCLE_COLLECTION_CLASS(DOMRequest)
47
48
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMRequest,
49
0
                                                  DOMEventTargetHelper)
50
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
51
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
52
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
53
54
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMRequest,
55
0
                                                DOMEventTargetHelper)
56
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
57
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
58
0
  tmp->mResult.setUndefined();
59
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
60
61
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DOMRequest,
62
0
                                               DOMEventTargetHelper)
63
0
  // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
64
0
  // DOMEventTargetHelper does it for us.
65
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResult)
66
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
67
68
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMRequest)
69
0
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
70
71
NS_IMPL_ADDREF_INHERITED(DOMRequest, DOMEventTargetHelper)
72
NS_IMPL_RELEASE_INHERITED(DOMRequest, DOMEventTargetHelper)
73
74
/* virtual */ JSObject*
75
DOMRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
76
0
{
77
0
  return DOMRequest_Binding::Wrap(aCx, this, aGivenProto);
78
0
}
79
80
void
81
DOMRequest::FireSuccess(JS::Handle<JS::Value> aResult)
82
0
{
83
0
  NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
84
0
  NS_ASSERTION(!mError, "mError shouldn't have been set!");
85
0
  NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!");
86
0
87
0
  mDone = true;
88
0
  if (aResult.isGCThing()) {
89
0
    RootResultVal();
90
0
  }
91
0
  mResult = aResult;
92
0
93
0
  FireEvent(NS_LITERAL_STRING("success"), false, false);
94
0
95
0
  if (mPromise) {
96
0
    mPromise->MaybeResolve(mResult);
97
0
  }
98
0
}
99
100
void
101
DOMRequest::FireError(const nsAString& aError)
102
0
{
103
0
  NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
104
0
  NS_ASSERTION(!mError, "mError shouldn't have been set!");
105
0
  NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!");
106
0
107
0
  mDone = true;
108
0
  // XXX Error code chosen arbitrarily
109
0
  mError = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR,
110
0
                                NS_ConvertUTF16toUTF8(aError));
111
0
112
0
  FireEvent(NS_LITERAL_STRING("error"), true, true);
113
0
114
0
  if (mPromise) {
115
0
    mPromise->MaybeRejectBrokenly(mError);
116
0
  }
117
0
}
118
119
void
120
DOMRequest::FireError(nsresult aError)
121
0
{
122
0
  NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
123
0
  NS_ASSERTION(!mError, "mError shouldn't have been set!");
124
0
  NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!");
125
0
126
0
  mDone = true;
127
0
  mError = DOMException::Create(aError);
128
0
129
0
  FireEvent(NS_LITERAL_STRING("error"), true, true);
130
0
131
0
  if (mPromise) {
132
0
    mPromise->MaybeRejectBrokenly(mError);
133
0
  }
134
0
}
135
136
void
137
DOMRequest::FireDetailedError(DOMException& aError)
138
0
{
139
0
  NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!");
140
0
  NS_ASSERTION(!mError, "mError shouldn't have been set!");
141
0
  NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!");
142
0
143
0
  mDone = true;
144
0
  mError = &aError;
145
0
146
0
  FireEvent(NS_LITERAL_STRING("error"), true, true);
147
0
148
0
  if (mPromise) {
149
0
    mPromise->MaybeRejectBrokenly(mError);
150
0
  }
151
0
}
152
153
void
154
DOMRequest::FireEvent(const nsAString& aType, bool aBubble, bool aCancelable)
155
0
{
156
0
  if (NS_FAILED(CheckInnerWindowCorrectness())) {
157
0
    return;
158
0
  }
159
0
160
0
  RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
161
0
  event->InitEvent(aType, aBubble, aCancelable);
162
0
  event->SetTrusted(true);
163
0
164
0
  DispatchEvent(*event);
165
0
}
166
167
void
168
DOMRequest::RootResultVal()
169
0
{
170
0
  mozilla::HoldJSObjects(this);
171
0
}
172
173
void
174
DOMRequest::Then(JSContext* aCx, AnyCallback* aResolveCallback,
175
                 AnyCallback* aRejectCallback,
176
                 JS::MutableHandle<JS::Value> aRetval,
177
                 mozilla::ErrorResult& aRv)
178
0
{
179
0
  if (!mPromise) {
180
0
    mPromise = Promise::Create(DOMEventTargetHelper::GetParentObject(), aRv);
181
0
    if (aRv.Failed()) {
182
0
      return;
183
0
    }
184
0
    if (mDone) {
185
0
      // Since we create mPromise lazily, it's possible that the DOMRequest object
186
0
      // has already fired its success/error event.  In that case we should
187
0
      // manually resolve/reject mPromise here.  mPromise will take care of
188
0
      // calling the callbacks on |promise| as needed.
189
0
      if (mError) {
190
0
        mPromise->MaybeRejectBrokenly(mError);
191
0
      } else {
192
0
        mPromise->MaybeResolve(mResult);
193
0
      }
194
0
    }
195
0
  }
196
0
197
0
  // Just use the global of the Promise itself as the callee global.
198
0
  JS::Rooted<JSObject*> global(aCx, mPromise->PromiseObj());
199
0
  global = JS::GetNonCCWObjectGlobal(global);
200
0
  mPromise->Then(aCx, global, aResolveCallback, aRejectCallback, aRetval, aRv);
201
0
}
202
203
NS_IMPL_ISUPPORTS(DOMRequestService, nsIDOMRequestService)
204
205
NS_IMETHODIMP
206
DOMRequestService::CreateRequest(mozIDOMWindow* aWindow,
207
                                 DOMRequest** aRequest)
208
0
{
209
0
  MOZ_ASSERT(NS_IsMainThread());
210
0
  NS_ENSURE_STATE(aWindow);
211
0
  auto* win = nsPIDOMWindowInner::From(aWindow);
212
0
  RefPtr<DOMRequest> req = new DOMRequest(win);
213
0
  req.forget(aRequest);
214
0
215
0
  return NS_OK;
216
0
}
217
218
NS_IMETHODIMP
219
DOMRequestService::FireSuccess(DOMRequest* aRequest,
220
                               JS::Handle<JS::Value> aResult)
221
0
{
222
0
  NS_ENSURE_STATE(aRequest);
223
0
  aRequest->FireSuccess(aResult);
224
0
225
0
  return NS_OK;
226
0
}
227
228
NS_IMETHODIMP
229
DOMRequestService::FireError(DOMRequest* aRequest,
230
                             const nsAString& aError)
231
0
{
232
0
  NS_ENSURE_STATE(aRequest);
233
0
  aRequest->FireError(aError);
234
0
235
0
  return NS_OK;
236
0
}
237
238
class FireSuccessAsyncTask : public mozilla::Runnable
239
{
240
241
  FireSuccessAsyncTask(DOMRequest* aRequest, const JS::Value& aResult)
242
    : mozilla::Runnable("FireSuccessAsyncTask")
243
    , mReq(aRequest)
244
    , mResult(RootingCx(), aResult)
245
0
  {
246
0
  }
247
248
public:
249
250
  // Due to the fact that initialization can fail during shutdown (since we
251
  // can't fetch a js context), set up an initiatization function to make sure
252
  // we can return the failure appropriately
253
  static nsresult
254
  Dispatch(DOMRequest* aRequest,
255
           const JS::Value& aResult)
256
0
  {
257
0
    RefPtr<FireSuccessAsyncTask> asyncTask =
258
0
      new FireSuccessAsyncTask(aRequest, aResult);
259
0
    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(asyncTask));
260
0
    return NS_OK;
261
0
  }
262
263
  NS_IMETHOD
264
  Run() override
265
0
  {
266
0
    mReq->FireSuccess(JS::Handle<JS::Value>::fromMarkedLocation(mResult.address()));
267
0
    return NS_OK;
268
0
  }
269
270
private:
271
  RefPtr<DOMRequest> mReq;
272
  JS::PersistentRooted<JS::Value> mResult;
273
};
274
275
class FireErrorAsyncTask : public mozilla::Runnable
276
{
277
public:
278
  FireErrorAsyncTask(DOMRequest* aRequest, const nsAString& aError)
279
    : mozilla::Runnable("FireErrorAsyncTask")
280
    , mReq(aRequest)
281
    , mError(aError)
282
0
  {
283
0
  }
284
285
  NS_IMETHOD
286
  Run() override
287
0
  {
288
0
    mReq->FireError(mError);
289
0
    return NS_OK;
290
0
  }
291
private:
292
  RefPtr<DOMRequest> mReq;
293
  nsString mError;
294
};
295
296
NS_IMETHODIMP
297
DOMRequestService::FireSuccessAsync(DOMRequest* aRequest,
298
                                    JS::Handle<JS::Value> aResult)
299
0
{
300
0
  NS_ENSURE_STATE(aRequest);
301
0
  return FireSuccessAsyncTask::Dispatch(aRequest, aResult);
302
0
}
303
304
NS_IMETHODIMP
305
DOMRequestService::FireErrorAsync(DOMRequest* aRequest,
306
                                  const nsAString& aError)
307
0
{
308
0
  NS_ENSURE_STATE(aRequest);
309
0
  nsCOMPtr<nsIRunnable> asyncTask = new FireErrorAsyncTask(aRequest, aError);
310
0
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(asyncTask));
311
0
  return NS_OK;
312
0
}