Bug 1054756, part 3 - Implement Symbol.toPrimitive. Replace existing convert hooks with methods. r=jandem.
☠☠ backed out by 027ddfe2c4af ☠ ☠
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 20 Mar 2015 14:02:55 -0500
changeset 264102 13128a88f2b91f31b6f79963768218c3997db41e
parent 264101 c250abf4fd1778496b8b0e8b58ff8b6554fe9ba2
child 264103 105433ce195b39f10f9f0b939c8786a0aff6a70f
push id29429
push usercbook@mozilla.com
push dateThu, 24 Sep 2015 10:05:08 +0000
treeherdermozilla-central@001942e4617b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1054756
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1054756, part 3 - Implement Symbol.toPrimitive. Replace existing convert hooks with methods. r=jandem. JSClass::convert is no longer used after this, but to minimize the noise, it will be deleted in a separate patch. However all non-nullptr convert hook implementations must be replaced with [@@toPrimitive] methods in this patch to avoid changing the behavior. The changes in XrayWrapper.cpp fix a pre-existing bug: when an Xray wrapper tries to emit the "Silently denied access" warning, if id is a symbol, the existing code triggers an error trying to convert it to a string for the warning message. Implementing Symbol.toPrimitive revealed this bug; the fix is straightforward.
dom/plugins/base/nsJSNPRuntime.cpp
dom/workers/WorkerScope.cpp
js/src/builtin/SymbolObject.cpp
js/src/builtin/SymbolObject.h
js/src/ctypes/CTypes.cpp
js/src/js.msg
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testOps.cpp
js/src/jsapi-tests/testUbiNode.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsdate.cpp
js/src/jsfriendapi.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/proxy/Proxy.cpp
js/src/tests/ecma_6/Date/toPrimitive.js
js/src/tests/ecma_6/Object/toPrimitive-callers.js
js/src/tests/ecma_6/Object/toPrimitive.js
js/src/tests/ecma_6/Reflect/propertyKeys.js
js/src/tests/ecma_6/Symbol/conversions.js
js/src/tests/ecma_6/Symbol/toPrimitive.js
js/src/vm/CommonPropertyNames.h
js/src/vm/Runtime.h
js/src/vm/Xdr.h
js/xpconnect/src/Sandbox.cpp
js/xpconnect/src/XPCWrappedNative.cpp
js/xpconnect/src/XPCWrappedNativeJSOps.cpp
js/xpconnect/tests/chrome/test_bug1042436.xul
js/xpconnect/tests/chrome/test_bug1065185.html
js/xpconnect/tests/chrome/test_xrayToJS.xul
js/xpconnect/wrappers/XrayWrapper.cpp
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -178,48 +178,48 @@ NPObjWrapper_GetProperty(JSContext *cx, 
 static bool
 NPObjWrapper_Enumerate(JSContext *cx, JS::Handle<JSObject*> obj, JS::AutoIdVector &properties,
                        bool enumerableOnly);
 
 static bool
 NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                      bool *resolvedp);
 
-static bool
-NPObjWrapper_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> vp);
-
 static void
 NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj);
 
 static void
 NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old);
 
 static bool
 NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp);
 
 static bool
 NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp);
 
 static bool
+NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
+
+static bool
 CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
                      JS::Handle<jsid> id,  NPVariant* getPropertyResult,
                      JS::MutableHandle<JS::Value> vp);
 
 const static js::Class sNPObjectJSWrapperClass =
   {
     NPRUNTIME_JSCLASS_NAME,
     JSCLASS_HAS_PRIVATE,
     NPObjWrapper_AddProperty,
     NPObjWrapper_DelProperty,
     NPObjWrapper_GetProperty,
     NPObjWrapper_SetProperty,
     nullptr,
     NPObjWrapper_Resolve,
     nullptr,                                                /* mayResolve */
-    NPObjWrapper_Convert,
+    nullptr,                                                /* convert */
     NPObjWrapper_Finalize,
     NPObjWrapper_Call,
     nullptr,                                                /* hasInstance */
     NPObjWrapper_Construct,
     nullptr,                                                /* trace */
     JS_NULL_CLASS_SPEC,
     {
       nullptr,                                              /* outerObject */
@@ -246,32 +246,36 @@ const static js::Class sNPObjectJSWrappe
 typedef struct NPObjectMemberPrivate {
     JS::Heap<JSObject *> npobjWrapper;
     JS::Heap<JS::Value> fieldValue;
     JS::Heap<jsid> methodName;
     NPP   npp;
 } NPObjectMemberPrivate;
 
 static bool
-NPObjectMember_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> vp);
+NPObjectMember_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
+                           JS::MutableHandleValue vp);
 
 static void
 NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj);
 
 static bool
 NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp);
 
 static void
 NPObjectMember_Trace(JSTracer *trc, JSObject *obj);
 
+static bool
+NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
+
 static const JSClass sNPObjectMemberClass =
   {
     "NPObject Ambiguous Member class", JSCLASS_HAS_PRIVATE,
+    nullptr, nullptr, NPObjectMember_GetProperty, nullptr,
     nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, NPObjectMember_Convert,
     NPObjectMember_Finalize, NPObjectMember_Call,
     nullptr, nullptr, NPObjectMember_Trace
   };
 
 static void
 OnWrapperDestroyed();
 
 static void
@@ -1387,16 +1391,30 @@ NPObjWrapper_GetProperty(JSContext *cx, 
 
   if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
       !npobj->_class->hasMethod || !npobj->_class->getProperty) {
     ThrowJSException(cx, "Bad NPObject as private data!");
 
     return false;
   }
 
+  if (JSID_IS_SYMBOL(id)) {
+    JS::RootedSymbol sym(cx, JSID_TO_SYMBOL(id));
+    if (JS::GetSymbolCode(sym) == JS::SymbolCode::toPrimitive) {
+      JS::RootedObject obj(cx, JS_GetFunctionObject(
+                                 JS_NewFunction(
+                                   cx, NPObjWrapper_toPrimitive, 1, 0,
+                                   "Symbol.toPrimitive")));
+      if (!obj)
+        return false;
+      vp.setObject(*obj);
+      return true;
+    }
+  }
+
   // Find out what plugin (NPP) is the owner of the object we're
   // manipulating, and make it own any JSObject wrappers created here.
   NPP npp = LookupNPP(npobj);
   if (!npp) {
     ThrowJSException(cx, "No NPP found for NPObject!");
 
     return false;
   }
@@ -1708,52 +1726,16 @@ NPObjWrapper_Resolve(JSContext *cx, JS::
 
     return fnc != nullptr;
   }
 
   // no property or method
   return true;
 }
 
-static bool
-NPObjWrapper_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType hint, JS::MutableHandle<JS::Value> vp)
-{
-  MOZ_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID);
-
-  // Plugins do not simply use the default [[DefaultValue]] behavior, because
-  // that behavior involves calling toString or valueOf on objects which
-  // weren't designed to accommodate this.  Usually this wouldn't be a problem,
-  // because the absence of either property, or the presence of either property
-  // with a value that isn't callable, will cause that property to simply be
-  // ignored.  But there is a problem in one specific case: Java, specifically
-  // java.lang.Integer.  The Integer class has static valueOf methods, none of
-  // which are nullary, so the JS-reflected method will behave poorly when
-  // called with no arguments.  We work around this problem by giving plugins a
-  // [[DefaultValue]] which uses only toString and not valueOf.
-
-  JS::Rooted<JS::Value> v(cx, JS::UndefinedValue());
-  if (!JS_GetProperty(cx, obj, "toString", &v))
-    return false;
-  if (!v.isPrimitive() && JS::IsCallable(v.toObjectOrNull())) {
-    if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), vp))
-      return false;
-    if (vp.isPrimitive())
-      return true;
-  }
-
-  JS_ReportErrorNumber(cx, js::GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
-                       JS_GetClass(obj)->name,
-                       hint == JSTYPE_VOID
-                       ? "primitive type"
-                       : hint == JSTYPE_NUMBER
-                       ? "number"
-                       : "string");
-  return false;
-}
-
 static void
 NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj)
 {
   NPObject *npobj = (NPObject *)::JS_GetPrivate(obj);
   if (npobj) {
     if (sNPObjWrappers) {
       sNPObjWrappers->Remove(npobj);
     }
@@ -1800,16 +1782,53 @@ NPObjWrapper_Call(JSContext *cx, unsigne
 static bool
 NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   JS::Rooted<JSObject*> obj(cx, &args.callee());
   return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, true);
 }
 
+static bool
+NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  // Plugins do not simply use the default OrdinaryToPrimitive behavior,
+  // because that behavior involves calling toString or valueOf on objects
+  // which weren't designed to accommodate this.  Usually this wouldn't be a
+  // problem, because the absence of either property, or the presence of either
+  // property with a value that isn't callable, will cause that property to
+  // simply be ignored.  But there is a problem in one specific case: Java,
+  // specifically java.lang.Integer.  The Integer class has static valueOf
+  // methods, none of which are nullary, so the JS-reflected method will behave
+  // poorly when called with no arguments.  We work around this problem by
+  // giving plugins a [Symbol.toPrimitive]() method which uses only toString
+  // and not valueOf.
+
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  JS::RootedValue thisv(cx, args.thisv());
+  if (thisv.isPrimitive())
+    return true;
+
+  JS::RootedObject obj(cx, &thisv.toObject());
+  JS::RootedValue v(cx);
+  if (!JS_GetProperty(cx, obj, "toString", &v))
+    return false;
+  if (v.isObject() && JS::IsCallable(&v.toObject())) {
+    if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), args.rval()))
+      return false;
+    if (args.rval().isPrimitive())
+      return true;
+  }
+
+  JS_ReportErrorNumber(cx, js::GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+                       JS_GetClass(obj)->name,
+                       "primitive type");
+  return false;
+}
+
 bool
 nsNPObjWrapper::IsWrapper(JSObject *obj)
 {
   return js::GetObjectClass(obj) == &sNPObjectJSWrapperClass;
 }
 
 // An NPObject is going away, make sure we null out the JS object's
 // private data in case this is an NPObject that came from a plugin
@@ -2118,48 +2137,34 @@ CreateNPObjectMember(NPP npp, JSContext 
   memberPrivate->fieldValue = fieldValue;
   memberPrivate->methodName = id;
   memberPrivate->npp = npp;
 
   return true;
 }
 
 static bool
