Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/bindings/CallbackObject.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 "mozilla/dom/CallbackObject.h"
8
#include "mozilla/CycleCollectedJSContext.h"
9
#include "mozilla/dom/BindingUtils.h"
10
#include "jsfriendapi.h"
11
#include "nsIScriptGlobalObject.h"
12
#include "nsIXPConnect.h"
13
#include "nsIScriptContext.h"
14
#include "nsPIDOMWindow.h"
15
#include "nsJSUtils.h"
16
#include "xpcprivate.h"
17
#include "WorkerPrivate.h"
18
#include "nsContentUtils.h"
19
#include "nsGlobalWindow.h"
20
#include "WorkerScope.h"
21
#include "jsapi.h"
22
#include "nsJSPrincipals.h"
23
24
namespace mozilla {
25
namespace dom {
26
27
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject)
28
0
  NS_INTERFACE_MAP_ENTRY(mozilla::dom::CallbackObject)
29
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
30
0
NS_INTERFACE_MAP_END
31
32
NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackObject)
33
NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject)
34
35
NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject)
36
37
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject)
38
0
  tmp->ClearJSReferences();
39
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal)
40
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
41
42
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CallbackObject)
43
0
  JSObject* callback = tmp->CallbackPreserveColor();
44
0
45
0
  if (!aRemovingAllowed) {
46
0
    // If our callback has been cleared, we can't be part of a garbage cycle.
47
0
    return !callback;
48
0
  }
49
0
50
0
  // mCallback is always wrapped for the CallbackObject's incumbent global. In
51
0
  // the case where the real callback is in a different compartment, we have a
52
0
  // cross-compartment wrapper, and it will automatically be cut when its
53
0
  // compartment is nuked. In the case where it is in the same compartment, we
54
0
  // have a reference to the real function. Since that means there are no
55
0
  // wrappers to cut, we need to check whether the compartment is still alive,
56
0
  // and drop the references if it is not.
57
0
58
0
  if (MOZ_UNLIKELY(!callback)) {
59
0
    return true;
60
0
  }
61
0
  auto pvt = xpc::CompartmentPrivate::Get(callback);
62
0
  if (MOZ_LIKELY(tmp->mIncumbentGlobal && pvt) && MOZ_UNLIKELY(pvt->wasNuked)) {
63
0
    // It's not safe to release our global reference or drop our JS objects at
64
0
    // this point, so defer their finalization until CC is finished.
65
0
    AddForDeferredFinalization(new JSObjectsDropper(tmp));
66
0
    DeferredFinalize(tmp->mIncumbentGlobal.forget().take());
67
0
    return true;
68
0
  }
69
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
70
0
71
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CallbackObject)
72
0
  return !tmp->mCallback;
73
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
74
75
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CallbackObject)
76
0
  return !tmp->mCallback;
77
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
78
79
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject)
80
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal)
81
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
82
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
83
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
84
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallbackGlobal)
85
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCreationStack)
86
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal)
87
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
88
89
void
90
CallbackObject::Trace(JSTracer* aTracer)
91
0
{
92
0
  JS::TraceEdge(aTracer, &mCallback, "CallbackObject.mCallback");
93
0
  JS::TraceEdge(aTracer, &mCallbackGlobal, "CallbackObject.mCallbackGlobal");
94
0
  JS::TraceEdge(aTracer, &mCreationStack, "CallbackObject.mCreationStack");
95
0
  JS::TraceEdge(aTracer, &mIncumbentJSGlobal,
96
0
                "CallbackObject.mIncumbentJSGlobal");
97
0
}
98
99
void
100
CallbackObject::FinishSlowJSInitIfMoreThanOneOwner(JSContext* aCx)
101
0
{
102
0
  MOZ_ASSERT(mRefCnt.get() > 0);
103
0
  if (mRefCnt.get() > 1) {
104
0
    mozilla::HoldJSObjects(this);
105
0
    if (JS::ContextOptionsRef(aCx).asyncStack()) {
106
0
      JS::RootedObject stack(aCx);
107
0
      if (!JS::CaptureCurrentStack(aCx, &stack)) {
108
0
        JS_ClearPendingException(aCx);
109
0
      }
110
0
      mCreationStack = stack;
111
0
    }
112
0
    mIncumbentGlobal = GetIncumbentGlobal();
113
0
    if (mIncumbentGlobal) {
114
0
      mIncumbentJSGlobal = mIncumbentGlobal->GetGlobalJSObject();
115
0
    }
116
0
  } else {
117
0
    // We can just forget all our stuff.
118
0
    ClearJSReferences();
119
0
  }
120
0
}
121
122
JSObject*
123
CallbackObject::Callback(JSContext* aCx)
124
0
{
125
0
  JSObject* callback = CallbackOrNull();
126
0
  if (!callback) {
127
0
    callback = JS_NewDeadWrapper(aCx);
128
0
  }
129
0
130
0
  MOZ_DIAGNOSTIC_ASSERT(callback);
131
0
  return callback;
132
0
}
133
134
CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
135
                                     ErrorResult& aRv,
