Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/xbl/nsXBLProtoImpl.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/DebugOnly.h"
8
9
#include "nsXBLProtoImpl.h"
10
#include "nsIContent.h"
11
#include "nsIDocument.h"
12
#include "nsContentUtils.h"
13
#include "nsIXPConnect.h"
14
#include "nsIServiceManager.h"
15
#include "nsXBLPrototypeBinding.h"
16
#include "nsXBLProtoImplProperty.h"
17
#include "nsIURI.h"
18
#include "mozilla/dom/ScriptSettings.h"
19
#include "mozilla/dom/XULElementBinding.h"
20
#include "xpcpublic.h"
21
#include "js/CharacterEncoding.h"
22
23
using namespace mozilla;
24
using namespace mozilla::dom;
25
using js::AssertSameCompartment;
26
27
nsresult
28
nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding,
29
                                      nsXBLBinding* aBinding)
30
0
{
31
0
  // This function is called to install a concrete implementation on a bound element using
32
0
  // this prototype implementation as a guide.  The prototype implementation is compiled lazily,
33
0
  // so for the first bound element that needs a concrete implementation, we also build the
34
0
  // prototype implementation.
35
0
  if (!mMembers && !mFields)  // Constructor and destructor also live in mMembers
36
0
    return NS_OK; // Nothing to do, so let's not waste time.
37
0
38
0
  // If the way this gets the script context changes, fix
39
0
  // nsXBLProtoImplAnonymousMethod::Execute
40
0
  nsIDocument* document = aBinding->GetBoundElement()->OwnerDoc();
41
0
42
0
  // This sometimes gets called when we have no outer window and if we don't
43
0
  // catch this, we get leaks during crashtests and reftests.
44
0
  if (NS_WARN_IF(!document->GetWindow())) {
45
0
    return NS_OK;
46
0
  }
47
0
48
0
  // |propertyHolder| (below) can be an existing object, so in theory we might
49
0
  // hit something that could end up running script. We never want that to
50
0
  // happen here, so we use an AutoJSAPI instead of an AutoEntryScript.
51
0
  dom::AutoJSAPI jsapi;
52
0
  if (NS_WARN_IF(!jsapi.Init(document->GetScopeObject()))) {
53
0
    return NS_OK;
54
0
  }
55
0
  JSContext* cx = jsapi.cx();
56
0
57
0
  // InitTarget objects gives us back the JS object that represents the bound element and the
58
0
  // class object in the bound document that represents the concrete version of this implementation.
59
0
  // This function also has the side effect of building up the prototype implementation if it has
60
0
  // not been built already.
61
0
  JS::Rooted<JSObject*> targetClassObject(cx, nullptr);
62
0
  bool targetObjectIsNew = false;
63
0
  nsresult rv = InitTargetObjects(aPrototypeBinding,
64
0
                                  aBinding->GetBoundElement(),
65
0
                                  &targetClassObject,
66
0
                                  &targetObjectIsNew);
67
0
  NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects
68
0
  MOZ_ASSERT(targetClassObject);
69
0
70
0
  // If the prototype already existed, we don't need to install anything. return early.
71
0
  if (!targetObjectIsNew)
72
0
    return NS_OK;
73
0
74
0
  // We want to define the canonical set of members in a safe place. If we're
75
0
  // using a separate XBL scope, we want to define them there first (so that
76
0
  // they'll be available for Xray lookups, among other things), and then copy
77
0
  // the properties to the content-side prototype as needed. We don't need to
78
0
  // bother about the field accessors here, since we don't use/support those
79
0
  // for in-content bindings.
80
0
81
0
  // First, start by entering the realm of the XBL scope. This may or may
82
0
  // not be the same realm as globalObject.
83
0
  JS::Rooted<JSObject*> globalObject(cx,
84
0
    JS::GetNonCCWObjectGlobal(targetClassObject));
85
0
  JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject));
86
0
  NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
87
0
  MOZ_ASSERT(JS_IsGlobalObject(scopeObject));
88
0
  JSAutoRealm ar(cx, scopeObject);
89
0
90
0
  // Determine the appropriate property holder.
91
0
  //
92
0
  // Note: If |targetIsNew| is false, we'll early-return above. However, that only
93
0
  // tells us if the content-side object is new, which may be the case even if
94
0
  // we've already set up the binding on the XBL side. For example, if we apply
95
0
  // a binding #foo to a <span> when we've already applied it to a <div>, we'll
96
0
  // end up with a different content prototype, but we'll already have a property
97
0
  // holder called |foo| in the XBL scope. Check for that to avoid wasteful and
98
0
  // weird property holder duplication.