-NPObjectMember_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> vp)
+NPObjectMember_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
+                           JS::MutableHandleValue vp)
 {
-  NPObjectMemberPrivate *memberPrivate =
-    (NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, obj,
-                                                     &sNPObjectMemberClass,
-                                                     nullptr);
-  if (!memberPrivate) {
-    NS_ERROR("no Ambiguous Member Private data!");
-    return false;
+  if (JSID_IS_SYMBOL(id)) {
+    JS::RootedSymbol sym(cx, JSID_TO_SYMBOL(id));
+    if (JS::GetSymbolCode(sym) == JS::SymbolCode::toPrimitive) {
+      JS::RootedObject obj(cx, JS_GetFunctionObject(
+                                 JS_NewFunction(
+                                   cx, NPObjectMember_toPrimitive, 1, 0,
+                                   "Symbol.toPrimitive")));
+      if (!obj)
+        return false;
+      vp.setObject(*obj);
+      return true;
+    }
   }
 
-  switch (type) {
-  case JSTYPE_VOID:
-  case JSTYPE_STRING:
-  case JSTYPE_NUMBER:
-    vp.set(memberPrivate->fieldValue);
-    if (vp.isObject()) {
-      JS::Rooted<JSObject*> objVal(cx, &vp.toObject());
-      return JS_DefaultValue(cx, objVal, type, vp);
-    }
-    return true;
-  case JSTYPE_BOOLEAN:
-  case JSTYPE_OBJECT:
-    vp.set(memberPrivate->fieldValue);
-    return true;
-  case JSTYPE_FUNCTION:
-    // Leave this to NPObjectMember_Call.
-    return true;
-  default:
-    NS_ERROR("illegal operation on JSObject prototype object");
-    return false;
-  }
+  return true;
 }
 
 static void
 NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj)
 {
   NPObjectMemberPrivate *memberPrivate;
 
   memberPrivate = (NPObjectMemberPrivate *)::JS_GetPrivate(obj);
@@ -2270,16 +2275,46 @@ NPObjectMember_Trace(JSTracer *trc, JSOb
   // NPObject, so make sure to mark the NPObject wrapper to keep the
   // NPObject alive as long as this NPObjectMember is alive.
   if (memberPrivate->npobjWrapper) {
     JS_CallObjectTracer(trc, &memberPrivate->npobjWrapper,
                         "NPObject Member => npobjWrapper");
   }
 }
 
+static bool
+NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  JS::RootedValue thisv(cx, args.thisv());
+  if (thisv.isPrimitive()) {
+    args.rval().set(thisv);
+    return true;
+  }
+
+  JS::RootedObject obj(cx, &thisv.toObject());
+  NPObjectMemberPrivate *memberPrivate =
+    (NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, obj,
+                                                     &sNPObjectMemberClass,
+                                                     &args);
+  if (!memberPrivate)
+    return false;
+
+  JSType type;
+  if (!JS::GetFirstArgumentAsTypeHint(cx, args, &type))
+    return false;
+
+  args.rval().set(memberPrivate->fieldValue);
+  if (args.rval().isObject()) {
+    JS::Rooted<JSObject*> objVal(cx, &args.rval().toObject());
+    return JS_DefaultValue(cx, objVal, type, args.rval());
+  }
+  return true;
+}
+
 // static
 bool
 nsJSObjWrapper::HasOwnProperty(NPObject *npobj, NPIdentifier npid)
 {
   NPP npp = NPPStack::Peek();
   dom::AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(GetGlobalObject(npp)))) {
     return false;
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -735,28 +735,16 @@ workerdebuggersandbox_enumerate(JSContex
 
 static bool
 workerdebuggersandbox_resolve(JSContext *cx, JS::Handle<JSObject *> obj,
                               JS::Handle<jsid> id, bool *resolvedp)
 {
   return JS_ResolveStandardClass(cx, obj, id, resolvedp);
 }
 
-static bool
-workerdebuggersandbox_convert(JSContext *cx, JS::Handle<JSObject *> obj,
-                              JSType type, JS::MutableHandle<JS::Value> vp)
-{
-  if (type == JSTYPE_OBJECT) {
-    vp.setObject(*obj);
-    return true;
-  }
-
-  return JS::OrdinaryToPrimitive(cx, obj, type, vp);
-}
-
 static void
 workerdebuggersandbox_finalize(js::FreeOp *fop, JSObject *obj)
 {
   nsIGlobalObject *globalObject =
     static_cast<nsIGlobalObject *>(JS_GetPrivate(obj));
   NS_RELEASE(globalObject);
 }
 
@@ -770,17 +758,17 @@ const js::Class workerdebuggersandbox_cl
     JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     workerdebuggersandbox_enumerate,
     workerdebuggersandbox_resolve,
     nullptr, /* mayResolve */
-    workerdebuggersandbox_convert,
+    nullptr, /* convert */
     workerdebuggersandbox_finalize,
     nullptr,
     nullptr,
     nullptr,
     JS_GlobalObjectTraceHook,
     JS_NULL_CLASS_SPEC, {
       nullptr,
       nullptr,
--- a/js/src/builtin/SymbolObject.cpp
+++ b/js/src/builtin/SymbolObject.cpp
@@ -13,25 +13,17 @@
 
 #include "vm/NativeObject-inl.h"
 
 using JS::Symbol;
 using namespace js;
 
 const Class SymbolObject::class_ = {
     "Symbol",
-    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Symbol),
-    nullptr, /* addProperty */
-    nullptr, /* delProperty */
-    nullptr, /* getProperty */
-    nullptr, /* setProperty */
-    nullptr, /* enumerate */
-    nullptr, /* resolve */
-    nullptr, /* mayResolve */
-    convert
+    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Symbol)
 };
 
 SymbolObject*
 SymbolObject::create(JSContext* cx, JS::HandleSymbol symbol)
 {
     JSObject* obj = NewBuiltinClassInstance(cx, &class_);
     if (!obj)
         return nullptr;
@@ -42,16 +34,17 @@ SymbolObject::create(JSContext* cx, JS::
 
 const JSPropertySpec SymbolObject::properties[] = {
     JS_PS_END
 };
 
 const JSFunctionSpec SymbolObject::methods[] = {
     JS_FN(js_toString_str, toString, 0, 0),
     JS_FN(js_valueOf_str, valueOf, 0, 0),
+    JS_SYM_FN(toPrimitive, toPrimitive, 1, JSPROP_READONLY),
     JS_FS_END
 };
 
 const JSFunctionSpec SymbolObject::staticMethods[] = {
     JS_FN("for", for_, 1, 0),
     JS_FN("keyFor", keyFor, 1, 0),
     JS_FS_END
 };
@@ -119,24 +112,16 @@ SymbolObject::construct(JSContext* cx, u
     // step 4
     RootedSymbol symbol(cx, JS::Symbol::new_(cx, JS::SymbolCode::UniqueSymbol, desc));
     if (!symbol)
         return false;
     args.rval().setSymbol(symbol);
     return true;
 }
 
-// Stand-in for Symbol.prototype[@@toPrimitive], ES6 rev 26 (2014 Jul 18) 19.4.3.4
-bool
-SymbolObject::convert(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
-{
-    vp.setSymbol(obj->as<SymbolObject>().unbox());
-    return true;
-}
-
 // ES6 rev 24 (2014 Apr 27) 19.4.2.2
 bool
 SymbolObject::for_(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // steps 1-2
     RootedString stringKey(cx, ToString(cx, args.get(0)));
@@ -225,13 +210,24 @@ SymbolObject::valueOf_impl(JSContext* cx
 
 bool
 SymbolObject::valueOf(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsSymbol, valueOf_impl>(cx, args);
 }
 
+// ES6 19.4.3.4
+bool
+SymbolObject::toPrimitive(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // The specification gives exactly the same algorithm for @@toPrimitive as
+    // for valueOf, so reuse the valueOf implementation.
+    return CallNonGenericMethod<IsSymbol, valueOf_impl>(cx, args);
+}
+
 JSObject*
 js::InitSymbolClass(JSContext* cx, HandleObject obj)
 {
     return SymbolObject::initClass(cx, obj);
 }
--- a/js/src/builtin/SymbolObject.h
+++ b/js/src/builtin/SymbolObject.h
@@ -36,27 +36,26 @@ class SymbolObject : public NativeObject
 
   private:
     inline void setPrimitiveValue(JS::Symbol* symbol) {
         setFixedSlot(PRIMITIVE_VALUE_SLOT, SymbolValue(symbol));
     }
 
     static bool construct(JSContext* cx, unsigned argc, Value* vp);
 
-    static bool convert(JSContext* cx, HandleObject obj, JSType type, MutableHandleValue vp);
-
     // Static methods.
     static bool for_(JSContext* cx, unsigned argc, Value* vp);
     static bool keyFor(JSContext* cx, unsigned argc, Value* vp);
 
     // Methods defined on Symbol.prototype.
     static bool toString_impl(JSContext* cx, const CallArgs& args);
     static bool toString(JSContext* cx, unsigned argc, Value* vp);
     static bool valueOf_impl(JSContext* cx, const CallArgs& args);
     static bool valueOf(JSContext* cx, unsigned argc, Value* vp);
+    static bool toPrimitive(JSContext* cx, unsigned argc, Value* vp);
 
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static const JSFunctionSpec staticMethods[];
 };
 
 extern JSObject*
 InitSymbolClass(JSContext* cx, HandleObject obj);
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -5219,16 +5219,18 @@ ArrayType::Getter(JSContext* cx, HandleO
   if (CType::GetTypeCode(typeObj) != TYPE_array)
     return true;
 
   // Convert the index to a size_t and bounds-check it.
   size_t index;
   size_t length = GetLength(typeObj);
   bool ok = jsidToSize(cx, idval, true, &index);
   int32_t dummy;
+  if (!ok && JSID_IS_SYMBOL(idval))
+    return true;
   if (!ok && JSID_IS_STRING(idval) &&
       !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) {
     // String either isn't a number, or doesn't fit in size_t.
     // Chances are it's a regular property lookup, so return.
     return true;
   }
   if (!ok || index >= length) {
     JS_ReportError(cx, "invalid index");
@@ -5257,16 +5259,18 @@ ArrayType::Setter(JSContext* cx, HandleO
   if (CType::GetTypeCode(typeObj) != TYPE_array)
     return result.succeed();
 
   // Convert the index to a size_t and bounds-check it.
   size_t index;
   size_t length = GetLength(typeObj);
   bool ok = jsidToSize(cx, idval, true, &index);
   int32_t dummy;
+  if (!ok && JSID_IS_SYMBOL(idval))
+    return true;
   if (!ok && JSID_IS_STRING(idval) &&
       !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) {
     // String either isn't a number, or doesn't fit in size_t.
     // Chances are it's a regular property lookup, so return.
     return result.succeed();
   }
   if (!ok || index >= length) {
     JS_ReportError(cx, "invalid index");
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -49,16 +49,18 @@ MSG_DEF(JSMSG_NO_CONSTRUCTOR,          1
 MSG_DEF(JSMSG_BAD_SORT_ARG,            0, JSEXN_TYPEERR, "invalid Array.prototype.sort argument")
 MSG_DEF(JSMSG_CANT_WATCH,              1, JSEXN_TYPEERR, "can't watch non-native objects of class {0}")
 MSG_DEF(JSMSG_READ_ONLY,               1, JSEXN_TYPEERR, "{0} is read-only")
 MSG_DEF(JSMSG_CANT_DELETE,             1, JSEXN_TYPEERR, "property {0} is non-configurable and can't be deleted")
 MSG_DEF(JSMSG_CANT_TRUNCATE_ARRAY,     0, JSEXN_TYPEERR, "can't delete non-configurable array element")
 MSG_DEF(JSMSG_NOT_FUNCTION,            1, JSEXN_TYPEERR, "{0} is not a function")
 MSG_DEF(JSMSG_NOT_CONSTRUCTOR,         1, JSEXN_TYPEERR, "{0} is not a constructor")
 MSG_DEF(JSMSG_CANT_CONVERT_TO,         2, JSEXN_TYPEERR, "can't convert {0} to {1}")
+MSG_DEF(JSMSG_TOPRIMITIVE_NOT_CALLABLE, 2, JSEXN_TYPEERR, "can't convert {0} to {1}: its [Symbol.toPrimitive] property is not a function")
+MSG_DEF(JSMSG_TOPRIMITIVE_RETURNED_OBJECT, 2, JSEXN_TYPEERR, "can't convert {0} to {1}: its [Symbol.toPrimitive] method returned an object")
 MSG_DEF(JSMSG_NO_PROPERTIES,           1, JSEXN_TYPEERR, "{0} has no properties")
 MSG_DEF(JSMSG_BAD_REGEXP_FLAG,         1, JSEXN_SYNTAXERR, "invalid regular expression flag {0}")
 MSG_DEF(JSMSG_ARG_INDEX_OUT_OF_RANGE,  1, JSEXN_RANGEERR, "argument {0} accesses an index that is out of range")
 MSG_DEF(JSMSG_SPREAD_TOO_LARGE,        0, JSEXN_RANGEERR, "array too large due to spread operand(s)")
 MSG_DEF(JSMSG_BAD_WEAKMAP_KEY,         0, JSEXN_TYPEERR, "cannot use the given object as a weak map key")
 MSG_DEF(JSMSG_BAD_GETTER_OR_SETTER,    1, JSEXN_TYPEERR, "invalid {0} usage")
 MSG_DEF(JSMSG_BAD_ARRAY_LENGTH,        0, JSEXN_RANGEERR, "invalid array length")
 MSG_DEF(JSMSG_REDECLARED_VAR,          2, JSEXN_TYPEERR, "redeclaration of {0} {1}")
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -56,17 +56,16 @@ UNIFIED_SOURCES += [
     'testLooselyEqual.cpp',
     'testMappedArrayBuffer.cpp',
     'testMutedErrors.cpp',
     'testNewObject.cpp',
     'testNewTargetInvokeConstructor.cpp',
     'testNullRoot.cpp',
     'testObjectEmulatingUndefined.cpp',
     'testOOM.cpp',
-    'testOps.cpp',
     'testParseJSON.cpp',
     'testPersistentRooted.cpp',
     'testPreserveJitCode.cpp',
     'testProfileStrings.cpp',
     'testPropCache.cpp',
     'testRegExp.cpp',
     'testResolveRecursion.cpp',
     'tests.cpp',
deleted file mode 100644
--- a/js/src/jsapi-tests/testOps.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set ts=8 sts=4 et sw=4 tw=99:
- *
- * Tests for operators and implicit type conversion.
- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "jsapi-tests/tests.h"
-
-static bool
-my_convert(JSContext* context, JS::HandleObject obj, JSType type, JS::MutableHandleValue rval)
-{
-    if (type == JSTYPE_VOID || type == JSTYPE_STRING || type == JSTYPE_NUMBER || type == JSTYPE_BOOLEAN) {
-        rval.set(JS_NumberValue(123));
-        return true;
-    }
-    return false;
-}
-
-static const JSClass myClass = {
-    "MyClass",
-    0,
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, my_convert
-};
-
-static bool
-createMyObject(JSContext* context, unsigned argc, JS::Value* vp)
-{
-    JS_BeginRequest(context);
-
-    //JS_GC(context); //<- if we make GC here, all is ok
-
-    JSObject* myObject = JS_NewObject(context, &myClass);
-    *vp = JS::ObjectOrNullValue(myObject);
-
-    JS_EndRequest(context);
-
-    return true;
-}
-
-static const JSFunctionSpec s_functions[] =
-{
-    JS_FN("createMyObject", createMyObject, 0, 0),
-    JS_FS_END
-};
-
-BEGIN_TEST(testOps_bug559006)
-{
-    CHECK(JS_DefineFunctions(cx, global, s_functions));
-
-    EXEC("function main() { while(1) return 0 + createMyObject(); }");
-
-    for (int i = 0; i < 9; i++) {
-        JS::RootedValue rval(cx);
-        CHECK(JS_CallFunctionName(cx, global, "main", JS::HandleValueArray::empty(),
-                                  &rval));
-        CHECK(rval.isInt32(123));
-    }
-    return true;
-}
-END_TEST(testOps_bug559006)
-
--- a/js/src/jsapi-tests/testUbiNode.cpp
+++ b/js/src/jsapi-tests/testUbiNode.cpp
@@ -5,16 +5,17 @@
 #include "builtin/TestingFunctions.h"
 #include "js/UbiNode.h"
 #include "jsapi-tests/tests.h"
 #include "vm/SavedFrame.h"
 
 using JS::RootedObject;
 using JS::RootedScript;
 using JS::RootedString;
+using namespace js;
 
 // ubi::Node::zone works
 BEGIN_TEST(test_ubiNodeZone)
 {
     RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
     CHECK(global1);
     CHECK(JS::ubi::Node(global1).zone() == cx->zone());
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1804,17 +1804,61 @@ JS_IdToValue(JSContext* cx, jsid id, Mut
 
 JS_PUBLIC_API(bool)
 JS_DefaultValue(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     MOZ_ASSERT(obj != nullptr);
     MOZ_ASSERT(hint == JSTYPE_VOID || hint == JSTYPE_STRING || hint == JSTYPE_NUMBER);
-    return ToPrimitive(cx, obj, hint, vp);
+    vp.setObject(*obj);
+    return ToPrimitiveSlow(cx, hint, vp);
+}
+
+JS_PUBLIC_API(bool)
+JS::GetFirstArgumentAsTypeHint(JSContext* cx, CallArgs args, JSType *result)
+{
+    if (!args.get(0).isString()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
+                             "Symbol.toPrimitive",
+                             "\"string\", \"number\", or \"default\"",
+                             InformalValueTypeName(args.get(0)));
+        return false;
+    }
+
+    RootedString str(cx, args.get(0).toString());
+    bool match;
+
+    if (!EqualStrings(cx, str, cx->names().default_, &match))
+        return false;
+    if (match) {
+        *result = JSTYPE_VOID;
+        return true;
+    }
+
+    if (!EqualStrings(cx, str, cx->names().string, &match))
+        return false;
+    if (match) {
+        *result = JSTYPE_STRING;
+        return true;
+    }
+
+    if (!EqualStrings(cx, str, cx->names().number, &match))
+        return false;
+    if (match) {
+        *result = JSTYPE_NUMBER;
+        return true;
+    }
+
+    JSAutoByteString bytes;
+    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
+                         "Symbol.toPrimitive",
+                         "\"string\", \"number\", or \"default\"",
+                         ValueToSourceForError(cx, args.get(0), bytes));
+    return false;
 }
 
 JS_PUBLIC_API(bool)
 JS_PropertyStub(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
 {
     return true;
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1881,25 +1881,42 @@ extern JS_PUBLIC_API(bool)
 JS_ValueToId(JSContext* cx, JS::HandleValue v, JS::MutableHandleId idp);
 
 extern JS_PUBLIC_API(bool)
 JS_StringToId(JSContext* cx, JS::HandleString s, JS::MutableHandleId idp);
 
 extern JS_PUBLIC_API(bool)
 JS_IdToValue(JSContext* cx, jsid id, JS::MutableHandle<JS::Value> vp);
 
-/*
- * Invoke the [[DefaultValue]] hook (see ES5 8.6.2) with the provided hint on
- * the specified object, computing a primitive default value for the object.
- * The hint must be JSTYPE_STRING, JSTYPE_NUMBER, or JSTYPE_VOID (no hint).  On
- * success the resulting value is stored in *vp.
- */
-extern JS_PUBLIC_API(bool)
-JS_DefaultValue(JSContext* cx, JS::Handle<JSObject*> obj, JSType hint,
-                JS::MutableHandle<JS::Value> vp);
+/**
+ * Convert obj to a primitive value. On success, store the result in vp and
+ * return true.
+ *
+ * The hint argument must be JSTYPE_STRING, JSTYPE_NUMBER, or JSTYPE_VOID (no
+ * hint).
+ *
+ * Implements: ES6 7.1.1 ToPrimitive(input, [PreferredType]).
+ */
+extern JS_PUBLIC_API(bool)
+JS_DefaultValue(JSContext* cx, JS::HandleObject obj, JSType hint,
+                JS::MutableHandleValue vp);
+
+namespace JS {
+
+/**
+ * If args.get(0) is one of the strings "string", "number", or "default", set
+ * *result to JSTYPE_STRING, JSTYPE_NUMBER, or JSTYPE_VOID accordingly and
+ * return true. Otherwise, return false with a TypeError pending.
+ *
+ * This can be useful in implementing a @@toPrimitive method.
+ */
+extern JS_PUBLIC_API(bool)
+GetFirstArgumentAsTypeHint(JSContext* cx, CallArgs args, JSType *result);
+
+} /* namespace JS */
 
 extern JS_PUBLIC_API(bool)
 JS_PropertyStub(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                 JS::MutableHandleValue vp);
 
 extern JS_PUBLIC_API(bool)
 JS_StrictPropertyStub(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                       JS::MutableHandleValue vp, JS::ObjectOpResult& result);
@@ -2096,17 +2113,17 @@ struct JSFunctionSpec {
  * @@iterator method.
  */
 #define JS_FS(name,call,nargs,flags)                                          \
     JS_FNSPEC(name, call, nullptr, nargs, flags, nullptr)
 #define JS_FN(name,call,nargs,flags)                                          \
     JS_FNSPEC(name, call, nullptr, nargs, (flags) | JSFUN_STUB_GSOPS, nullptr)
 #define JS_INLINABLE_FN(name,call,nargs,flags,native)                         \
     JS_FNSPEC(name, call, &js::jit::JitInfo_##native, nargs, (flags) | JSFUN_STUB_GSOPS, nullptr)
-#define JS_SYM_FN(name,call,nargs,flags)                                      \
+#define JS_SYM_FN(symbol,call,nargs,flags)                                    \
     JS_SYM_FNSPEC(symbol, call, nullptr, nargs, (flags) | JSFUN_STUB_GSOPS, nullptr)
 #define JS_FNINFO(name,call,info,nargs,flags)                                 \
     JS_FNSPEC(name, call, info, nargs, flags, nullptr)
 #define JS_SELF_HOSTED_FN(name,selfHostedName,nargs,flags)                    \
     JS_FNSPEC(name, nullptr, nullptr, nargs, flags, selfHostedName)
 #define JS_SELF_HOSTED_SYM_FN(symbol, selfHostedName, nargs, flags)           \
     JS_SYM_FNSPEC(symbol, nullptr, nullptr, nargs, flags, selfHostedName)
 #define JS_SYM_FNSPEC(symbol, call, info, nargs, flags, selfHostedName)       \
@@ -4365,25 +4382,26 @@ GetSymbolFor(JSContext* cx, HandleString
  * This function is infallible. If it returns null, that means the symbol's
  * [[Description]] is undefined.
  */
 JS_PUBLIC_API(JSString*)
 GetSymbolDescription(HandleSymbol symbol);
 
 /* Well-known symbols. */
 enum class SymbolCode : uint32_t {
-    iterator,                       // Symbol.iterator
-    match,                          // Symbol.match
-    species,                        // Symbol.species
+    iterator,                       // well-known symbols
+    match,
+    species,
+    toPrimitive,
     InSymbolRegistry = 0xfffffffe,  // created by Symbol.for() or JS::GetSymbolFor()
     UniqueSymbol = 0xffffffff       // created by Symbol() or JS::NewSymbol()
 };
 
 /* For use in loops that iterate over the well-known symbols. */
-const size_t WellKnownSymbolLimit = 3;
+const size_t WellKnownSymbolLimit = 4;
 
 /*
  * Return the SymbolCode telling what sort of symbol `symbol` is.
  *
  * A symbol's SymbolCode never changes once it is created.
  */
 JS_PUBLIC_API(SymbolCode)
 GetSymbolCode(Handle<Symbol*> symbol);
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -513,25 +513,16 @@ MakeTime(double hour, double min, double
     /* Steps 6-7. */
     return h * msPerHour + m * msPerMinute + s * msPerSecond + milli;
 }
 
 /**
  * end of ECMA 'support' functions
  */
 
-static bool
-date_convert(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
-{
-    MOZ_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID);
-    MOZ_ASSERT(obj->is<DateObject>());
-
-    return JS::OrdinaryToPrimitive(cx, obj, hint == JSTYPE_VOID ? JSTYPE_STRING : hint, vp);
-}
-
 /* for use by date_parse */
 
 static const char* const wtb[] = {
     "am", "pm",
     "monday", "tuesday", "wednesday", "thursday", "friday",
     "saturday", "sunday",
     "january", "february", "march", "april", "may", "june",
     "july", "august", "september", "october", "november", "december",
@@ -2907,16 +2898,40 @@ date_valueOf_impl(JSContext* cx, const C
 
 bool
 js::date_valueOf(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, date_valueOf_impl>(cx, args);
 }
 
+// ES6 20.3.4.45 Date.prototype[@@toPrimitive]
+static bool
+date_toPrimitive(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Steps 1-2.
+    if (!args.thisv().isObject()) {
+        ReportIncompatible(cx, args);
+        return false;
+    }
+
+    // Steps 3-5.
+    JSType hint;
+    if (!GetFirstArgumentAsTypeHint(cx, args, &hint))
+        return false;
+    if (hint == JSTYPE_VOID)
+        hint = JSTYPE_STRING;
+
+    args.rval().set(args.thisv());
+    RootedObject obj(cx, &args.thisv().toObject());
+    return OrdinaryToPrimitive(cx, obj, hint, args.rval());
+}
+
 static const JSFunctionSpec date_static_methods[] = {
     JS_FN("UTC",                 date_UTC,                7,0),
     JS_FN("parse",               date_parse,              1,0),
     JS_FN("now",                 date_now,                0,0),
     JS_FS_END
 };
 
 static const JSFunctionSpec date_methods[] = {
@@ -2970,16 +2985,17 @@ static const JSFunctionSpec date_methods
     JS_FN("toTimeString",        date_toTimeString,       0,0),
     JS_FN("toISOString",         date_toISOString,        0,0),
     JS_FN(js_toJSON_str,         date_toJSON,             1,0),
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str,       date_toSource,           0,0),
 #endif
     JS_FN(js_toString_str,       date_toString,           0,0),
     JS_FN(js_valueOf_str,        date_valueOf,            0,0),
+    JS_SYM_FN(toPrimitive,       date_toPrimitive,        1,JSPROP_READONLY),
     JS_FS_END
 };
 
 static bool
 NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t)
 {
     JSObject* obj = NewDateObjectMsec(cx, t);
     if (!obj)
@@ -3161,17 +3177,17 @@ const Class DateObject::class_ = {
     JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
-    date_convert,
+    nullptr, /* convert */
     nullptr, /* finalize */
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     nullptr, /* trace */
     {
         GenericCreateConstructor<DateConstructor, 7, gc::AllocKind::FUNCTION>,
         CreateDatePrototype,
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -327,17 +327,17 @@ namespace js {
             flags,                                                                      \
         nullptr,                 /* addProperty */                                      \
         nullptr,                 /* delProperty */                                      \
         nullptr,                 /* getProperty */                                      \
         nullptr,                 /* setProperty */                                      \
         nullptr,                 /* enumerate */                                        \
         nullptr,                 /* resolve */                                          \
         nullptr,                 /* mayResolve */                                       \
-        js::proxy_Convert,                                                              \
+        nullptr,                 /* convert */                                          \
         js::proxy_Finalize,      /* finalize    */                                      \
         nullptr,                 /* call        */                                      \
         js::proxy_HasInstance,   /* hasInstance */                                      \
         nullptr,                 /* construct   */                                      \
         js::proxy_Trace,         /* trace       */                                      \
         JS_NULL_CLASS_SPEC,                                                             \
         ext,                                                                            \
         {                                                                               \
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2855,36 +2855,62 @@ js::HasDataProperty(JSContext* cx, Nativ
             *vp = obj->getSlot(shape->slot());
             return true;
         }
     }
 
     return false;
 }
 
+
+/*** ToPrimitive *************************************************************/
+
 /*
- * Gets |obj[id]|.  If that value's not callable, returns true and stores a
- * non-primitive value in *vp.  If it's callable, calls it with no arguments
- * and |obj| as |this|, returning the result in *vp.
+ * Gets |obj[id]|.  If that value's not callable, returns true and stores an
+ * object value in *vp.  If it's callable, calls it with no arguments and |obj|
+ * as |this|, returning the result in *vp.
  *
- * This is a mini-abstraction for ES5 8.12.8 [[DefaultValue]], either steps 1-2
- * or steps 3-4.
+ * This is a mini-abstraction for ES6 draft rev 36 (2015 Mar 17),
+ * 7.1.1, second algorithm (OrdinaryToPrimitive), steps 5.a-c.
  */
 static bool
 MaybeCallMethod(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
 {
     if (!GetProperty(cx, obj, obj, id, vp))
         return false;
     if (!IsCallable(vp)) {
         vp.setObject(*obj);
         return true;
     }
     return Invoke(cx, ObjectValue(*obj), vp, 0, nullptr, vp);
 }
 
+static bool
+ReportCantConvert(JSContext* cx, unsigned errorNumber, HandleObject obj, JSType hint)
+{
+    const Class* clasp = obj->getClass();
+
+    // Avoid recursive death when decompiling in ReportValueError.
+    RootedString str(cx);
+    if (hint == JSTYPE_STRING) {
+        str = JS_AtomizeAndPinString(cx, clasp->name);
+        if (!str)
+            return false;
+    } else {
+        str = nullptr;
+    }
+
+    RootedValue val(cx, ObjectValue(*obj));
+    ReportValueError2(cx, errorNumber, JSDVG_SEARCH_STACK, val, str,
+                      hint == JSTYPE_VOID
+                      ? "primitive type"
+                      : hint == JSTYPE_STRING ? "string" : "number");
+    return false;
+}
+
 bool
 JS::OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
 {
     MOZ_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID);
 
     Rooted<jsid> id(cx);
 
     const Class* clasp = obj->getClass();
@@ -2906,109 +2932,95 @@ JS::OrdinaryToPrimitive(JSContext* cx, H
             return true;
 
         id = NameToId(cx->names().valueOf);
         if (!MaybeCallMethod(cx, obj, id, vp))
             return false;
         if (vp.isPrimitive())
             return true;
     } else {
+        id = NameToId(cx->names().valueOf);
 
         /* Optimize new String(...).valueOf(). */
         if (clasp == &StringObject::class_) {
-            id = NameToId(cx->names().valueOf);
             StringObject* nobj = &obj->as<StringObject>();
             if (ClassMethodIsNative(cx, nobj, &StringObject::class_, id, str_toString)) {
                 vp.setString(nobj->unbox());
                 return true;
             }
         }
 
         /* Optimize new Number(...).valueOf(). */
         if (clasp == &NumberObject::class_) {
-            id = NameToId(cx->names().valueOf);
             NumberObject* nobj = &obj->as<NumberObject>();
             if (ClassMethodIsNative(cx, nobj, &NumberObject::class_, id, num_valueOf)) {
                 vp.setNumber(nobj->unbox());
                 return true;
             }
         }
 
-        id = NameToId(cx->names().valueOf);
         if (!MaybeCallMethod(cx, obj, id, vp))
             return false;
         if (vp.isPrimitive())
             return true;
 
         id = NameToId(cx->names().toString);
         if (!MaybeCallMethod(cx, obj, id, vp))
             return false;
         if (vp.isPrimitive())
             return true;
     }
 
-    /* Avoid recursive death when decompiling in ReportValueError. */
-    RootedString str(cx);
-    if (hint == JSTYPE_STRING) {
-        str = JS_AtomizeAndPinString(cx, clasp->name);
-        if (!str)
-            return false;
-    } else {
-        str = nullptr;
-    }
-
-    RootedValue val(cx, ObjectValue(*obj));
-    ReportValueError2(cx, JSMSG_CANT_CONVERT_TO, JSDVG_SEARCH_STACK, val, str,
-                      hint == JSTYPE_VOID
-                      ? "primitive type"
-                      : hint == JSTYPE_STRING ? "string" : "number");
-    return false;
+    return ReportCantConvert(cx, JSMSG_CANT_CONVERT_TO, obj, hint);
 }
 
 bool
-js::ToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
-{
-    bool ok;
-    if (JSConvertOp op = obj->getClass()->convert)
-        ok = op(cx, obj, hint, vp);
-    else
-        ok = JS::OrdinaryToPrimitive(cx, obj, hint, vp);
-    MOZ_ASSERT_IF(ok, vp.isPrimitive());
-    return ok;
-}
-
-bool
-js::ToPrimitiveSlow(JSContext* cx, MutableHandleValue vp)
+js::ToPrimitiveSlow(JSContext* cx, JSType preferredType, MutableHandleValue vp)
 {
-    JSObject* obj = &vp.toObject();
-
-    /* Optimize new String(...).valueOf(). */
-    if (obj->is<StringObject>()) {
-        jsid id = NameToId(cx->names().valueOf);
-        StringObject* nobj = &obj->as<StringObject>();
-        if (ClassMethodIsNative(cx, nobj, &StringObject::class_, id, str_toString)) {
-            vp.setString(nobj->unbox());
-            return true;
-        }
+    // Step numbers refer to the first algorithm listed in ES6 draft rev 36
+    // (2015 Mar 17) 7.1.1 ToPrimitive.
+    MOZ_ASSERT(preferredType == JSTYPE_VOID ||
+               preferredType == JSTYPE_STRING ||
+               preferredType == JSTYPE_NUMBER);
+    RootedObject obj(cx, &vp.toObject());
+
+    // Steps 4-5.
+    RootedId id(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().toPrimitive));
+    RootedValue method(cx);
+    if (!GetProperty(cx, obj, obj, id, &method))
+        return false;
+
+    // Step 6.
+    if (!method.isUndefined()) {
+        // Step 6 of GetMethod. Invoke() below would do this check and throw a
+        // TypeError anyway, but this produces a better error message.
+        if (!IsCallable(method))
+            return ReportCantConvert(cx, JSMSG_TOPRIMITIVE_NOT_CALLABLE, obj, preferredType);
+
+        // Steps 1-3.
+        RootedValue hint(cx, StringValue(preferredType == JSTYPE_STRING ? cx->names().string :
+                                         preferredType == JSTYPE_NUMBER ? cx->names().number :
+                                         cx->names().default_));
+
+        // Steps 6.a-b.
+        if (!Invoke(cx, vp, method, 1, hint.address(), vp))
+            return false;
+
+        // Steps 6.c-d.
+        if (vp.isObject())
+            return ReportCantConvert(cx, JSMSG_TOPRIMITIVE_RETURNED_OBJECT, obj, preferredType);
+        return true;
     }
 
-    /* Optimize new Number(...).valueOf(). */
-    if (obj->is<NumberObject>()) {
-        jsid id = NameToId(cx->names().valueOf);
-        NumberObject* nobj = &obj->as<NumberObject>();
-        if (ClassMethodIsNative(cx, nobj, &NumberObject::class_, id, num_valueOf)) {
-            vp.setNumber(nobj->unbox());
-            return true;
-        }
-    }
-
-    RootedObject objRoot(cx, obj);
-    return ToPrimitive(cx, objRoot, JSTYPE_VOID, vp);
+    return OrdinaryToPrimitive(cx, obj, preferredType, vp);
 }
 
+
+/* * */
+
 bool
 js::IsDelegate(JSContext* cx, HandleObject obj, const js::Value& v, bool* result)
 {
     if (v.isPrimitive()) {
         *result = false;
         return true;
     }
     return IsDelegateOfObject(cx, obj, &v.toObject(), result);
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1003,39 +1003,34 @@ HasOwnProperty(JSContext* cx, HandleObje
  */
 extern bool
 WatchProperty(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable);
 
 /* Clear a watchpoint. */
 extern bool
 UnwatchProperty(JSContext* cx, HandleObject obj, HandleId id);
 
-/* ES6 draft rev 36 (2015 March 17) 7.1.1 ToPrimitive(vp, preferredType) */
+/* ES6 draft rev 36 (2015 March 17) 7.1.1 ToPrimitive(vp[, preferredType]) */
 extern bool
-ToPrimitiveSlow(JSContext* cx, MutableHandleValue vp);
+ToPrimitiveSlow(JSContext* cx, JSType hint, MutableHandleValue vp);
 
 inline bool
 ToPrimitive(JSContext* cx, MutableHandleValue vp)
 {
     if (vp.isPrimitive())
         return true;
-    return ToPrimitiveSlow(cx, vp);
+    return ToPrimitiveSlow(cx, JSTYPE_VOID, vp);
 }
 
-extern bool
-ToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp);
-
 inline bool
 ToPrimitive(JSContext* cx, JSType preferredType, MutableHandleValue vp)
 {
-    MOZ_ASSERT(preferredType != JSTYPE_VOID);  // Use the other ToPrimitive!
     if (vp.isPrimitive())
         return true;
-    RootedObject obj(cx, &vp.toObject());
-    return ToPrimitive(cx, obj, preferredType, vp);
+    return ToPrimitiveSlow(cx, preferredType, vp);
 }
 
 /*
  * toString support. (This isn't called GetClassName because there's a macro in
  * <windows.h> with that name.)
  */
 extern const char*
 GetObjectClassName(JSContext* cx, HandleObject obj);
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -508,23 +508,16 @@ Proxy::regexp_toShared(JSContext* cx, Ha
 
 bool
 Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp)
 {
     JS_CHECK_RECURSION(cx, return false);
     return proxy->as<ProxyObject>().handler()->boxedValue_unbox(cx, proxy, vp);
 }
 
-bool
-Proxy::defaultValue(JSContext* cx, HandleObject proxy, JSType hint, MutableHandleValue vp)
-{
-    JS_CHECK_RECURSION(cx, return false);
-    return proxy->as<ProxyObject>().handler()->defaultValue(cx, proxy, hint, vp);
-}
-
 JSObject * const TaggedProto::LazyProto = reinterpret_cast<JSObject*>(0x1);
 
 /* static */ bool
 Proxy::watch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleObject callable)
 {
     JS_CHECK_RECURSION(cx, return false);
     return proxy->as<ProxyObject>().handler()->watch(cx, proxy, id, callable);
 }
@@ -675,23 +668,16 @@ ProxyObject::trace(JSTracer* trc, JSObje
 
 JSObject*
 js::proxy_WeakmapKeyDelegate(JSObject* obj)
 {
     MOZ_ASSERT(obj->is<ProxyObject>());
     return obj->as<ProxyObject>().handler()->weakmapKeyDelegate(obj);
 }
 
-bool
-js::proxy_Convert(JSContext* cx, HandleObject proxy, JSType hint, MutableHandleValue vp)
-{
-    MOZ_ASSERT(proxy->is<ProxyObject>());
-    return Proxy::defaultValue(cx, proxy, hint, vp);
-}
-
 void
 js::proxy_Finalize(FreeOp* fop, JSObject* obj)
 {
     // Suppress a bogus warning about finalize().
     JS::AutoSuppressGCAnalysis nogc;
 
     MOZ_ASSERT(obj->is<ProxyObject>());
     obj->as<ProxyObject>().handler()->finalize(fop, obj);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Date/toPrimitive.js
@@ -0,0 +1,62 @@
+// ES6 20.3.4.45 Date.prototype[@@toPrimitive](hint)
+
+// The toPrimitive method throws if the this value isn't an object.
+var toPrimitive = Date.prototype[Symbol.toPrimitive];
+assertThrowsInstanceOf(() => toPrimitive.call(undefined, "default"), TypeError);
+assertThrowsInstanceOf(() => toPrimitive.call(3, "default"), TypeError);
+
+// It doesn't have to be a Date object, though.
+var obj = {
+    toString() { return "str"; },
+    valueOf() { return "val"; }
+};
+assertEq(toPrimitive.call(obj, "number"), "val");
+assertEq(toPrimitive.call(obj, "string"), "str");
+assertEq(toPrimitive.call(obj, "default"), "str");
+
+// It throws if the hint argument is missing or not one of the three allowed values.
+assertThrowsInstanceOf(() => toPrimitive.call(obj), TypeError);
+assertThrowsInstanceOf(() => toPrimitive.call(obj, undefined), TypeError);
+assertThrowsInstanceOf(() => toPrimitive.call(obj, "boolean"), TypeError);
+assertThrowsInstanceOf(() => toPrimitive.call(obj, ["number"]), TypeError);
+assertThrowsInstanceOf(() => toPrimitive.call(obj, {toString() { throw "FAIL"; }}), TypeError);
+
+// The next few tests cover the OrdinaryToPrimitive algorithm, specified in
+// ES6 7.1.1 ToPrimitive(input [, PreferredType]).
+
+// Date.prototype.toString or .valueOf can be overridden.
+var dateobj = new Date();
+Date.prototype.toString = function () {
+    assertEq(this, dateobj);
+    return 14;
+};
+Date.prototype.valueOf = function () {
+    return "92";
+};
+assertEq(dateobj[Symbol.toPrimitive]("number"), "92");
+assertEq(dateobj[Symbol.toPrimitive]("string"), 14);
+assertEq(dateobj[Symbol.toPrimitive]("default"), 14);
+assertEq(dateobj == 14, true);  // equality comparison: passes "default"
+
+// If this.toString is a non-callable value, this.valueOf is called instead.
+Date.prototype.toString = {};
+assertEq(dateobj[Symbol.toPrimitive]("string"), "92");
+assertEq(dateobj[Symbol.toPrimitive]("default"), "92");
+
+// And vice versa.
+Date.prototype.toString = function () { return 15; };
+Date.prototype.valueOf = "ponies";
+assertEq(dateobj[Symbol.toPrimitive]("number"), 15);
+
+// If neither is callable, it throws a TypeError.
+Date.prototype.toString = "ponies";
+assertThrowsInstanceOf(() => dateobj[Symbol.toPrimitive]("default"), TypeError);
+
+// Surface features.
+assertEq(toPrimitive.name, "[Symbol.toPrimitive]");
+var desc = Object.getOwnPropertyDescriptor(Date.prototype, Symbol.toPrimitive);
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, false);
+assertEq(desc.writable, false);
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Object/toPrimitive-callers.js
@@ -0,0 +1,57 @@
+// Check all the algorithms that call ToPrimitive. Confirm that they're passing
+// the correct hint, per spec.
+
+var STRING = "xyzzy";
+var NUMBER = 42;
+
+function assertCallsToPrimitive(f, expectedHint, expectedResult) {
+    var hint = undefined;
+    var testObj = {
+        [Symbol.toPrimitive](h) {
+            assertEq(hint, undefined);
+            hint = h;
+            return h === "number" ? NUMBER : STRING;
+        }
+    };
+    var result = f(testObj);
+    assertEq(hint, expectedHint, String(f));
+    assertEq(result, expectedResult, String(f));
+}
+
+// ToNumber
+assertCallsToPrimitive(Number, "number", NUMBER);
+
+// ToString
+assertCallsToPrimitive(String, "string", STRING);
+
+// ToPropertyKey
+var obj = {[STRING]: "pass"};
+assertCallsToPrimitive(key => obj[key], "string", "pass");
+
+// Abstract Relational Comparison
+assertCallsToPrimitive(x => x >= 42, "number", true);
+assertCallsToPrimitive(x => x > "42", "number", false);
+
+// Abstract Equality Comparison
+assertCallsToPrimitive(x => x != STRING, "default", false);
+assertCallsToPrimitive(x => STRING == x, "default", true);
+assertCallsToPrimitive(x => x == NUMBER, "default", false);
+assertCallsToPrimitive(x => NUMBER != x, "default", true);
+
+// Addition
+assertCallsToPrimitive(x => 1 + x, "default", "1" + STRING);
+assertCallsToPrimitive(x => "" + x, "default", STRING);
+
+// Date constructor
+assertCallsToPrimitive(x => (new Date(x)).valueOf(), "default", Number(STRING));
+
+// Date.prototype.toJSON
+var expected = "a suffusion of yellow";
+function testJSON(x) {
+    x.toJSON = Date.prototype.toJSON;
+    x.toISOString = function () { return expected; };
+    return JSON.stringify(x);
+}
+assertCallsToPrimitive(testJSON, "number", JSON.stringify(expected));
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Object/toPrimitive.js
@@ -0,0 +1,101 @@
+// ES6 7.1.1 ToPrimitive(input [, PreferredType]) specifies a new extension
+// point in the language. Objects can override the behavior of ToPrimitive
+// somewhat by supporting the method obj[@@toPrimitive](hint).
+//
+// (Rationale: ES5 had a [[DefaultValue]] internal method, overridden only by
+// Date objects. The change in ES6 is to make [[DefaultValue]] a plain old
+// method. This allowed ES6 to eliminate the [[DefaultValue]] internal method,
+// simplifying the meta-object protocol and thus proxies.)
+
+// obj[Symbol.toPrimitive]() is called whenever the ToPrimitive algorithm is invoked.
+var expectedThis, expectedHint;
+var obj = {
+    [Symbol.toPrimitive](hint, ...rest) {
+        assertEq(this, expectedThis);
+        assertEq(hint, expectedHint);
+        assertEq(rest.length, 0);
+        return 2015;
+    }
+};
+expectedThis = obj;
+expectedHint = "string";
+assertEq(String(obj), "2015");
+expectedHint = "number";
+assertEq(Number(obj), 2015);
+
+// It is called even through proxies.
+var proxy = new Proxy(obj, {});
+expectedThis = proxy;
+expectedHint = "default";
+assertEq("ES" + proxy, "ES2015");
+
+// It is called even through additional proxies and the prototype chain.
+proxy = new Proxy(Object.create(proxy), {});
+expectedThis = proxy;
+expectedHint = "default";
+assertEq("ES" + (proxy + 1), "ES2016");
+
+// It is not called if the operand is already a primitive.
+var ok = true;
+for (var constructor of [Boolean, Number, String, Symbol]) {
+    constructor.prototype[Symbol.toPrimitive] = function () {
+        ok = false;
+        throw "FAIL";
+    };
+}
+assertEq(Number(true), 1);
+assertEq(Number(77.7), 77.7);
+assertEq(Number("123"), 123);
+assertThrowsInstanceOf(() => Number(Symbol.iterator), TypeError);
+assertEq(String(true), "true");
+assertEq(String(77.7), "77.7");
+assertEq(String("123"), "123");
+assertEq(String(Symbol.iterator), "Symbol(Symbol.iterator)");
+assertEq(ok, true);
+
+// Converting a primitive symbol to another primitive type throws even if you
+// delete the @@toPrimitive method from Symbol.prototype.
+delete Symbol.prototype[Symbol.toPrimitive];
+var sym = Symbol("ok");
+assertThrowsInstanceOf(() => `${sym}`, TypeError);
+assertThrowsInstanceOf(() => Number(sym), TypeError);
+assertThrowsInstanceOf(() => "" + sym, TypeError);
+
+// However, having deleted that method, converting a Symbol wrapper object does
+// work: it calls Symbol.prototype.toString().
+obj = Object(sym);
+assertEq(String(obj), "Symbol(ok)");
+assertEq(`${obj}`, "Symbol(ok)");
+
+// Deleting valueOf as well makes numeric conversion also call toString().
+delete Symbol.prototype.valueOf;
+delete Object.prototype.valueOf;
+assertEq(Number(obj), NaN);
+Symbol.prototype.toString = function () { return "2060"; };
+assertEq(Number(obj), 2060);
+
+// Deleting Date.prototype[Symbol.toPrimitive] changes the result of addition
+// involving Date objects.
+var d = new Date;
+assertEq(0 + d, 0 + d.toString());
+delete Date.prototype[Symbol.toPrimitive];
+assertEq(0 + d, 0 + d.valueOf());
+
+// If @@toPrimitive, .toString, and .valueOf are all missing, we get a
+// particular sequence of property accesses, followed by a TypeError exception.
+var log = [];
+function doGet(target, propertyName, receiver) {
+    log.push(propertyName);
+}
+var handler = new Proxy({}, {
+    get(target, trapName, receiver) {
+        if (trapName !== "get")
+            throw `FAIL: system tried to access handler method: ${uneval(trapName)}`;
+        return doGet;
+    }
+});
+proxy = new Proxy(Object.create(null), handler);
+assertThrowsInstanceOf(() => proxy == 0, TypeError);
+assertDeepEq(log, [Symbol.toPrimitive, "valueOf", "toString"]);
+
+reportCompare(0, 0);
--- a/js/src/tests/ecma_6/Reflect/propertyKeys.js
+++ b/js/src/tests/ecma_6/Reflect/propertyKeys.js
@@ -33,24 +33,25 @@ var keys = [
         expected: "key"
     },
     {
         value: {
             toString: undefined,
             valueOf() { return "fallback"; }
         },
         expected: "fallback"
+    },
+    {
+        value: {
+            [Symbol.toPrimitive](hint) { return hint; }
+        },
+        expected: "string"
     }
 ];
 
-if ("toPrimitive" in Symbol) {
-    throw new Error("Congratulations on implementing Symbol.toPrimitive! " +
-                    "Please add an object with an @@toPrimitive method in the list above.");
-}
-
 for (var {value, expected} of keys) {
     if (expected === undefined)
         expected = value;
 
     var obj = {};
     assertEq(Reflect.defineProperty(obj, value, {value: 1, configurable: true}), true);
     assertDeepEq(Reflect.ownKeys(obj), [expected]);
     assertDeepEq(Reflect.getOwnPropertyDescriptor(obj, value),
--- a/js/src/tests/ecma_6/Symbol/conversions.js
+++ b/js/src/tests/ecma_6/Symbol/conversions.js
@@ -2,65 +2,88 @@
 
 var symbols = [
     Symbol(),
     Symbol("one"),
     Symbol.for("two"),
     Symbol.iterator
 ];
 
-if (Symbol.toPrimitive in Symbol.prototype) {
-    // We should test that deleting Symbol.prototype[@@toPrimitive] changes the
-    // behavior of ToPrimitive on Symbol objects, but @@toPrimitive is not
-    // implemented yet.
-    throw new Error("Congratulations on implementing @@toPrimitive! Please update this test.");
-}
-
-for (var sym of symbols) {
-    // 7.1.1 ToPrimitive
-    var symobj = Object(sym);
-    assertThrowsInstanceOf(() => Number(symobj), TypeError);
-    assertThrowsInstanceOf(() => String(symobj), TypeError);
-    assertThrowsInstanceOf(() => symobj < 0, TypeError);
-    assertThrowsInstanceOf(() => 0 < symobj, TypeError);
-    assertThrowsInstanceOf(() => symobj + 1, TypeError);
-    assertThrowsInstanceOf(() => "" + symobj, TypeError);
-    assertEq(sym == symobj, true);
-    assertEq(sym === symobj, false);
-    assertEq(symobj == 0, false);
-    assertEq(0 != symobj, true);
-
+function testSymbolConversions(sym) {
     // 7.1.2 ToBoolean
     assertEq(Boolean(sym), true);
     assertEq(!sym, false);
     assertEq(sym || 13, sym);
     assertEq(sym && 13, 13);
 
     // 7.1.3 ToNumber
     assertThrowsInstanceOf(() => +sym, TypeError);
     assertThrowsInstanceOf(() => sym | 0, TypeError);
 
     // 7.1.12 ToString
     assertThrowsInstanceOf(() => "" + sym, TypeError);
     assertThrowsInstanceOf(() => sym + "", TypeError);
-    assertThrowsInstanceOf(() => "" + [1, 2, Symbol()], TypeError);
-    assertThrowsInstanceOf(() => ["simple", "thimble", Symbol()].join(), TypeError);
+    assertThrowsInstanceOf(() => "" + [1, 2, sym], TypeError);
+    assertThrowsInstanceOf(() => ["simple", "thimble", sym].join(), TypeError);
 
     // 21.1.1.1 String()
     assertEq(String(sym), sym.toString());
-    assertThrowsInstanceOf(() => String(Object(sym)), TypeError);
 
     // 21.1.1.2 new String()
     assertThrowsInstanceOf(() => new String(sym), TypeError);
 
     // 7.1.13 ToObject
     var obj = Object(sym);
     assertEq(typeof obj, "object");
     assertEq(Object.prototype.toString.call(obj), "[object Symbol]");
     assertEq(Object.getPrototypeOf(obj), Symbol.prototype);
     assertEq(Object.getOwnPropertyNames(obj).length, 0);
     assertEq(Object(sym) === Object(sym), false);  // new object each time
     var f = function () { return this; };
     assertEq(f.call(sym) === f.call(sym), false);  // new object each time
 }
 
+
+for (var sym of symbols) {
+    testSymbolConversions(sym);
+
+    // 7.1.1 ToPrimitive
+    var symobj = Object(sym);
+    assertThrowsInstanceOf(() => Number(symobj), TypeError);
+    assertThrowsInstanceOf(() => String(symobj), TypeError);
+    assertThrowsInstanceOf(() => symobj < 0, TypeError);
+    assertThrowsInstanceOf(() => 0 < symobj, TypeError);
+    assertThrowsInstanceOf(() => symobj + 1, TypeError);
+    assertThrowsInstanceOf(() => "" + symobj, TypeError);
+    assertEq(sym == symobj, true);
+    assertEq(sym === symobj, false);
+    assertEq(symobj == 0, false);
+    assertEq(0 != symobj, true);
+
+    // 7.1.12 ToString
+    assertThrowsInstanceOf(() => String(Object(sym)), TypeError);
+}
+
+// Deleting Symbol.prototype[@@toPrimitive] does not change the behavior of
+// conversions from a symbol to other types.
+delete Symbol.prototype[Symbol.toPrimitive];
+assertEq(Symbol.toPrimitive in Symbol.prototype, false);
+testSymbolConversions(symbols[0]);
+
+// It does change the behavior of ToPrimitive on Symbol objects, though.
+// It causes the default algorithm (OrdinaryToPrimitive) to be used.
+var VALUEOF_CALLED = 117.25;
+Symbol.prototype.valueOf =  function () { return VALUEOF_CALLED; };
+Symbol.prototype.toString = function () { return "toString called"; };
+for (var sym of symbols) {
+    var symobj = Object(sym);
+    assertEq(Number(symobj), VALUEOF_CALLED);
+    assertEq(String(symobj), "toString called");
+    assertEq(symobj < 0, VALUEOF_CALLED < 0);
+    assertEq(0 < symobj, 0 < VALUEOF_CALLED);
+    assertEq(symobj + 1, VALUEOF_CALLED + 1);
+    assertEq("" + symobj, "" + VALUEOF_CALLED);
+    assertEq(symobj == 0, false);
+    assertEq(0 != symobj, true);
+}
+
 if (typeof reportCompare === "function")
     reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Symbol/toPrimitive.js
@@ -0,0 +1,39 @@
+// ES6 19.4.3.4 Symbol.prototype[@@toPrimitive](hint)
+
+// This method gets the primitive symbol from a Symbol wrapper object.
+var sym = Symbol.for("truth")
+var obj = Object(sym);
+assertEq(obj[Symbol.toPrimitive]("default"), sym);
+
+// The hint argument is ignored.
+assertEq(obj[Symbol.toPrimitive]("number"), sym);
+assertEq(obj[Symbol.toPrimitive]("string"), sym);
+assertEq(obj[Symbol.toPrimitive](), sym);
+assertEq(obj[Symbol.toPrimitive](Math.atan2), sym);
+
+// The this value can also be a primitive symbol.
+assertEq(sym[Symbol.toPrimitive](), sym);
+
+// Or a wrapper to a Symbol object in another compartment.
+var obj2 = newGlobal().Object(sym);
+assertEq(obj2[Symbol.toPrimitive]("default"), sym);
+
+// Otherwise a TypeError is thrown.
+var symbolToPrimitive = Symbol.prototype[Symbol.toPrimitive];
+var nonSymbols = [
+    undefined, null, true, 13, NaN, "justice", {}, [sym],
+    symbolToPrimitive,
+    new Proxy(obj, {})
+];
+for (var value of nonSymbols) {
+    assertThrowsInstanceOf(() => symbolToPrimitive.call(value, "string"), TypeError);
+}
+
+// Surface features:
+assertEq(symbolToPrimitive.name, "[Symbol.toPrimitive]");
+var desc = Object.getOwnPropertyDescriptor(Symbol.prototype, Symbol.toPrimitive);
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, false);
+assertEq(desc.writable, false);
+
+reportCompare(0, 0);
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -262,18 +262,18 @@
     macro(boolean, boolean, "boolean") \
     macro(null, null, "null") \
     macro(symbol, symbol, "symbol") \
     /* Well-known atom names must be continuous and ordered, matching \
      * enum JS::SymbolCode in jsapi.h. */ \
     macro(iterator, iterator, "iterator") \
     macro(match, match, "match") \
     macro(species, species, "species") \
+    macro(toPrimitive, toPrimitive, "toPrimitive") \
     /* Same goes for the descriptions of the well-known symbols. */ \
-    macro(Symbol_create, Symbol_create, "Symbol.create") \
     macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
     macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \
     macro(Symbol_iterator, Symbol_iterator, "Symbol.iterator") \
     macro(Symbol_match,    Symbol_match,    "Symbol.match") \
     macro(Symbol_species,  Symbol_species,  "Symbol.species") \
     macro(Symbol_toPrimitive, Symbol_toPrimitive, "Symbol.toPrimitive") \
     macro(Symbol_toStringTag, Symbol_toStringTag, "Symbol.toStringTag") \
     macro(Symbol_unscopables, Symbol_unscopables, "Symbol.unscopables") \
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -439,16 +439,17 @@ namespace js {
  * Well-known symbols are never GC'd. The description() of each well-known
  * symbol is a permanent atom.
  */
 struct WellKnownSymbols
 {
     js::ImmutableSymbolPtr iterator;
     js::ImmutableSymbolPtr match;
     js::ImmutableSymbolPtr species;
+    js::ImmutableSymbolPtr toPrimitive;
 
     const ImmutableSymbolPtr& get(size_t u) const {
         MOZ_ASSERT(u < JS::WellKnownSymbolLimit);
         const ImmutableSymbolPtr* symbols = reinterpret_cast<const ImmutableSymbolPtr*>(this);
         return symbols[u];
     }
 
     const ImmutableSymbolPtr& get(JS::SymbolCode code) const {
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -24,21 +24,21 @@ namespace js {
  * versions.  If deserialization fails, the data should be invalidated if
  * possible.
  *
  * When you change this, run make_opcode_doc.py and copy the new output into
  * this wiki page:
  *
  *  https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
  */
-static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 309;
+static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 310;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
-static_assert(JSErr_Limit == 413,
+static_assert(JSErr_Limit == 415,
               "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
               "removed MSG_DEFs from js.msg, you should increment "
               "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "
               "expected JSErr_Limit value.");
 
 class XDRBuffer {
   public:
     explicit XDRBuffer(JSContext* cx)
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -436,27 +436,16 @@ sandbox_moved(JSObject* obj, const JSObj
     // do.
     nsIScriptObjectPrincipal* sop =
         static_cast<nsIScriptObjectPrincipal*>(xpc_GetJSPrivate(obj));
     if (sop)
         static_cast<SandboxPrivate*>(sop)->ObjectMoved(obj, old);
 }
 
 static bool
-sandbox_convert(JSContext* cx, HandleObject obj, JSType type, MutableHandleValue vp)
-{
-    if (type == JSTYPE_OBJECT) {
-        vp.setObject(*obj);
-        return true;
-    }
-
-    return OrdinaryToPrimitive(cx, obj, type, vp);
-}
-
-static bool
 writeToProto_setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                          JS::MutableHandleValue vp, JS::ObjectOpResult& result)
 {
     RootedObject proto(cx);
     if (!JS_GetPrototype(cx, obj, &proto))
         return false;
 
     RootedValue receiver(cx, ObjectValue(*proto));
@@ -564,17 +553,18 @@ sandbox_addProperty(JSContext* cx, Handl
 #define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)
 
 static const js::Class SandboxClass = {
     "Sandbox",
     XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
     nullptr, nullptr, nullptr, nullptr,
     sandbox_enumerate, sandbox_resolve,
     nullptr,        /* mayResolve */
-    sandbox_convert,  sandbox_finalize,
+    nullptr,        /* convert */
+    sandbox_finalize,
     nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
     JS_NULL_CLASS_SPEC,
     {
       nullptr,      /* outerObject */
       nullptr,      /* innerObject */
       false,        /* isWrappedNative */
       nullptr,      /* weakmapKeyDelegateOp */
       sandbox_moved /* objectMovedOp */
@@ -585,17 +575,18 @@ static const js::Class SandboxClass = {
 // Note to whomever comes here to remove addProperty hooks: billm has promised
 // to do the work for this class.
 static const js::Class SandboxWriteToProtoClass = {
     "Sandbox",
     XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
     sandbox_addProperty, nullptr, nullptr, nullptr,
     sandbox_enumerate, sandbox_resolve,
     nullptr,        /* mayResolve */
-    sandbox_convert,  sandbox_finalize,
+    nullptr,        /* convert */
+    sandbox_finalize,
     nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
     JS_NULL_CLASS_SPEC,
     {
       nullptr,      /* outerObject */
       nullptr,      /* innerObject */
       false,        /* isWrappedNative */
       nullptr,      /* weakmapKeyDelegateOp */
       sandbox_moved /* objectMovedOp */
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -771,17 +771,16 @@ XPCWrappedNative::Init(const XPCNativeSc
 
     // We should have the global jsclass flag if and only if we're a global.
     MOZ_ASSERT_IF(si, !!si->GetFlags().IsGlobalObject() == !!(jsclazz->flags & JSCLASS_IS_GLOBAL));
 
     MOZ_ASSERT(jsclazz &&
                jsclazz->name &&
                jsclazz->flags &&
                jsclazz->resolve &&
-               jsclazz->convert &&
                jsclazz->finalize, "bad class");
 
     // XXXbz JS_GetObjectPrototype wants an object, even though it then asserts
     // that this object is same-compartment with cx, which means it could just
     // use the cx global...
     RootedObject global(cx, CurrentGlobalOrNull(cx));
     RootedObject protoJSObject(cx, HasProto() ?
                                    GetProto()->GetJSProtoObject() :
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -91,16 +91,54 @@ XPC_WN_Shared_ToSource(JSContext* cx, un
     JSString* str = JS_NewStringCopyN(cx, empty, sizeof(empty)-1);
     if (!str)
         return false;
     args.rval().setString(str);
 
     return true;
 }
 
+static bool
+XPC_WN_Shared_toPrimitive(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedObject obj(cx);
+    if (!JS_ValueToObject(cx, args.thisv(), &obj))
+        return false;
+    XPCCallContext ccx(JS_CALLER, cx, obj);
+    XPCWrappedNative* wrapper = ccx.GetWrapper();
+    THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
+
+    JSType hint;
+    if (!GetFirstArgumentAsTypeHint(cx, args, &hint))
+        return false;
+
+    if (hint == JSTYPE_NUMBER) {
+        args.rval().set(JS_GetNaNValue(cx));
+        return true;
+    }
+
+    MOZ_ASSERT(hint == JSTYPE_STRING || hint == JSTYPE_VOID);
+    ccx.SetName(ccx.GetRuntime()->GetStringID(XPCJSRuntime::IDX_TO_STRING));
+    ccx.SetArgsAndResultPtr(0, nullptr, args.rval().address());
+
+    XPCNativeMember* member = ccx.GetMember();
+    if (member && member->IsMethod()) {
+        if (!XPCWrappedNative::CallMethod(ccx))
+            return false;
+
+        if (args.rval().isPrimitive())
+            return true;
+    }
+
+    // else...
+    return ToStringGuts(ccx);
+}
+
 /***************************************************************************/
 
 // A "double wrapped object" is a user JSObject that has been wrapped as a
 // wrappedJS in order to be used by native code and then re-wrapped by a
 // wrappedNative wrapper to be used by JS code. One might think of it as:
 //    wrappedNative(wrappedJS(underlying_JSObject))
 // This is done (as opposed to just unwrapping the wrapped JS and automatically
 // returning the underlying JSObject) so that JS callers will see what looks
@@ -228,26 +266,28 @@ DefinePropertyIfFound(XPCCallContext& cc
             bool overwriteToString = !(flags & nsIClassInfo::DOM_OBJECT)
                 || Preferences::GetBool("dom.XPCToStringForDOMClasses", false);
 
             if(id == rt->GetStringID(XPCJSRuntime::IDX_TO_STRING)
                 && overwriteToString)
             {
                 call = XPC_WN_Shared_ToString;
                 name = rt->GetStringName(XPCJSRuntime::IDX_TO_STRING);
-                id   = rt->GetStringID(XPCJSRuntime::IDX_TO_STRING);
             } else if (id == rt->GetStringID(XPCJSRuntime::IDX_TO_SOURCE)) {
                 call = XPC_WN_Shared_ToSource;
                 name = rt->GetStringName(XPCJSRuntime::IDX_TO_SOURCE);
-                id   = rt->GetStringID(XPCJSRuntime::IDX_TO_SOURCE);
+            } else if (id == SYMBOL_TO_JSID(
+                               JS::GetWellKnownSymbol(ccx, JS::SymbolCode::toPrimitive)))
+            {
+                call = XPC_WN_Shared_toPrimitive;
+                name = "[Symbol.toPrimitive]";
+            } else {
+                call = nullptr;
             }
 
-            else
-                call = nullptr;
-
             if (call) {
                 RootedFunction fun(ccx, JS_NewFunction(ccx, call, 0, 0, name));
                 if (!fun) {
                     JS_ReportOutOfMemory(ccx);
                     return false;
                 }
 
                 AutoResolveName arn(ccx, id);
@@ -446,73 +486,16 @@ XPC_WN_CantDeletePropertyStub(JSContext*
 static bool
 XPC_WN_CannotModifySetPropertyStub(JSContext* cx, HandleObject obj, HandleId id,
                                    MutableHandleValue vp, ObjectOpResult& result)
 {
     return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx);
 }
 
 static bool
-XPC_WN_Shared_Convert(JSContext* cx, HandleObject obj, JSType type, MutableHandleValue vp)
-{
-    if (type == JSTYPE_OBJECT) {
-        vp.setObject(*obj);
-        return true;
-    }
-
-    XPCCallContext ccx(JS_CALLER, cx, obj);
-    XPCWrappedNative* wrapper = ccx.GetWrapper();
-    THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
-
-    switch (type) {
-        case JSTYPE_FUNCTION:
-            {
-                if (!ccx.GetTearOff()) {
-                    XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo();
-                    if (si && (si->GetFlags().WantCall() ||
-                               si->GetFlags().WantConstruct())) {
-                        vp.setObject(*obj);
-                        return true;
-                    }
-                }
-            }
-            return Throw(NS_ERROR_XPC_CANT_CONVERT_WN_TO_FUN, cx);
-        case JSTYPE_NUMBER:
-            vp.set(JS_GetNaNValue(cx));
-            return true;
-        case JSTYPE_BOOLEAN:
-            vp.setBoolean(true);
-            return true;
-        case JSTYPE_VOID:
-        case JSTYPE_STRING:
-        {
-            ccx.SetName(ccx.GetRuntime()->GetStringID(XPCJSRuntime::IDX_TO_STRING));
-            ccx.SetArgsAndResultPtr(0, nullptr, vp.address());
-
-            XPCNativeMember* member = ccx.GetMember();
-            if (member && member->IsMethod()) {
-                if (!XPCWrappedNative::CallMethod(ccx))
-                    return false;
-
-                if (vp.isPrimitive())
-                    return true;
-            }
-
-            // else...
-            return ToStringGuts(ccx);
-        }
-        default:
-            NS_ERROR("bad type in conversion");
-            return false;
-    }
-    NS_NOTREACHED("huh?");
-    return false;
-}
-
-static bool
 XPC_WN_Shared_Enumerate(JSContext* cx, HandleObject obj)
 {
     XPCCallContext ccx(JS_CALLER, cx, obj);
     XPCWrappedNative* wrapper = ccx.GetWrapper();
     THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
 
     // Since we aren't going to enumerate tearoff names and the prototype
     // handles non-mutated members, we can do this potential short-circuit.
@@ -647,17 +630,17 @@ const XPCWrappedNativeJSClass XPC_WN_NoH
     XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty
     XPC_WN_CantDeletePropertyStub,     // delProperty
     nullptr,                           // getProperty
     nullptr,                           // setProperty
 
     XPC_WN_Shared_Enumerate,           // enumerate
     XPC_WN_NoHelper_Resolve,           // resolve
     nullptr,                           // mayResolve
-    XPC_WN_Shared_Convert,             // convert
+    nullptr,                           // convert
     XPC_WN_NoHelper_Finalize,          // finalize
 
     /* Optionally non-null members start here. */
     nullptr,                         // call
     nullptr,                         // construct
     nullptr,                         // hasInstance
     XPCWrappedNative::Trace,         // trace
     JS_NULL_CLASS_SPEC,
@@ -1041,18 +1024,16 @@ XPCNativeScriptableShared::PopulateJSCla
     else if (mFlags.WantEnumerate())
         mJSClass.base.enumerate = XPC_WN_Helper_Enumerate;
     else
         mJSClass.base.enumerate = XPC_WN_Shared_Enumerate;
 
     // We have to figure out resolve strategy at call time
     mJSClass.base.resolve = XPC_WN_Helper_Resolve;
 
-    mJSClass.base.convert = XPC_WN_Shared_Convert;
-
     if (mFlags.WantFinalize())
         mJSClass.base.finalize = XPC_WN_Helper_Finalize;
     else
         mJSClass.base.finalize = XPC_WN_NoHelper_Finalize;
 
     js::ObjectOps* ops = &mJSClass.base.ops;
     if (mFlags.WantNewEnumerate())
         ops->enumerate = XPC_WN_JSOp_Enumerate;
@@ -1516,17 +1497,17 @@ const js::Class XPC_WN_Tearoff_JSClass =
     JSCLASS_HAS_RESERVED_SLOTS(XPC_WN_TEAROFF_RESERVED_SLOTS), // flags;
     XPC_WN_OnlyIWrite_AddPropertyStub,         // addProperty;
     XPC_WN_CantDeletePropertyStub,             // delProperty;
     nullptr,                                   // getProperty;
     nullptr,                                   // setProperty;
     XPC_WN_TearOff_Enumerate,                  // enumerate;
     XPC_WN_TearOff_Resolve,                    // resolve;
     nullptr,                                   // mayResolve;
-    XPC_WN_Shared_Convert,                     // convert;
+    nullptr,                                   // convert;
     XPC_WN_TearOff_Finalize,                   // finalize;
 
     /* Optionally non-null members start here. */
     nullptr,                                   // call
     nullptr,                                   // construct
     nullptr,                                   // hasInstance
     nullptr,                                   // trace
     JS_NULL_CLASS_SPEC,
--- a/js/xpconnect/tests/chrome/test_bug1042436.xul
+++ b/js/xpconnect/tests/chrome/test_bug1042436.xul
@@ -35,15 +35,15 @@ https://bugzilla.mozilla.org/show_bug.cg
       var contentObjWithGetter = contentSb.eval('({ get getterProp() {return 42;}, valueProp: 42 })');
       is(contentObjWithGetter.wrappedJSObject.getterProp, 42, "Getter prop set up correctly");
       is(contentObjWithGetter.getterProp, undefined, "Xrays work right");
       is(contentObjWithGetter.valueProp, 42, "Getter prop set up correctly");
       chromeSb.contentObjWithGetter = contentObjWithGetter;
       Cu.evalInSandbox('contentObjWithGetter.getterProp; contentObjWithGetter.valueProp; contentObjWithGetter.getterProp;',
                        chromeSb, "1.7", "http://phony.example.com/file.js", 99);
   },
-  [{ errorMessage: /property someExpandoProperty \(reason: object is not safely Xrayable/, sourceName: /test_bug1042436/, isWarning: true },
-   { errorMessage: /property getterProp \(reason: property has accessor/, sourceName: /phony/, lineNumber: 99, isWarning: true } ],
+  [{ errorMessage: /property "someExpandoProperty" \(reason: object is not safely Xrayable/, sourceName: /test_bug1042436/, isWarning: true },
+   { errorMessage: /property "getterProp" \(reason: property has accessor/, sourceName: /phony/, lineNumber: 99, isWarning: true } ],
   SimpleTest.finish.bind(SimpleTest));
 
   ]]>
   </script>
 </window>
--- a/js/xpconnect/tests/chrome/test_bug1065185.html
+++ b/js/xpconnect/tests/chrome/test_bug1065185.html
@@ -28,17 +28,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     switch(++gLoadCount) {
       case 0:
         doMonitor([]);
         window[0].wrappedJSObject.probe = { a: 2, __exposedProps__: { 'a': 'r' } };
         is(window[0].eval('probe.a'), 2, "Accessed exposed prop");
         endMonitor();
         break;
       case 1:
-        doMonitor([/access to property a/i]);
+        doMonitor([/access to property "a"/i]);
         window[0].wrappedJSObject.probe = { a: 2 };
         is(window[0].eval('probe.a'), undefined, "Non-exposed prop undefined");
         is(window[0].eval('probe.a'), undefined, "Non-exposed prop undefined again");
         endMonitor();
         break;
       case 2:
         SimpleTest.finish();
         break;
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -154,17 +154,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     "getHours", "getUTCHours", "getMinutes", "getUTCMinutes", "getSeconds",
     "getUTCSeconds", "getMilliseconds", "getUTCMilliseconds", "setTime",
     "setYear", "setFullYear", "setUTCFullYear", "setMonth", "setUTCMonth",
     "setDate", "setUTCDate", "setHours", "setUTCHours", "setMinutes",
     "setUTCMinutes", "setSeconds", "setUTCSeconds", "setMilliseconds",
     "setUTCMilliseconds", "toUTCString", "toLocaleFormat", "toLocaleString",
     "toLocaleDateString", "toLocaleTimeString", "toDateString", "toTimeString",
     "toISOString", "toJSON", "toSource", "toString", "valueOf", "constructor",
-    "toGMTString"];
+    "toGMTString", Symbol.toPrimitive];
   gPrototypeProperties['Object'] =
     ["constructor", "toSource", "toString", "toLocaleString", "valueOf", "watch",
      "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable",
      "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__",
      "__proto__"];
   gPrototypeProperties['Array'] =
     ["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push",
       "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf",
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -191,24 +191,30 @@ ReportWrapperDenial(JSContext* cx, Handl
     // The browser console warning is only emitted for the first violation,
     // whereas the (debug-only) NS_WARNING is emitted for each violation.
 #ifndef DEBUG
     if (alreadyWarnedOnce)
         return true;
 #endif
 
     nsAutoJSString propertyName;
-    if (!propertyName.init(cx, id))
+    RootedValue idval(cx);
+    if (!JS_IdToValue(cx, id, &idval))
+        return false;
+    JSString* str = JS_ValueToSource(cx, idval);
+    if (!str)
+        return false;
+    if (!propertyName.init(cx, str))
         return false;
     AutoFilename filename;
     unsigned line = 0, column = 0;
     DescribeScriptedCaller(cx, &filename, &line, &column);
 
     // Warn to the terminal for the logs.
-    NS_WARNING(nsPrintfCString("Silently denied access to property |%s|: %s (@%s:%u:%u)",
+    NS_WARNING(nsPrintfCString("Silently denied access to property %s: %s (@%s:%u:%u)",
                                NS_LossyConvertUTF16toASCII(propertyName).get(), reason,
                                filename.get(), line, column).get());
 
     // If this isn't the first warning on this topic for this global, we've
     // already bailed out in opt builds. Now that the NS_WARNING is done, bail
     // out in debug builds as well.
     if (alreadyWarnedOnce)
         return true;