136
                                     const char* aExecutionReason,
137
                                     ExceptionHandling aExceptionHandling,
138
                                     JS::Realm* aRealm,
139
                                     bool aIsJSImplementedWebIDL)
140
  : mCx(nullptr)
141
  , mRealm(aRealm)
142
  , mErrorResult(aRv)
143
  , mExceptionHandling(aExceptionHandling)
144
  , mIsMainThread(NS_IsMainThread())
145
0
{
146
0
  CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
147
0
  if (ccjs) {
148
0
    ccjs->EnterMicroTask();
149
0
  }
150
0
151
0
  // Compute the caller's subject principal (if necessary) early, before we
152
0
  // do anything that might perturb the relevant state.
153
0
  nsIPrincipal* webIDLCallerPrincipal = nullptr;
154
0
  if (aIsJSImplementedWebIDL) {
155
0
    webIDLCallerPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
156
0
  }
157
0
158
0
  JSObject* wrappedCallback = aCallback->CallbackPreserveColor();
159
0
  if (!wrappedCallback) {
160
0
    aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
161
0
      NS_LITERAL_CSTRING("Cannot execute callback from a nuked compartment."));
162
0
    return;
163
0
  }
164
0
165
0
  nsIGlobalObject* globalObject = nullptr;
166
0
167
0
  {
168
0
    // First, find the real underlying callback.
169
0
    JSObject* realCallback = js::UncheckedUnwrap(wrappedCallback);
170
0
171
0
    // Check that it's ok to run this callback. JS-implemented WebIDL is always
172
0
    // OK to run, since it runs with Chrome privileges anyway.
173
0
    if (mIsMainThread && !aIsJSImplementedWebIDL) {
174
0
      // Make sure to use realCallback to get the global of the callback
175
0
      // object, not the wrapper.
176
0
      if (!xpc::Scriptability::Get(realCallback).Allowed()) {
177
0
        aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
178
0
          NS_LITERAL_CSTRING("Refusing to execute function from global in which "
179
0
                             "script is disabled."));
180
0
        return;
181
0
      }
182
0
    }
183
0
184
0
    // Now get the global for this callback. Note that for the case of
185
0
    // JS-implemented WebIDL we never have a window here.
186
0
    nsGlobalWindowInner* win = mIsMainThread && !aIsJSImplementedWebIDL
187
0
                            ? xpc::WindowGlobalOrNull(realCallback)
188
0
                            : nullptr;
189
0
    if (win) {
190
0
      // We don't want to run script in windows that have been navigated away
191
0
      // from.
192
0
      if (!win->AsInner()->HasActiveDocument()) {
193
0
        aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
194
0
          NS_LITERAL_CSTRING("Refusing to execute function from window "
195
0
                             "whose document is no longer active."));
196
0
        return;
197
0
      }
198
0
      globalObject = win;
199
0
    } else {
200
0
      // No DOM Window. Store the global.
201
0
      globalObject = xpc::NativeGlobal(realCallback);
202
0
      MOZ_ASSERT(globalObject);
203
0
    }
204
0
  }
205
0
206
0
  // Bail out if there's no useful global.
207
0
  if (!globalObject->GetGlobalJSObject()) {
208
0
    aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
209
0
      NS_LITERAL_CSTRING("Refusing to execute function from global which is "
210
0
                         "being torn down."));
211
0
    return;
212
0
  }
213
0
214
0
  mAutoEntryScript.emplace(globalObject, aExecutionReason, mIsMainThread);
215
0
  mAutoEntryScript->SetWebIDLCallerPrincipal(webIDLCallerPrincipal);