99
0
  const char16_t* className = aPrototypeBinding->ClassName().get();
100
0
  JS::Rooted<JSObject*> propertyHolder(cx);
101
0
  JS::Rooted<JS::PropertyDescriptor> existingHolder(cx);
102
0
  if (scopeObject != globalObject &&
103
0
      !JS_GetOwnUCPropertyDescriptor(cx, scopeObject, className, &existingHolder)) {
104
0
    return NS_ERROR_FAILURE;
105
0
  }
106
0
  bool propertyHolderIsNew = !existingHolder.object() || !existingHolder.value().isObject();
107
0
108
0
  if (!propertyHolderIsNew) {
109
0
    propertyHolder = &existingHolder.value().toObject();
110
0
  } else if (scopeObject != globalObject) {
111
0
112
0
    // This is just a property holder, so it doesn't need any special JSClass.
113
0
    propertyHolder = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
114
0
    NS_ENSURE_TRUE(propertyHolder, NS_ERROR_OUT_OF_MEMORY);
115
0
116
0
    // Define it as a property on the scopeObject, using the same name used on
117
0
    // the content side.
118
0
    bool ok = JS_DefineUCProperty(cx, scopeObject, className, -1, propertyHolder,
119
0
                                  JSPROP_PERMANENT | JSPROP_READONLY);
120
0
    NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
121
0
  } else {
122
0
    propertyHolder = targetClassObject;
123
0
  }
124
0
125
0
  // Walk our member list and install each one in turn on the XBL scope object.
126
0
  if (propertyHolderIsNew) {
127
0
    for (nsXBLProtoImplMember* curr = mMembers;
128
0
         curr;
129
0
         curr = curr->GetNext())
130
0
      curr->InstallMember(cx, propertyHolder);
131
0
  }
132
0
133
0
  // Now, if we're using a separate XBL scope, enter the compartment of the
134
0
  // bound node and copy exposable properties to the prototype there. This
135
0
  // rewraps them appropriately, which should result in cross-compartment
136
0
  // function wrappers.
137
0
  if (propertyHolder != targetClassObject) {
138
0
    AssertSameCompartment(propertyHolder, scopeObject);
139
0
    AssertSameCompartment(targetClassObject, globalObject);
140
0
    bool inContentXBLScope = xpc::IsInContentXBLScope(scopeObject);
141
0
    for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
142
0
      if (!inContentXBLScope || curr->ShouldExposeToUntrustedContent()) {
143
0
        JS::Rooted<jsid> id(cx);
144
0
        JS::TwoByteChars chars(curr->GetName(), NS_strlen(curr->GetName()));
145
0
        bool ok = JS_CharsToId(cx, chars, &id);
146
0
        NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
147
0
148
0
        bool found;
149
0
        ok = JS_HasPropertyById(cx, propertyHolder, id, &found);
150
0
        NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
151
0
        if (!found) {
152
0
          // Some members don't install anything in InstallMember (e.g.,
153
0
          // nsXBLProtoImplAnonymousMethod). We need to skip copying in
154
0
          // those cases.
155
0
          continue;
156
0
        }
157
0
158
0
        ok = JS_CopyPropertyFrom(cx, id, targetClassObject, propertyHolder);
159
0
        NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
160
0
      }
161
0
    }
162
0
  }
163
0
164
0
  // From here on out, work in the scope of the bound element.
165
0
  JSAutoRealm ar2(cx, targetClassObject);
166
0
167
0
  // Install all of our field accessors.
168
0
  for (nsXBLProtoImplField* curr = mFields;
169
0
       curr;
170
0
       curr = curr->GetNext())
171
0
    curr->InstallAccessors(cx, targetClassObject);
172
0
173
0
  return NS_OK;
174
0
}
175
176
nsresult
177
nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding,
178
                                  nsIContent* aBoundElement,
179
                                  JS::MutableHandle<JSObject*> aTargetClassObject,
180
                                  bool* aTargetIsNew)
