Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsIAtom.h"
7 #include "nsIContent.h"
8 #include "nsString.h"
9 #include "nsJSUtils.h"
10 #include "jsapi.h"
11 #include "js/CharacterEncoding.h"
12 #include "nsUnicharUtils.h"
13 #include "nsReadableUtils.h"
14 #include "nsXBLProtoImplField.h"
15 #include "nsIScriptContext.h"
16 #include "nsIURI.h"
17 #include "nsXBLSerialize.h"
18 #include "nsXBLPrototypeBinding.h"
19 #include "mozilla/dom/BindingUtils.h"
20 #include "mozilla/dom/ScriptSettings.h"
21 #include "nsGlobalWindow.h"
22 #include "xpcpublic.h"
23 #include "WrapperFactory.h"
25 using namespace mozilla;
26 using namespace mozilla::dom;
28 nsXBLProtoImplField::nsXBLProtoImplField(const char16_t* aName, const char16_t* aReadOnly)
29 : mNext(nullptr),
30 mFieldText(nullptr),
31 mFieldTextLength(0),
32 mLineNumber(0)
33 {
34 MOZ_COUNT_CTOR(nsXBLProtoImplField);
35 mName = NS_strdup(aName); // XXXbz make more sense to use a stringbuffer?
37 mJSAttributes = JSPROP_ENUMERATE;
38 if (aReadOnly) {
39 nsAutoString readOnly; readOnly.Assign(aReadOnly);
40 if (readOnly.LowerCaseEqualsLiteral("true"))
41 mJSAttributes |= JSPROP_READONLY;
42 }
43 }
46 nsXBLProtoImplField::nsXBLProtoImplField(const bool aIsReadOnly)
47 : mNext(nullptr),
48 mFieldText(nullptr),
49 mFieldTextLength(0),
50 mLineNumber(0)
51 {
52 MOZ_COUNT_CTOR(nsXBLProtoImplField);
54 mJSAttributes = JSPROP_ENUMERATE;
55 if (aIsReadOnly)
56 mJSAttributes |= JSPROP_READONLY;
57 }
59 nsXBLProtoImplField::~nsXBLProtoImplField()
60 {
61 MOZ_COUNT_DTOR(nsXBLProtoImplField);
62 if (mFieldText)
63 nsMemory::Free(mFieldText);
64 NS_Free(mName);
65 NS_CONTENT_DELETE_LIST_MEMBER(nsXBLProtoImplField, this, mNext);
66 }
68 void
69 nsXBLProtoImplField::AppendFieldText(const nsAString& aText)
70 {
71 if (mFieldText) {
72 nsDependentString fieldTextStr(mFieldText, mFieldTextLength);
73 nsAutoString newFieldText = fieldTextStr + aText;
74 char16_t* temp = mFieldText;
75 mFieldText = ToNewUnicode(newFieldText);
76 mFieldTextLength = newFieldText.Length();
77 nsMemory::Free(temp);
78 }
79 else {
80 mFieldText = ToNewUnicode(aText);
81 mFieldTextLength = aText.Length();
82 }
83 }
85 // XBL fields are represented on elements inheriting that field a bit trickily.
86 // When setting up the XBL prototype object, we install accessors for the fields
87 // on the prototype object. Those accessors, when used, will then (via
88 // InstallXBLField below) reify a property for the field onto the actual XBL-backed
89 // element.
90 //
91 // The accessor property is a plain old property backed by a getter function and
92 // a setter function. These properties are backed by the FieldGetter and
93 // FieldSetter natives; they're created by InstallAccessors. The precise field to be
94 // reified is identified using two extra slots on the getter/setter functions.
95 // XBLPROTO_SLOT stores the XBL prototype object that provides the field.
96 // FIELD_SLOT stores the name of the field, i.e. its JavaScript property name.
97 //
98 // This two-step field installation process -- creating an accessor on the
99 // prototype, then have that reify an own property on the actual element -- is
100 // admittedly convoluted. Better would be for XBL-backed elements to be proxies
101 // that could resolve fields onto themselves. But given that XBL bindings are
102 // associated with elements mutably -- you can add/remove/change -moz-binding
103 // whenever you want, alas -- doing so would require all elements to be proxies,
104 // which isn't performant now. So we do this two-step instead.
105 static const uint32_t XBLPROTO_SLOT = 0;
106 static const uint32_t FIELD_SLOT = 1;
108 bool
109 ValueHasISupportsPrivate(JS::Handle<JS::Value> v)
110 {
111 if (!v.isObject()) {
112 return false;
113 }
115 const DOMClass* domClass = GetDOMClass(&v.toObject());
116 if (domClass) {
117 return domClass->mDOMObjectIsISupports;
118 }
120 const JSClass* clasp = ::JS_GetClass(&v.toObject());
121 const uint32_t HAS_PRIVATE_NSISUPPORTS =
122 JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS;
123 return (clasp->flags & HAS_PRIVATE_NSISUPPORTS) == HAS_PRIVATE_NSISUPPORTS;
124 }
126 #ifdef DEBUG
127 static bool
128 ValueHasISupportsPrivate(JSContext* cx, const JS::Value& aVal)
129 {
130 JS::Rooted<JS::Value> v(cx, aVal);
131 return ValueHasISupportsPrivate(v);
132 }
133 #endif
135 // Define a shadowing property on |this| for the XBL field defined by the
136 // contents of the callee's reserved slots. If the property was defined,
137 // *installed will be true, and idp will be set to the property name that was
138 // defined.
139 static bool
140 InstallXBLField(JSContext* cx,
141 JS::Handle<JSObject*> callee, JS::Handle<JSObject*> thisObj,
142 JS::MutableHandle<jsid> idp, bool* installed)
143 {
144 *installed = false;
146 // First ensure |this| is a reasonable XBL bound node.
147 //
148 // FieldAccessorGuard already determined whether |thisObj| was acceptable as
149 // |this| in terms of not throwing a TypeError. Assert this for good measure.
150 MOZ_ASSERT(ValueHasISupportsPrivate(cx, JS::ObjectValue(*thisObj)));
152 // But there are some cases where we must accept |thisObj| but not install a
153 // property on it, or otherwise touch it. Hence this split of |this|-vetting
154 // duties.
155 nsISupports* native =
156 nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, thisObj);
157 if (!native) {
158 // Looks like whatever |thisObj| is it's not our nsIContent. It might well
159 // be the proto our binding installed, however, where the private is the
160 // nsXBLDocumentInfo, so just baul out quietly. Do NOT throw an exception
161 // here.
162 //
163 // We could make this stricter by checking the class maybe, but whatever.
164 return true;
165 }
167 nsCOMPtr<nsIContent> xblNode = do_QueryInterface(native);
168 if (!xblNode) {
169 xpc::Throw(cx, NS_ERROR_UNEXPECTED);
170 return false;
171 }
173 // Now that |this| is okay, actually install the field.
175 // Because of the possibility (due to XBL binding inheritance, because each
176 // XBL binding lives in its own global object) that |this| might be in a
177 // different compartment from the callee (not to mention that this method can
178 // be called with an arbitrary |this| regardless of how insane XBL is), and
179 // because in this method we've entered |this|'s compartment (see in
180 // Field[GS]etter where we attempt a cross-compartment call), we must enter
181 // the callee's compartment to access its reserved slots.
182 nsXBLPrototypeBinding* protoBinding;
183 nsDependentJSString fieldName;
184 {
185 JSAutoCompartment ac(cx, callee);
187 JS::Rooted<JSObject*> xblProto(cx);
188 xblProto = &js::GetFunctionNativeReserved(callee, XBLPROTO_SLOT).toObject();
190 JS::Rooted<JS::Value> name(cx, js::GetFunctionNativeReserved(callee, FIELD_SLOT));
191 JSFlatString* fieldStr = JS_ASSERT_STRING_IS_FLAT(name.toString());
192 fieldName.init(fieldStr);
194 MOZ_ALWAYS_TRUE(JS_ValueToId(cx, name, idp));
196 // If a separate XBL scope is being used, the callee is not same-compartment
197 // with the xbl prototype, and the object is a cross-compartment wrapper.
198 xblProto = js::UncheckedUnwrap(xblProto);
199 JSAutoCompartment ac2(cx, xblProto);
200 JS::Value slotVal = ::JS_GetReservedSlot(xblProto, 0);
201 protoBinding = static_cast<nsXBLPrototypeBinding*>(slotVal.toPrivate());
202 MOZ_ASSERT(protoBinding);
203 }
205 nsXBLProtoImplField* field = protoBinding->FindField(fieldName);
206 MOZ_ASSERT(field);
208 nsresult rv = field->InstallField(thisObj, protoBinding->DocURI(), installed);
209 if (NS_SUCCEEDED(rv)) {
210 return true;
211 }
213 if (!::JS_IsExceptionPending(cx)) {
214 xpc::Throw(cx, rv);
215 }
216 return false;
217 }
219 bool
220 FieldGetterImpl(JSContext *cx, JS::CallArgs args)
221 {
222 JS::Handle<JS::Value> thisv = args.thisv();
223 MOZ_ASSERT(ValueHasISupportsPrivate(thisv));
225 JS::Rooted<JSObject*> thisObj(cx, &thisv.toObject());
227 // We should be in the compartment of |this|. If we got here via nativeCall,
228 // |this| is not same-compartment with |callee|, and it's possible via
229 // asymmetric security semantics that |args.calleev()| is actually a security
230 // wrapper. In this case, we know we want to do an unsafe unwrap, and
231 // InstallXBLField knows how to handle cross-compartment pointers.
232 bool installed = false;
233 JS::Rooted<JSObject*> callee(cx, js::UncheckedUnwrap(&args.calleev().toObject()));
234 JS::Rooted<jsid> id(cx);
235 if (!InstallXBLField(cx, callee, thisObj, &id, &installed)) {
236 return false;
237 }
239 if (!installed) {
240 args.rval().setUndefined();
241 return true;
242 }
244 JS::Rooted<JS::Value> v(cx);
245 if (!JS_GetPropertyById(cx, thisObj, id, &v)) {
246 return false;
247 }
248 args.rval().set(v);
249 return true;
250 }
252 static bool
253 FieldGetter(JSContext *cx, unsigned argc, JS::Value *vp)
254 {
255 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
256 return JS::CallNonGenericMethod<ValueHasISupportsPrivate, FieldGetterImpl>
257 (cx, args);
258 }
260 bool
261 FieldSetterImpl(JSContext *cx, JS::CallArgs args)
262 {
263 JS::Handle<JS::Value> thisv = args.thisv();
264 MOZ_ASSERT(ValueHasISupportsPrivate(thisv));
266 JS::Rooted<JSObject*> thisObj(cx, &thisv.toObject());
268 // We should be in the compartment of |this|. If we got here via nativeCall,
269 // |this| is not same-compartment with |callee|, and it's possible via
270 // asymmetric security semantics that |args.calleev()| is actually a security
271 // wrapper. In this case, we know we want to do an unsafe unwrap, and
272 // InstallXBLField knows how to handle cross-compartment pointers.
273 bool installed = false;
274 JS::Rooted<JSObject*> callee(cx, js::UncheckedUnwrap(&args.calleev().toObject()));
275 JS::Rooted<jsid> id(cx);
276 if (!InstallXBLField(cx, callee, thisObj, &id, &installed)) {
277 return false;
278 }
280 if (installed) {
281 if (!::JS_SetPropertyById(cx, thisObj, id, args.get(0))) {
282 return false;
283 }
284 }
285 args.rval().setUndefined();
286 return true;
287 }
289 static bool
290 FieldSetter(JSContext *cx, unsigned argc, JS::Value *vp)
291 {
292 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
293 return JS::CallNonGenericMethod<ValueHasISupportsPrivate, FieldSetterImpl>
294 (cx, args);
295 }
297 nsresult
298 nsXBLProtoImplField::InstallAccessors(JSContext* aCx,
299 JS::Handle<JSObject*> aTargetClassObject)
300 {
301 MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx));
302 JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject));
303 JS::Rooted<JSObject*> scopeObject(aCx, xpc::GetXBLScopeOrGlobal(aCx, globalObject));
304 NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
306 // Don't install it if the field is empty; see also InstallField which also must
307 // implement the not-empty requirement.
308 if (IsEmpty()) {
309 return NS_OK;
310 }
312 // Install a getter/setter pair which will resolve the field onto the actual
313 // object, when invoked.
315 // Get the field name as an id.
316 JS::Rooted<jsid> id(aCx);
317 JS::TwoByteChars chars(mName, NS_strlen(mName));
318 if (!JS_CharsToId(aCx, chars, &id))
319 return NS_ERROR_OUT_OF_MEMORY;
321 // Properties/Methods have historically taken precendence over fields. We
322 // install members first, so just bounce here if the property is already
323 // defined.
324 bool found = false;
325 if (!JS_AlreadyHasOwnPropertyById(aCx, aTargetClassObject, id, &found))
326 return NS_ERROR_FAILURE;
327 if (found)
328 return NS_OK;
330 // FieldGetter and FieldSetter need to run in the XBL scope so that they can
331 // see through any SOWs on their targets.
333 // First, enter the XBL scope, and compile the functions there.
334 JSAutoCompartment ac(aCx, scopeObject);
335 JS::Rooted<JS::Value> wrappedClassObj(aCx, JS::ObjectValue(*aTargetClassObject));
336 if (!JS_WrapValue(aCx, &wrappedClassObj) || !JS_WrapId(aCx, &id))
337 return NS_ERROR_OUT_OF_MEMORY;
339 JS::Rooted<JSObject*> get(aCx,
340 JS_GetFunctionObject(js::NewFunctionByIdWithReserved(aCx, FieldGetter,
341 0, 0, scopeObject, id)));
342 if (!get) {
343 return NS_ERROR_OUT_OF_MEMORY;
344 }
345 js::SetFunctionNativeReserved(get, XBLPROTO_SLOT, wrappedClassObj);
346 js::SetFunctionNativeReserved(get, FIELD_SLOT,
347 JS::StringValue(JSID_TO_STRING(id)));
349 JS::Rooted<JSObject*> set(aCx,
350 JS_GetFunctionObject(js::NewFunctionByIdWithReserved(aCx, FieldSetter,
351 1, 0, scopeObject, id)));
352 if (!set) {
353 return NS_ERROR_OUT_OF_MEMORY;
354 }
355 js::SetFunctionNativeReserved(set, XBLPROTO_SLOT, wrappedClassObj);
356 js::SetFunctionNativeReserved(set, FIELD_SLOT,
357 JS::StringValue(JSID_TO_STRING(id)));
359 // Now, re-enter the class object's scope, wrap the getters/setters, and define
360 // them there.
361 JSAutoCompartment ac2(aCx, aTargetClassObject);
362 if (!JS_WrapObject(aCx, &get) || !JS_WrapObject(aCx, &set) ||
363 !JS_WrapId(aCx, &id))
364 {
365 return NS_ERROR_OUT_OF_MEMORY;
366 }
368 if (!::JS_DefinePropertyById(aCx, aTargetClassObject, id, JS::UndefinedValue(),
369 JS_DATA_TO_FUNC_PTR(JSPropertyOp, get.get()),
370 JS_DATA_TO_FUNC_PTR(JSStrictPropertyOp, set.get()),
371 AccessorAttributes())) {
372 return NS_ERROR_OUT_OF_MEMORY;
373 }
375 return NS_OK;
376 }
378 nsresult
379 nsXBLProtoImplField::InstallField(JS::Handle<JSObject*> aBoundNode,
380 nsIURI* aBindingDocURI,
381 bool* aDidInstall) const
382 {
383 NS_PRECONDITION(aBoundNode,
384 "uh-oh, bound node should NOT be null or bad things will "
385 "happen");
387 *aDidInstall = false;
389 // Empty fields are treated as not actually present.
390 if (IsEmpty()) {
391 return NS_OK;
392 }
394 nsAutoMicroTask mt;
396 // EvaluateString and JS_DefineUCProperty can both trigger GC, so
397 // protect |result| here.
398 nsresult rv;
400 nsAutoCString uriSpec;
401 aBindingDocURI->GetSpec(uriSpec);
403 nsIGlobalObject* globalObject = xpc::WindowGlobalOrNull(aBoundNode);
404 if (!globalObject) {
405 return NS_OK;
406 }
408 // We are going to run script via EvaluateString, so we need a script entry
409 // point, but as this is XBL related it does not appear in the HTML spec.
410 AutoEntryScript entryScript(globalObject, true);
411 JSContext* cx = entryScript.cx();
413 NS_ASSERTION(!::JS_IsExceptionPending(cx),
414 "Shouldn't get here when an exception is pending!");
416 // First, enter the xbl scope, wrap the node, and use that as the scope for
417 // the evaluation.
418 JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, aBoundNode));
419 NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
420 JSAutoCompartment ac(cx, scopeObject);
422 JS::Rooted<JSObject*> wrappedNode(cx, aBoundNode);
423 if (!JS_WrapObject(cx, &wrappedNode))
424 return NS_ERROR_OUT_OF_MEMORY;
426 JS::Rooted<JS::Value> result(cx);
427 JS::CompileOptions options(cx);
428 options.setFileAndLine(uriSpec.get(), mLineNumber)
429 .setVersion(JSVERSION_LATEST);
430 nsJSUtils::EvaluateOptions evalOptions;
431 rv = nsJSUtils::EvaluateString(cx, nsDependentString(mFieldText,
432 mFieldTextLength),
433 wrappedNode, options, evalOptions,
434 &result);
435 if (NS_FAILED(rv)) {
436 return rv;
437 }
440 // Now, enter the node's compartment, wrap the eval result, and define it on
441 // the bound node.
442 JSAutoCompartment ac2(cx, aBoundNode);
443 nsDependentString name(mName);
444 if (!JS_WrapValue(cx, &result) ||
445 !::JS_DefineUCProperty(cx, aBoundNode,
446 reinterpret_cast<const jschar*>(mName),
447 name.Length(), result, nullptr, nullptr,
448 mJSAttributes)) {
449 return NS_ERROR_OUT_OF_MEMORY;
450 }
452 *aDidInstall = true;
453 return NS_OK;
454 }
456 nsresult
457 nsXBLProtoImplField::Read(nsIObjectInputStream* aStream)
458 {
459 nsAutoString name;
460 nsresult rv = aStream->ReadString(name);
461 NS_ENSURE_SUCCESS(rv, rv);
462 mName = ToNewUnicode(name);
464 rv = aStream->Read32(&mLineNumber);
465 NS_ENSURE_SUCCESS(rv, rv);
467 nsAutoString fieldText;
468 rv = aStream->ReadString(fieldText);
469 NS_ENSURE_SUCCESS(rv, rv);
470 mFieldTextLength = fieldText.Length();
471 if (mFieldTextLength)
472 mFieldText = ToNewUnicode(fieldText);
474 return NS_OK;
475 }
477 nsresult
478 nsXBLProtoImplField::Write(nsIObjectOutputStream* aStream)
479 {
480 XBLBindingSerializeDetails type = XBLBinding_Serialize_Field;
482 if (mJSAttributes & JSPROP_READONLY) {
483 type |= XBLBinding_Serialize_ReadOnly;
484 }
486 nsresult rv = aStream->Write8(type);
487 NS_ENSURE_SUCCESS(rv, rv);
488 rv = aStream->WriteWStringZ(mName);
489 NS_ENSURE_SUCCESS(rv, rv);
490 rv = aStream->Write32(mLineNumber);
491 NS_ENSURE_SUCCESS(rv, rv);
493 return aStream->WriteWStringZ(mFieldText ? mFieldText : MOZ_UTF16(""));
494 }