216
0
  nsIGlobalObject* incumbent = aCallback->IncumbentGlobalOrNull();
217
0
  if (incumbent) {
218
0
    // The callback object traces its incumbent JS global, so in general it
219
0
    // should be alive here. However, it's possible that we could run afoul
220
0
    // of the same IPC global weirdness described above, wherein the
221
0
    // nsIGlobalObject has severed its reference to the JS global. Let's just
222
0
    // be safe here, so that nobody has to waste a day debugging gaia-ui tests.
223
0
    if (!incumbent->GetGlobalJSObject()) {
224
0
      aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
225
0
        NS_LITERAL_CSTRING("Refusing to execute function because our "
226
0
                           "incumbent global is being torn down."));
227
0
      return;
228
0
    }
229
0
    mAutoIncumbentScript.emplace(incumbent);
230
0
  }
231
0
232
0
  JSContext* cx = mAutoEntryScript->cx();
233
0
234
0
  // Unmark the callable (by invoking CallbackOrNull() and not the
235
0
  // CallbackPreserveColor() variant), and stick it in a Rooted before it can
236
0
  // go gray again.
237
0
  // Nothing before us in this function can trigger a CC, so it's safe to wait
238
0
  // until here it do the unmark. This allows us to construct mRootedCallable
239
0
  // with the cx from mAutoEntryScript, avoiding the cost of finding another
240
0
  // JSContext. (Rooted<> does not care about requests or compartments.)
241
0
  mRootedCallable.emplace(cx, aCallback->CallbackOrNull());
242
0
  mRootedCallableGlobal.emplace(cx, aCallback->CallbackGlobalOrNull());
243
0
244
0
  mAsyncStack.emplace(cx, aCallback->GetCreationStack());
245
0
  if (*mAsyncStack) {
246
0
    mAsyncStackSetter.emplace(cx, *mAsyncStack, aExecutionReason);
247
0
  }
248
0
249
0
  // Enter the realm of our callback, so we can actually work with it.
250
0
  //
251
0
  // Note that if the callback is a wrapper, this will not be the same
252
0
  // realm that we ended up in with mAutoEntryScript above, because the
253
0
  // entry point is based off of the unwrapped callback (realCallback).
254
0
  mAr.emplace(cx, *mRootedCallableGlobal);
255
0
256
0
  // And now we're ready to go.
257
0
  mCx = cx;