181
0
{
182
0
  nsresult rv = NS_OK;
183
0
184
0
  if (!mPrecompiledMemberHolder) {
185
0
    rv = CompilePrototypeMembers(aBinding); // This is the first time we've ever installed this binding on an element.
186
0
                                 // We need to go ahead and compile all methods and properties on a class
187
0
                                 // in our prototype binding.
188
0
    if (NS_FAILED(rv))
189
0
      return rv;
190
0
191
0
    MOZ_ASSERT(mPrecompiledMemberHolder);
192
0
  }
193
0
194
0
  nsIDocument *ownerDoc = aBoundElement->OwnerDoc();
195
0
  nsIGlobalObject *sgo;
196
0
197
0
  if (!(sgo = ownerDoc->GetScopeObject())) {
198
0
    return NS_ERROR_UNEXPECTED;
199
0
  }
200
0
201
0
  // Because our prototype implementation has a class, we need to build up a corresponding
202
0
  // class for the concrete implementation in the bound document.
203
0
  AutoJSContext cx;
204
0
  JS::Rooted<JSObject*> global(cx, sgo->GetGlobalJSObject());
205
0
  JS::Rooted<JS::Value> v(cx);
206
0
207
0
  JSAutoRealm ar(cx, global);
208
0
  // Make sure the interface object is created before the prototype object
209
0
  // so that XULElement is hidden from content. See bug 909340.
210
0
  bool defineOnGlobal = dom::XULElement_Binding::ConstructorEnabled(cx, global);
211
0
  dom::XULElement_Binding::GetConstructorObjectHandle(cx, defineOnGlobal);
212
0
213
0
  rv = nsContentUtils::WrapNative(cx, aBoundElement, &v,
214
0
                                  /* aAllowWrapping = */ false);
215
0
  NS_ENSURE_SUCCESS(rv, rv);
216
0
217
0
  JS::Rooted<JSObject*> value(cx, &v.toObject());
218
0
219
0
  // We passed aAllowWrapping = false to nsContentUtils::WrapNative so we
220
0
  // should not have a wrapper.
221
0
  MOZ_ASSERT(!js::IsWrapper(value));
222
0
223
0
  JSAutoRealm ar2(cx, value);
224
0
225
0
  // All of the above code was just obtaining the bound element's script object and its immediate
226
0
  // concrete base class.  We need to alter the object so that our concrete class is interposed
227
0
  // between the object and its base class.  We become the new base class of the object, and the
228
0
  // object's old base class becomes the new class' base class.
229
0
  rv = aBinding->InitClass(mClassName, cx, value, aTargetClassObject, aTargetIsNew);
230
0
  if (NS_FAILED(rv)) {
231
0
    return rv;
232
0
  }
233
0
234
0
  aBoundElement->PreserveWrapper(aBoundElement);
235
0
236
0
  return rv;
237
0
}
238
239
nsresult
240
nsXBLProtoImpl::CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding)
241
0
{
242
0
  // We want to pre-compile our implementation's members against a "prototype context". Then when we actually
243
0
  // bind the prototype to a real xbl instance, we'll clone the pre-compiled JS into the real instance's
244
0
  // context.
245
0
  AutoJSAPI jsapi;
246
0
  if (NS_WARN_IF(!jsapi.Init(xpc::CompilationScope())))
247
0
    return NS_ERROR_FAILURE;
248
0
  JSContext* cx = jsapi.cx();
249
0
250
0
  mPrecompiledMemberHolder = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
251
0
  if (!mPrecompiledMemberHolder)
252
0
    return NS_ERROR_OUT_OF_MEMORY;
253
0
254
0
  // Now that we have a class object installed, we walk our member list and compile each of our
255
0
  // properties and methods in turn.
256
0
  JS::Rooted<JSObject*> rootedHolder(cx, mPrecompiledMemberHolder);
257
0
  for (nsXBLProtoImplMember* curr = mMembers;
258
0
       curr;
259
0
       curr = curr->GetNext()) {
260
0
    nsresult rv = curr->CompileMember(jsapi, mClassName, rootedHolder);
261
0
    if (NS_FAILED(rv)) {
262
0
      DestroyMembers();
263
0
      return rv;
264
0
    }
265
0
  }
266
0
267
0
  return NS_OK;
268
0
}
269
270
bool
271
nsXBLProtoImpl::LookupMember(JSContext* aCx, nsString& aName,
272
                             JS::Handle<jsid> aNameAsId,
273
                             JS::MutableHandle<JS::PropertyDescriptor> aDesc,
274
                             JS::Handle<JSObject*> aClassObject)