258
0
}
259
260
bool
261
CallbackObject::CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException)
262
0
{
263
0
  if (mExceptionHandling == eRethrowExceptions) {
264
0
    if (!mRealm) {
265
0
      // Caller didn't ask us to filter for only exceptions we subsume.
266
0
      return true;
267
0
    }
268
0
269
0
    // On workers, we don't have nsIPrincipals to work with.  But we also only
270
0
    // have one realm, so check whether mRealm is the same as the current realm
271
0
    // of mCx.
272
0
    if (mRealm == js::GetContextRealm(mCx)) {
273
0
      return true;
274
0
    }
275
0
276
0
    MOZ_ASSERT(NS_IsMainThread());
277
0
278
0
    // At this point mCx is in the realm of our unwrapped callback, so just
279
0
    // check whether the principal of mRealm subsumes that of the current
280
0
    // realm/global of mCx.
281
0
    nsIPrincipal* callerPrincipal =
282
0
      nsJSPrincipals::get(JS::GetRealmPrincipals(mRealm));
283
0
    nsIPrincipal* calleePrincipal = nsContentUtils::SubjectPrincipal();
284
0
    if (callerPrincipal->SubsumesConsideringDomain(calleePrincipal)) {
285
0
      return true;
286
0
    }
287
0
  }
288
0
289
0
  MOZ_ASSERT(mRealm);
290
0
291
0
  // Now we only want to throw an exception to the caller if the object that was
292
0
  // thrown is in the caller realm (which we stored in mRealm).
293
0
294
0
  if (!aException.isObject()) {
295
0
    return false;
296
0
  }
297
0
298
0
  JS::Rooted<JSObject*> obj(mCx, &aException.toObject());
299
0
  obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
300
0
  return js::GetNonCCWObjectRealm(obj) == mRealm;
301
0
}
302
303
CallbackObject::CallSetup::~CallSetup()
304
0
{
305
0
  // To get our nesting right we have to destroy our JSAutoRealm first.
306
0
  // In particular, we want to do this before we try reporting any exceptions,
307
0
  // so we end up reporting them while in the realm of our entry point,
308
0
  // not whatever cross-compartment wrappper mCallback might be.
309
0
  // Be careful: the JSAutoRealm might not have been constructed at all!
310
0
  mAr.reset();
311
0
312
0
  // Now, if we have a JSContext, report any pending errors on it, unless we
313
0
  // were told to re-throw them.
314
0
  if (mCx) {
315
0
    bool needToDealWithException = mAutoEntryScript->HasException();
316
0
    if ((mRealm && mExceptionHandling == eRethrowContentExceptions) ||
317
0
        mExceptionHandling == eRethrowExceptions) {
318
0
      mErrorResult.MightThrowJSException();
319
0
      if (needToDealWithException) {
320
0
        JS::Rooted<JS::Value> exn(mCx);
321
0
        if (mAutoEntryScript->PeekException(&exn) &&
322
0
            ShouldRethrowException(exn)) {
323
0
          mAutoEntryScript->ClearException();
324
0
          MOZ_ASSERT(!mAutoEntryScript->HasException());
325
0
          mErrorResult.ThrowJSException(mCx, exn);
326
0
          needToDealWithException = false;
327
0
        }
328
0
      }
329
0
    }
330
0
331
0
    if (needToDealWithException) {
332
0
      // Either we're supposed to report our exceptions, or we're supposed to
333
0
      // re-throw them but we failed to get the exception value.  Either way,
334
0
      // we'll just report the pending exception, if any, once ~mAutoEntryScript
335
0
      // runs.  Note that we've already run ~mAr, effectively, so we don't have
336
0
      // to worry about ordering here.
337
0
      if (mErrorResult.IsJSContextException()) {
338
0
        // XXXkhuey bug 1117269.  When this is fixed, please consider fixing
339
0
        // ThrowExceptionValueIfSafe over in Exceptions.cpp in the same way.
340
0
341
0
        // IsJSContextException shouldn't be true anymore because we will report
342
0
        // the exception on the JSContext ... so throw something else.
343
0
        mErrorResult.ThrowWithCustomCleanup(NS_ERROR_UNEXPECTED);
344
0
      }
345
0
    }
346
0
  }
347
0
348
0
  mAutoIncumbentScript.reset();
349
0
  mAutoEntryScript.reset();
350
0
351
0
  // It is important that this is the last thing we do, after leaving the
352
0
  // realm and undoing all our entry/incumbent script changes
353
0
  CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
354
0
  if (ccjs) {
355
0
    ccjs->LeaveMicroTask();
356
0
  }
357
0
}
358
359
already_AddRefed<nsISupports>
360
CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback,
361
                                          const nsIID& aIID) const
362
0
{
363
0
  MOZ_ASSERT(NS_IsMainThread());
364
0
  if (!aCallback) {
365
0
    return nullptr;
366
0
  }
367
0
368
0
  // We don't init the AutoJSAPI with our callback because we don't want it
369
0
  // reporting errors to its global's onerror handlers.
370
0
  AutoJSAPI jsapi;
371
0
  jsapi.Init();
372
0
  JSContext* cx = jsapi.cx();
373
0
374
0
  JS::Rooted<JSObject*> callback(cx, aCallback->CallbackOrNull());
375
0
  if (!callback) {
376
0
    return nullptr;
377
0
  }
378
0
379
0
  JSAutoRealm ar(cx, aCallback->CallbackGlobalOrNull());
380
0
381
0
  RefPtr<nsXPCWrappedJS> wrappedJS;
382
0
  nsresult rv =
383
0
    nsXPCWrappedJS::GetNewOrUsed(cx, callback, aIID, getter_AddRefs(wrappedJS));
384
0
  if (NS_FAILED(rv) || !wrappedJS) {
385
0
    return nullptr;
386
0
  }
387
0
388
0
  nsCOMPtr<nsISupports> retval;
389
0
  rv = wrappedJS->QueryInterface(aIID, getter_AddRefs(retval));
390
0
  if (NS_FAILED(rv)) {
391
0
    return nullptr;
392
0
  }
393
0
394
0
  return retval.forget();
395
0
}
396
397
} // namespace dom
398
} // namespace mozilla