275
0
{
276
0
  for (nsXBLProtoImplMember* m = mMembers; m; m = m->GetNext()) {
277
0
    if (aName.Equals(m->GetName())) {
278
0
      return JS_GetPropertyDescriptorById(aCx, aClassObject, aNameAsId, aDesc);
279
0
    }
280
0
  }
281
0
  return true;
282
0
}
283
284
void
285
nsXBLProtoImpl::Trace(const TraceCallbacks& aCallbacks, void *aClosure)
286
0
{
287
0
  // If we don't have a class object then we either didn't compile members
288
0
  // or we only have fields, in both cases there are no cycles through our
289
0
  // members.
290
0
  if (!mPrecompiledMemberHolder) {
291
0
    return;
292
0
  }
293
0
294
0
  nsXBLProtoImplMember *member;
295
0
  for (member = mMembers; member; member = member->GetNext()) {
296
0
    member->Trace(aCallbacks, aClosure);
297
0
  }
298
0
}
299
300
void
301
nsXBLProtoImpl::UnlinkJSObjects()
302
0
{
303
0
  if (mPrecompiledMemberHolder) {
304
0
    DestroyMembers();
305
0
  }
306
0
}
307
308
nsXBLProtoImplField*
309
nsXBLProtoImpl::FindField(const nsString& aFieldName) const
310
0
{
311
0
  for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
312
0
    if (aFieldName.Equals(f->GetName())) {
313
0
      return f;
314
0
    }
315
0
  }
316
0
317
0
  return nullptr;
318
0
}
319
320
bool
321
nsXBLProtoImpl::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const
322
0
{
323
0
  for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
324
0
    nsDependentString name(f->GetName());
325
0
    bool dummy;
326
0
    if (!::JS_HasUCProperty(cx, obj, name.get(), name.Length(), &dummy)) {
327
0
      return false;
328
0
    }
329
0
  }
330
0
331
0
  return true;
332
0
}
333
334
void
335
nsXBLProtoImpl::UndefineFields(JSContext *cx, JS::Handle<JSObject*> obj) const
336
0
{
337
0
  for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
338
0
    nsDependentString name(f->GetName());
339
0
340
0
    const char16_t* s = name.get();
341
0
    bool hasProp;
342
0
    if (::JS_AlreadyHasOwnUCProperty(cx, obj, s, name.Length(), &hasProp) &&
343
0
        hasProp) {
344
0
      JS::ObjectOpResult ignored;
345
0
      ::JS_DeleteUCProperty(cx, obj, s, name.Length(), ignored);
346
0
    }
347
0
  }
348
0
}
349
350
void
351
nsXBLProtoImpl::DestroyMembers()
352
0
{
353
0
  MOZ_ASSERT(mPrecompiledMemberHolder);
354
0
355
0
  delete mMembers;
356
0
  mMembers = nullptr;
357
0
  mConstructor = nullptr;
358
0
  mDestructor = nullptr;
359
0
}
360
361
nsresult
362
nsXBLProtoImpl::Read(nsIObjectInputStream* aStream,
363
                     nsXBLPrototypeBinding* aBinding)
364
0
{
365
0
  AssertInCompilationScope();
366
0
  AutoJSContext cx;
367
0
  // Set up a class object first so that deserialization is possible
368
0
  mPrecompiledMemberHolder = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
369
0
  if (!mPrecompiledMemberHolder)
370
0
    return NS_ERROR_OUT_OF_MEMORY;
371
0
372
0
  nsXBLProtoImplField* previousField = nullptr;
373
0
  nsXBLProtoImplMember* previousMember = nullptr;
374
0
375
0
  do {
376
0
    XBLBindingSerializeDetails type;
377
0
    nsresult rv = aStream->Read8(&type);
378
0
    NS_ENSURE_SUCCESS(rv, rv);
379
0
    if (type == XBLBinding_Serialize_NoMoreItems)
380
0
      break;
381
0
382
0
    switch (type & XBLBinding_Serialize_Mask) {
383
0
      case XBLBinding_Serialize_Field:
384
0
      {
385
0
        nsXBLProtoImplField* field =
386
0
          new nsXBLProtoImplField(type & XBLBinding_Serialize_ReadOnly);
387
0
        rv = field->Read(aStream);
388
0
        if (NS_FAILED(rv)) {
389
0
          delete field;
390
0
          return rv;
391
0
        }
392
0
393
0
        if (previousField) {
394
0
          previousField->SetNext(field);
395
0
        }
396
0
        else {
397
0
          mFields = field;
398
0
        }
399
0
        previousField = field;
400
0
401
0
        break;
402
0
      }
403
0
      case XBLBinding_Serialize_GetterProperty:
404
0
      case XBLBinding_Serialize_SetterProperty:
405
0
      case XBLBinding_Serialize_GetterSetterProperty:
406
0
      {
407
0
        nsAutoString name;
408
0
        nsresult rv = aStream->ReadString(name);
409
0
        NS_ENSURE_SUCCESS(rv, rv);
410
0
411
0
        nsXBLProtoImplProperty* prop =
412
0
          new nsXBLProtoImplProperty(name.get(), type & XBLBinding_Serialize_ReadOnly);
413
0
        rv = prop->Read(aStream, type & XBLBinding_Serialize_Mask);
414
0
        if (NS_FAILED(rv)) {
415
0
          delete prop;
416
0
          return rv;
417
0
        }
418
0
419
0
        previousMember = AddMember(prop, previousMember);
420
0
        break;
421
0
      }
422
0
      case XBLBinding_Serialize_Method:
423
0
      {
424
0
        nsAutoString name;
425
0
        rv = aStream->ReadString(name);
426
0
        NS_ENSURE_SUCCESS(rv, rv);
427
0
428
0
        nsXBLProtoImplMethod* method = new nsXBLProtoImplMethod(name.get());
429
0
        rv = method->Read(aStream);
430
0
        if (NS_FAILED(rv)) {
431
0
          delete method;
432
0
          return rv;
433
0
        }
434
0
435
0
        previousMember = AddMember(method, previousMember);
436
0
        break;
437
0
      }
438
0
      case XBLBinding_Serialize_Constructor:
439
0
      {
440
0
        nsAutoString name;
441
0
        rv = aStream->ReadString(name);
442
0
        NS_ENSURE_SUCCESS(rv, rv);
443
0
444
0
        mConstructor = new nsXBLProtoImplAnonymousMethod(name.get());
445
0
        rv = mConstructor->Read(aStream);
446
0
        if (NS_FAILED(rv)) {
447
0
          delete mConstructor;
448
0
          mConstructor = nullptr;
449
0
          return rv;
450
0
        }
451
0
452
0
        previousMember = AddMember(mConstructor, previousMember);
453
0
        break;
454
0
      }
455
0
      case XBLBinding_Serialize_Destructor:
456
0
      {
457
0
        nsAutoString name;
458
0
        rv = aStream->ReadString(name);
459
0
        NS_ENSURE_SUCCESS(rv, rv);
460
0
461
0
        mDestructor = new nsXBLProtoImplAnonymousMethod(name.get());
462
0
        rv = mDestructor->Read(aStream);
463
0
        if (NS_FAILED(rv)) {
464
0
          delete mDestructor;
465
0
          mDestructor = nullptr;
466
0
          return rv;
467
0
        }
468
0
469
0
        previousMember = AddMember(mDestructor, previousMember);
470
0
        break;
471
0
      }
472
0
      default:
473
0
        NS_ERROR("Unexpected binding member type");
474
0
        break;
475
0
    }
476
0
  } while (1);
477
0
478
0
  return NS_OK;
479
0
}
480
481
nsresult
482
nsXBLProtoImpl::Write(nsIObjectOutputStream* aStream,
483
                      nsXBLPrototypeBinding* aBinding)
484
0
{
485
0
  nsresult rv;
486
0
487
0
  if (!mPrecompiledMemberHolder) {
488
0
    rv = CompilePrototypeMembers(aBinding);
489
0
    NS_ENSURE_SUCCESS(rv, rv);
490
0
  }
491
0
492
0
  rv = aStream->WriteUtf8Z(mClassName.get());
493
0
  NS_ENSURE_SUCCESS(rv, rv);
494
0
495
0
  for (nsXBLProtoImplField* curr = mFields; curr; curr = curr->GetNext()) {
496
0
    rv = curr->Write(aStream);
497
0
    NS_ENSURE_SUCCESS(rv, rv);
498
0
  }
499
0
  for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
500
0
    if (curr == mConstructor) {
501
0
      rv = mConstructor->Write(aStream, XBLBinding_Serialize_Constructor);
502
0
    }
503
0
    else if (curr == mDestructor) {
504
0
      rv = mDestructor->Write(aStream, XBLBinding_Serialize_Destructor);
505
0
    }
506
0
    else {
507
0
      rv = curr->Write(aStream);
508
0
    }
509
0
    NS_ENSURE_SUCCESS(rv, rv);
510
0
  }
511
0
512
0
  return aStream->Write8(XBLBinding_Serialize_NoMoreItems);
513
0
}
514
515
void
516
NS_NewXBLProtoImpl(nsXBLPrototypeBinding* aBinding,
517
                   const char16_t* aClassName,
518
                   nsXBLProtoImpl** aResult)
519
0
{
520
0
  nsXBLProtoImpl* impl = new nsXBLProtoImpl();
521
0
  if (aClassName) {
522
0
    impl->mClassName = aClassName;
523
0
  } else {
524
0
    nsCString spec;
525
0
    nsresult rv = aBinding->BindingURI()->GetSpec(spec);
526
0
    // XXX: should handle this better
527
0
    MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
528
0
    impl->mClassName = NS_ConvertUTF8toUTF16(spec);
529
0
  }
530
0
531
0
  aBinding->SetImplementation(impl);
532
0
  *aResult = impl;
533
0
}
534