Merge mozilla-central to autoland. a=merge on a CLOSED TREE
authorRazvan Maries <rmaries@mozilla.com>
Sat, 09 Mar 2019 23:55:26 +0200
changeset 521270 72d3e9ad8d2c
parent 521269 fc3a2f173e66 (current diff)
parent 521261 30385b68bea1 (diff)
child 521271 199af6c43953
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.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
Merge mozilla-central to autoland. a=merge on a CLOSED TREE
testing/web-platform/meta/svg/painting/parsing/stroke-miterlimit-computed.svg.ini
testing/web-platform/meta/svg/painting/parsing/stroke-miterlimit-valid.svg.ini
new file mode 100644
--- /dev/null
+++ b/dom/svg/crashtests/1531578-1.html
@@ -0,0 +1,17 @@
+<style>
+* {
+  filter: brightness(0.1638);
+  outline: 64px dotted;
+}
+</style>
+<script>
+function go() {
+  a.setAttribute("text-decoration", "line-through")
+}
+</script>
+<body onload=go()>
+<svg>
+<marker>
+<foreignObject id="a">
+<ins>
+
--- a/dom/svg/crashtests/crashtests.list
+++ b/dom/svg/crashtests/crashtests.list
@@ -86,9 +86,10 @@ load 1347617-2.svg
 load 1347617-3.svg
 load 1402798.html
 load 1419250-1.html
 load 1420492.html
 load 1477853.html
 load 1486488.html
 load 1493447.html
 skip-if(Android) load 1507961-1.html  # times out on Android due to the test size
+load 1531578-1.html
 load test_nested_svg.html
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1403679.js
@@ -0,0 +1,195 @@
+load(libdir + "asserts.js");
+
+const thisGlobal = this;
+const otherGlobalSameCompartment = newGlobal({sameCompartmentAs: thisGlobal});
+const otherGlobalNewCompartment = newGlobal({newCompartment: true});
+
+const globals = [thisGlobal, otherGlobalSameCompartment, otherGlobalNewCompartment];
+
+function testWithOptions(fn, variants = [undefined]) {
+    for (let variant of variants) {
+        for (let global of globals) {
+            for (let options of [
+                {},
+                {proxy: true},
+                {object: new FakeDOMObject()},
+            ]) {
+                fn(options, global, variant);
+            }
+        }
+    }
+}
+
+function testWithGlobals(fn) {
+    for (let global of globals) {
+        fn(global);
+    }
+}
+
+function testBasic(options, global) {
+    let {object: source, transplant} = transplantableObject(options);
+
+    // Validate that |source| is an object and |transplant| is a function.
+    assertEq(typeof source, "object");
+    assertEq(typeof transplant, "function");
+
+    // |source| is created in the current global.
+    assertEq(objectGlobal(source), this);
+
+    // |source|'s prototype is %ObjectPrototype%, unless it's a FakeDOMObject.
+    let oldPrototype;
+    if (options.object) {
+        oldPrototype = FakeDOMObject.prototype;
+    } else {
+        oldPrototype = Object.prototype;
+    }
+    assertEq(Object.getPrototypeOf(source), oldPrototype);
+
+    // Properties can be created on |source|.
+    assertEq(source.foo, undefined);
+    source.foo = 1;
+    assertEq(source.foo, 1);
+
+    // Calling |transplant| transplants the object and then returns undefined.
+    assertEq(transplant(global), undefined);
+
+    // |source| was moved into the new global. If the new global is in a
+    // different compartment, |source| is a now a CCW.
+    if (global !== otherGlobalNewCompartment) {
+        assertEq(objectGlobal(source), global);
+    } else {
+        assertEq(objectGlobal(source), null);
+        assertEq(isProxy(source), true);
+    }
+
+    // The properties are copied over to the swapped object.
+    assertEq(source.foo, 1);
+
+    // The prototype was changed to %ObjectPrototype% of |global| or the
+    // FakeDOMObject.prototype.
+    let newPrototype;
+    if (options.object) {
+        newPrototype = global.FakeDOMObject.prototype;
+    } else {
+        newPrototype = global.Object.prototype;
+    }
+    assertEq(Object.getPrototypeOf(source), newPrototype);
+}
+testWithOptions(testBasic);
+
+// Objects can be transplanted multiple times between globals.
+function testTransplantMulti(options, global1, global2) {
+    let {object: source, transplant} = transplantableObject(options);
+
+    transplant(global1);
+    transplant(global2);
+}
+testWithOptions(testTransplantMulti, globals);
+
+// Test the case when the source object already has a wrapper in the target global.
+function testHasWrapperInTarget(options, global) {
+    let {object: source, transplant} = transplantableObject(options);
+
+    // Create a wrapper for |source| in the other global.
+    global.p = source;
+    assertEq(global.eval("p"), source);
+
+    if (options.proxy) {
+        // It's a proxy object either way.
+        assertEq(global.eval("isProxy(p)"), true);
+    } else {
+        if (global === otherGlobalNewCompartment) {
+            // |isProxy| returns true because |p| is a CCW.
+            assertEq(global.eval("isProxy(p)"), true);
+        } else {
+            // |isProxy| returns false because |p| is not a CCW.
+            assertEq(global.eval("isProxy(p)"), false);
+        }
+    }
+
+    // And now transplant it into that global.
+    transplant(global);
+
+    assertEq(global.eval("p"), source);
+
+    if (options.proxy) {
+        // It's a proxy object either way.
+        assertEq(global.eval("isProxy(p)"), true);
+    } else {
+        // The previous CCW was replaced with a same-compartment object.
+        assertEq(global.eval("isProxy(p)"), false);
+    }
+}
+testWithOptions(testHasWrapperInTarget);
+
+// Test the case when the source object has a wrapper, but in a different compartment.
+function testHasWrapperOtherCompartment(options, global) {
+    let thirdGlobal = newGlobal({newCompartment: true});
+    let {object: source, transplant} = transplantableObject(options);
+
+    // Create a wrapper for |source| in the new global.
+    thirdGlobal.p = source;
+    assertEq(thirdGlobal.eval("p"), source);
+
+    // And now transplant the object.
+    transplant(global);
+
+    assertEq(thirdGlobal.eval("p"), source);
+}
+testWithOptions(testHasWrapperOtherCompartment);
+
+// Ensure a transplanted object is correctly handled by (weak) collections.
+function testCollections(options, global, AnySet) {
+    let {object, transplant} = transplantableObject(options);
+
+    let set = new AnySet();
+
+    assertEq(set.has(object), false);
+    set.add(object);
+    assertEq(set.has(object), true);
+
+    transplant(global);
+
+    assertEq(set.has(object), true);
+}
+testWithOptions(testCollections, [Set, WeakSet]);
+
+// Ensure DOM object slot is correctly transplanted.
+function testDOMObjectSlot(global) {
+    let domObject = new FakeDOMObject();
+    let expectedValue = domObject.x;
+    assertEq(typeof expectedValue, "number");
+
+    let {object, transplant} = transplantableObject({object: domObject});
+    assertEq(object, domObject);
+
+    transplant(global);
+
+    assertEq(object, domObject);
+    assertEq(domObject.x, expectedValue);
+}
+testWithGlobals(testDOMObjectSlot);
+
+function testArgumentValidation() {
+    // Throws an error if too many arguments are present.
+    assertThrowsInstanceOf(() => transplantableObject(thisGlobal, {}), Error);
+
+    let {object, transplant} = transplantableObject();
+
+    // Throws an error if called with no arguments.
+    assertThrowsInstanceOf(() => transplant(), Error);
+
+    // Throws an error if called with too many arguments.
+    assertThrowsInstanceOf(() => transplant(thisGlobal, {}), Error);
+
+    // Throws an error if the first argument isn't an object
+    assertThrowsInstanceOf(() => transplant(null), Error);
+
+    // Throws an error if the argument isn't a global object.
+    assertThrowsInstanceOf(() => transplant({}), Error);
+
+    // Throws an error if the 'object' option isn't a FakeDOMObject.
+    assertThrowsInstanceOf(() => transplant({object: null}), Error);
+    assertThrowsInstanceOf(() => transplant({object: {}}), Error);
+}
+testArgumentValidation();
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -93,16 +93,17 @@
 #include "js/Debug.h"
 #include "js/Equality.h"  // JS::SameValue
 #include "js/GCVector.h"
 #include "js/Initialization.h"
 #include "js/JSON.h"
 #include "js/MemoryFunctions.h"
 #include "js/Printf.h"
 #include "js/PropertySpec.h"
+#include "js/Realm.h"
 #include "js/SourceText.h"
 #include "js/StableStringChars.h"
 #include "js/StructuredClone.h"
 #include "js/SweepingAPI.h"
 #include "js/Wrapper.h"
 #include "perf/jsperf.h"
 #include "shell/jsoptparse.h"
 #include "shell/jsshell.h"
@@ -8055,16 +8056,297 @@ static bool WasmLoop(JSContext* cx, unsi
     }
   }
 
 #ifdef __AFL_HAVE_MANUAL_CONTROL  // to silence unreachable code warning
   return true;
 #endif
 }
 
+static constexpr uint32_t DOM_OBJECT_SLOT = 0;
+
+static const JSClass* GetDomClass();
+
+static JSObject* GetDOMPrototype(JSObject* global);
+
+static const JSClass TransplantableDOMObjectClass = {
+    "TransplantableDOMObject",
+    JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1)};
+
+static const Class TransplantableDOMProxyObjectClass =
+    PROXY_CLASS_DEF("TransplantableDOMProxyObject",
+                    JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1));
+
+class TransplantableDOMProxyHandler final : public ForwardingProxyHandler {
+ public:
+  static const TransplantableDOMProxyHandler singleton;
+  static const char family;
+
+  constexpr TransplantableDOMProxyHandler() : ForwardingProxyHandler(&family) {}
+
+  // These two proxy traps are called in |js::DeadProxyTargetValue|, which in
+  // turn is called when nuking proxies. Because this proxy can temporarily be
+  // without an object in its private slot, see |EnsureExpandoObject|, the
+  // default implementation inherited from ForwardingProxyHandler can't be used,
+  // since it tries to derive the callable/constructible value from the target.
+  bool isCallable(JSObject* obj) const override { return false; }
+  bool isConstructor(JSObject* obj) const override { return false; }
+
+  // Simplified implementation of |DOMProxyHandler::GetAndClearExpandoObject|.
+  static JSObject* GetAndClearExpandoObject(JSObject* obj) {
+    const Value& v = GetProxyPrivate(obj);
+    if (v.isUndefined()) {
+      return nullptr;
+    }
+
+    JSObject* expandoObject = &v.toObject();
+    SetProxyPrivate(obj, UndefinedValue());
+    return expandoObject;
+  }
+
+  // Simplified implementation of |DOMProxyHandler::EnsureExpandoObject|.
+  static JSObject* EnsureExpandoObject(JSContext* cx, JS::HandleObject obj) {
+    const Value& v = GetProxyPrivate(obj);
+    if (v.isObject()) {
+      return &v.toObject();
+    }
+    MOZ_ASSERT(v.isUndefined());
+
+    JSObject* expando = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
+    if (!expando) {
+      return nullptr;
+    }
+    SetProxyPrivate(obj, ObjectValue(*expando));
+    return expando;
+  }
+};
+
+const TransplantableDOMProxyHandler TransplantableDOMProxyHandler::singleton;
+const char TransplantableDOMProxyHandler::family = 0;
+
+enum TransplantObjectSlots {
+  TransplantSourceObject = 0,
+};
+
+static bool TransplantObject(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  RootedFunction callee(cx, &args.callee().as<JSFunction>());
+
+  if (args.length() != 1 || !args[0].isObject()) {
+    JS_ReportErrorASCII(cx, "transplant() must be called with an object");
+    return false;
+  }
+
+  // |newGlobal| needs to be a GlobalObject.
+  RootedObject newGlobal(cx, CheckedUnwrapDynamic(&args[0].toObject(), cx));
+  if (!newGlobal) {
+    ReportAccessDenied(cx);
+    return false;
+  }
+  if (!JS_IsGlobalObject(newGlobal)) {
+    JS_ReportErrorNumberASCII(
+        cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
+        "\"global\" passed to transplant()", "not a global object");
+    return false;
+  }
+
+  const Value& reserved =
+      GetFunctionNativeReserved(callee, TransplantSourceObject);
+  RootedObject source(cx, CheckedUnwrapStatic(&reserved.toObject()));
+  if (!source) {
+    ReportAccessDenied(cx);
+    return false;
+  }
+  MOZ_ASSERT(source->getClass()->isDOMClass());
+
+  // The following steps aim to replicate the behavior of UpdateReflectorGlobal
+  // in dom/bindings/BindingUtils.cpp. In detail:
+  // 1. Check the recursion depth using CheckRecursionLimitConservative.
+  // 2. Enter the target compartment.
+  // 3. Clone the source object using JS_CloneObject.
+  // 4. Copy all properties from source to a temporary holder object.
+  // 5. Actually transplant the object.
+  // 6. And finally copy the properties back to the source object.
+  //
+  // As an extension to the algorithm in UpdateReflectorGlobal, we also allow
+  // to transplant an object into the same compartment as the source object to
+  // cover all operations supported by JS_TransplantObject.
+
+  if (!CheckRecursionLimitConservative(cx)) {
+    return false;
+  }
+
+  bool isProxy = IsProxy(source);
+  RootedObject expandoObject(cx);
+  if (isProxy) {
+    expandoObject =
+        TransplantableDOMProxyHandler::GetAndClearExpandoObject(source);
+  }
+
+  JSAutoRealm ar(cx, newGlobal);
+
+  RootedObject proto(cx);
+  if (JS_GetClass(source) == GetDomClass()) {
+    proto = GetDOMPrototype(newGlobal);
+  } else {
+    proto = JS::GetRealmObjectPrototype(cx);
+    if (!proto) {
+      return false;
+    }
+  }
+
+  RootedObject target(cx, JS_CloneObject(cx, source, proto));
+  if (!target) {
+    return false;
+  }
+
+  RootedObject copyFrom(cx, isProxy ? expandoObject : source);
+  RootedObject propertyHolder(cx,
+                              JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
+  if (!propertyHolder) {
+    return false;
+  }
+
+  if (!JS_CopyPropertiesFrom(cx, propertyHolder, copyFrom)) {
+    return false;
+  }
+
+  SetReservedSlot(target, DOM_OBJECT_SLOT,
+                  GetReservedSlot(source, DOM_OBJECT_SLOT));
+  SetReservedSlot(source, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
+
+  source = JS_TransplantObject(cx, source, target);
+  if (!source) {
+    return false;
+  }
+
+  RootedObject copyTo(cx);
+  if (isProxy) {
+    copyTo = TransplantableDOMProxyHandler::EnsureExpandoObject(cx, source);
+    if (!copyTo) {
+      return false;
+    }
+  } else {
+    copyTo = source;
+  }
+  if (!JS_CopyPropertiesFrom(cx, copyTo, propertyHolder)) {
+    return false;
+  }
+
+  args.rval().setUndefined();
+  return true;
+}
+
+static bool TransplantableObject(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  RootedObject callee(cx, &args.callee());
+
+  if (args.length() > 1) {
+    ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+    return false;
+  }
+
+  bool createProxy = false;
+  RootedObject source(cx);
+  if (args.length() == 1 && !args[0].isUndefined()) {
+    if (!args[0].isObject()) {
+      ReportUsageErrorASCII(cx, callee, "Argument must be an object");
+      return false;
+    }
+
+    RootedObject options(cx, &args[0].toObject());
+    RootedValue value(cx);
+
+    if (!JS_GetProperty(cx, options, "proxy", &value)) {
+      return false;
+    }
+    createProxy = JS::ToBoolean(value);
+
+    if (!JS_GetProperty(cx, options, "object", &value)) {
+      return false;
+    }
+    if (!value.isUndefined()) {
+      if (!value.isObject()) {
+        ReportUsageErrorASCII(cx, callee, "'object' option must be an object");
+        return false;
+      }
+
+      source = &value.toObject();
+      if (JS_GetClass(source) != GetDomClass()) {
+        ReportUsageErrorASCII(cx, callee, "Object not a FakeDOMObject");
+        return false;
+      }
+
+      // |source| must be a tenured object to be transplantable.
+      if (gc::IsInsideNursery(source)) {
+        JS_GC(cx);
+
+        MOZ_ASSERT(!gc::IsInsideNursery(source),
+                   "Live objects should be tenured after one GC, because "
+                   "the nursery has only a single generation");
+      }
+    }
+  }
+
+  if (!source) {
+    if (!createProxy) {
+      source = NewBuiltinClassInstance(
+          cx, Valueify(&TransplantableDOMObjectClass), TenuredObject);
+      if (!source) {
+        return false;
+      }
+
+      SetReservedSlot(source, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
+    } else {
+      JSObject* expando = JS_NewPlainObject(cx);
+      if (!expando) {
+        return false;
+      }
+      RootedValue expandoVal(cx, ObjectValue(*expando));
+
+      ProxyOptions options;
+      options.setClass(&TransplantableDOMProxyObjectClass);
+      options.setLazyProto(true);
+
+      source = NewProxyObject(cx, &TransplantableDOMProxyHandler::singleton,
+                              expandoVal, nullptr, options);
+      if (!source) {
+        return false;
+      }
+
+      SetProxyReservedSlot(source, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
+    }
+  }
+
+  jsid emptyId = NameToId(cx->names().empty);
+  RootedObject transplant(
+      cx, NewFunctionByIdWithReserved(cx, TransplantObject, 0, 0, emptyId));
+  if (!transplant) {
+    return false;
+  }
+
+  SetFunctionNativeReserved(transplant, TransplantSourceObject,
+                            ObjectValue(*source));
+
+  RootedObject result(cx, JS_NewPlainObject(cx));
+  if (!result) {
+    return false;
+  }
+
+  RootedValue sourceVal(cx, ObjectValue(*source));
+  RootedValue transplantVal(cx, ObjectValue(*transplant));
+  if (!JS_DefineProperty(cx, result, "object", sourceVal, 0) ||
+      !JS_DefineProperty(cx, result, "transplant", transplantVal, 0)) {
+    return false;
+  }
+
+  args.rval().setObject(*result);
+  return true;
+}
+
 // clang-format off
 static const JSFunctionSpecWithHelp shell_functions[] = {
     JS_FN_HELP("clone", Clone, 1, 0,
 "clone(fun[, scope])",
 "  Clone function object."),
 
     JS_FN_HELP("options", Options, 0, 0,
 "options([option ...])",
@@ -8700,16 +8982,29 @@ JS_FN_HELP("parseBin", BinParse, 1, 0,
 #endif // ENABLE_INTL_API
 
     JS_FN_HELP("wasmCompileInSeparateProcess", WasmCompileInSeparateProcess, 1, 0,
 "wasmCompileInSeparateProcess(buffer)",
 "  Compile the given buffer in a separate process, serialize the resulting\n"
 "  wasm::Module into bytes, and deserialize those bytes in the current\n"
 "  process, returning the resulting WebAssembly.Module."),
 
+    JS_FN_HELP("transplantableObject", TransplantableObject, 0, 0,
+"transplantableObject([options])",
+"  Returns the pair {object, transplant}. |object| is an object which can be\n"
+"  transplanted into a new object when the |transplant| function, which must\n"
+"  be invoked with a global object, is called.\n"
+"  |object| is swapped with a cross-compartment wrapper if the global object\n"
+"  is in a different compartment.\n"
+"\n"
+"  If options is given, it may have any of the following properties:\n"
+"    proxy: Create a DOM Proxy object instead of a plain DOM object.\n"
+"    object: Don't create a new DOM object, but instead use the supplied\n"
+"            FakeDOMObject."),
+
     JS_FS_HELP_END
 };
 // clang-format on
 
 // clang-format off
 static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
     JS_FN_HELP("getSelfHostedValue", GetSelfHostedValue, 1, 0,
 "getSelfHostedValue()",
@@ -9216,83 +9511,86 @@ static const JSClassOps global_classOps 
                                            global_resolve,
                                            global_mayResolve,
                                            nullptr,
                                            nullptr,
                                            nullptr,
                                            nullptr,
                                            JS_GlobalObjectTraceHook};
 
-static const JSClass global_class = {"global", JSCLASS_GLOBAL_FLAGS,
-                                     &global_classOps};
+static constexpr uint32_t DOM_PROTOTYPE_SLOT = JSCLASS_GLOBAL_SLOT_COUNT;
+static constexpr uint32_t DOM_GLOBAL_SLOTS = 1;
+
+static const JSClass global_class = {
+    "global",
+    JSCLASS_GLOBAL_FLAGS | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS),
+    &global_classOps};
 
 /*
  * Define a FakeDOMObject constructor. It returns an object with a getter,
  * setter and method with attached JitInfo. This object can be used to test
  * IonMonkey DOM optimizations in the shell.
  */
-static const uint32_t DOM_OBJECT_SLOT = 0;
+
+/* Fow now just use to a constant we can check. */
+static const void* DOM_PRIVATE_VALUE = (void*)0x1234;
 
 static bool dom_genericGetter(JSContext* cx, unsigned argc, JS::Value* vp);
 
 static bool dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp);
 
 static bool dom_genericMethod(JSContext* cx, unsigned argc, JS::Value* vp);
 
-#ifdef DEBUG
-static const JSClass* GetDomClass();
-#endif
-
 static bool dom_get_x(JSContext* cx, HandleObject obj, void* self,
                       JSJitGetterCallArgs args) {
   MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
-  MOZ_ASSERT(self == (void*)0x1234);
+  MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
   args.rval().set(JS_NumberValue(double(3.14)));
   return true;
 }
 
 static bool dom_set_x(JSContext* cx, HandleObject obj, void* self,
                       JSJitSetterCallArgs args) {
   MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
-  MOZ_ASSERT(self == (void*)0x1234);
+  MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
   return true;
 }
 
 static bool dom_get_global(JSContext* cx, HandleObject obj, void* self,
                            JSJitGetterCallArgs args) {
   MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
-  MOZ_ASSERT(self == (void*)0x1234);
+  MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
 
   // Return the current global (instead of obj->global()) to test cx->realm
   // switching in the JIT.
   args.rval().setObject(*ToWindowProxyIfWindow(cx->global()));
 
   return true;
 }
 
 static bool dom_set_global(JSContext* cx, HandleObject obj, void* self,
                            JSJitSetterCallArgs args) {
   MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
-  MOZ_ASSERT(self == (void*)0x1234);
+  MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
 
   // Throw an exception if our argument is not the current global. This lets
   // us test cx->realm switching.
   if (!args[0].isObject() ||
       ToWindowIfWindowProxy(&args[0].toObject()) != cx->global()) {
     JS_ReportErrorASCII(cx, "Setter not called with matching global argument");
     return false;
   }
 
   return true;
 }
 
 static bool dom_doFoo(JSContext* cx, HandleObject obj, void* self,
                       const JSJitMethodCallArgs& args) {
   MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
-  MOZ_ASSERT(self == (void*)0x1234);
+  MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
   MOZ_ASSERT(cx->realm() == args.callee().as<JSFunction>().realm());
 
   /* Just return args.length(). */
   args.rval().setInt32(args.length());
   return true;
 }
 
 static const JSJitInfo dom_x_getterinfo = {
@@ -9396,19 +9694,17 @@ static const JSPropertySpec dom_props[] 
 static const JSFunctionSpec dom_methods[] = {
     JS_FNINFO("doFoo", dom_genericMethod, &doFoo_methodinfo, 3,
               JSPROP_ENUMERATE),
     JS_FS_END};
 
 static const JSClass dom_class = {
     "FakeDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2)};
 
-#ifdef DEBUG
 static const JSClass* GetDomClass() { return &dom_class; }
-#endif
 
 static bool dom_genericGetter(JSContext* cx, unsigned argc, JS::Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!args.thisv().isObject()) {
     args.rval().setUndefined();
     return true;
   }
@@ -9471,18 +9767,24 @@ static bool dom_genericMethod(JSContext*
 
   const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   MOZ_ASSERT(info->type() == JSJitInfo::Method);
   JSJitMethodOp method = info->method;
   return method(cx, obj, val.toPrivate(), JSJitMethodCallArgs(args));
 }
 
 static void InitDOMObject(HandleObject obj) {
-  /* Fow now just initialize to a constant we can check. */
-  SetReservedSlot(obj, DOM_OBJECT_SLOT, PrivateValue((void*)0x1234));
+  SetReservedSlot(obj, DOM_OBJECT_SLOT,
+                  PrivateValue(const_cast<void*>(DOM_PRIVATE_VALUE)));
+}
+
+static JSObject* GetDOMPrototype(JSObject* global) {
+  MOZ_ASSERT(JS_IsGlobalObject(global));
+  MOZ_ASSERT(GetReservedSlot(global, DOM_PROTOTYPE_SLOT).isObject());
+  return &GetReservedSlot(global, DOM_PROTOTYPE_SLOT).toObject();
 }
 
 static bool dom_constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   RootedObject callee(cx, &args.callee());
   RootedValue protov(cx);
   if (!GetProperty(cx, callee, callee, cx->names().prototype, &protov)) {
@@ -9504,19 +9806,18 @@ static bool dom_constructor(JSContext* c
   InitDOMObject(domObj);
 
   args.rval().setObject(*domObj);
   return true;
 }
 
 static bool InstanceClassHasProtoAtDepth(const Class* clasp, uint32_t protoID,
                                          uint32_t depth) {
-  // There's only a single (fake) DOM object in the shell, so just return
-  // true.
-  return true;
+  // Only the (fake) DOM object supports any JIT optimizations.
+  return clasp == Valueify(GetDomClass());
 }
 
 static bool ShellBuildId(JS::BuildIdCharVector* buildId) {
   // The browser embeds the date into the buildid and the buildid is embedded
   // in the binary, so every 'make' necessarily builds a new firefox binary.
   // Fortunately, the actual firefox executable is tiny -- all the code is in
   // libxul.so and other shared modules -- so this isn't a big deal. Not so
   // for the statically-linked JS shell. To avoid recompiling js.cpp and
@@ -9635,16 +9936,21 @@ static JSObject* NewGlobalObject(JSConte
 
     RootedObject domProto(
         cx, JS_InitClass(cx, glob, nullptr, &dom_class, dom_constructor, 0,
                          dom_props, dom_methods, nullptr, nullptr));
     if (!domProto) {
       return nullptr;
     }
 
+    // FakeDOMObject.prototype is the only DOM object which needs to retrieved
+    // in the shell; store it directly instead of creating a separate layer
+    // (ProtoAndIfaceCache) as done in the browser.
+    SetReservedSlot(glob, DOM_PROTOTYPE_SLOT, ObjectValue(*domProto));
+
     /* Initialize FakeDOMObject.prototype */
     InitDOMObject(domProto);
 
     JS_FireOnNewGlobalObject(cx, glob);
   }
 
   return glob;
 }
--- a/js/src/vm/JSObject.cpp
+++ b/js/src/vm/JSObject.cpp
@@ -1352,17 +1352,20 @@ JSObject* js::CloneObject(JSContext* cx,
   if (!obj->isNative() && !obj->is<ProxyObject>()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_CANT_CLONE_OBJECT);
     return nullptr;
   }
 
   RootedObject clone(cx);
   if (obj->isNative()) {
-    clone = NewObjectWithGivenTaggedProto(cx, obj->getClass(), proto);
+    // CloneObject is used to create the target object for JSObject::swap() and
+    // swap() requires its arguments are tenured, so ensure tenure allocation.
+    clone = NewObjectWithGivenTaggedProto(cx, obj->getClass(), proto,
+                                          NewObjectKind::TenuredObject);
     if (!clone) {
       return nullptr;
     }
 
     if (clone->is<JSFunction>() &&
         (obj->compartment() != clone->compartment())) {
       JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                 JSMSG_CANT_CLONE_OBJECT);
@@ -1372,18 +1375,27 @@ JSObject* js::CloneObject(JSContext* cx,
     if (obj->as<NativeObject>().hasPrivate()) {
       clone->as<NativeObject>().setPrivate(
           obj->as<NativeObject>().getPrivate());
     }
   } else {
     ProxyOptions options;
     options.setClass(obj->getClass());
 
-    clone = ProxyObject::New(cx, GetProxyHandler(obj), JS::NullHandleValue,
-                             proto, options);
+    auto* handler = GetProxyHandler(obj);
+
+    // Same as above, require tenure allocation of the clone. This means for
+    // proxy objects we need to reject nursery allocatable proxies.
+    if (handler->canNurseryAllocate()) {
+      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                                JSMSG_CANT_CLONE_OBJECT);
+      return nullptr;
+    }
+
+    clone = ProxyObject::New(cx, handler, JS::NullHandleValue, proto, options);
     if (!clone) {
       return nullptr;
     }
 
     if (!CopyProxyObject(cx, obj.as<ProxyObject>(), clone.as<ProxyObject>())) {
       return nullptr;
     }
   }
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1613,17 +1613,18 @@ void RestyleManager::ProcessRestyledFram
       NS_ASSERTION(frame, "This shouldn't happen");
 
       if (!frame->FrameMaintainsOverflow()) {
         // frame does not maintain overflow rects, so avoid calling
         // FinishAndStoreOverflow on it:
         hint &=
             ~(nsChangeHint_UpdateOverflow | nsChangeHint_ChildrenOnlyTransform |
               nsChangeHint_UpdatePostTransformOverflow |
-              nsChangeHint_UpdateParentOverflow);
+              nsChangeHint_UpdateParentOverflow |
+              nsChangeHint_UpdateSubtreeOverflow);
       }
 
       if (primaryFrame &&
           !(primaryFrame->GetStateBits() & NS_FRAME_MAY_BE_TRANSFORMED)) {
         // Frame can not be transformed, and thus a change in transform will
         // have no effect and we should not use the
         // nsChangeHint_UpdatePostTransformOverflow hint.
         hint &= ~nsChangeHint_UpdatePostTransformOverflow;
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -5589,18 +5589,18 @@ var gCSSProperties = {
     other_values: [ "round", "bevel" ],
     invalid_values: []
   },
   "stroke-miterlimit": {
     domProp: "strokeMiterlimit",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "4" ],
-    other_values: [ "1", "7", "5000", "1.1" ],
-    invalid_values: [ "0.9", "0", "-1", "3px", "-0.3" ]
+    other_values: [ "0", "0.9", "1", "7", "5000", "1.1" ],
+    invalid_values: [ "-1", "3px", "-0.3" ]
   },
   "stroke-opacity": {
     domProp: "strokeOpacity",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "1", "2.8", "1.000" ],
     other_values: [ "0", "0.3", "-7.3", "context-fill-opacity", "context-stroke-opacity" ],
     invalid_values: []
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -258,18 +258,19 @@ var supported_properties = {
                        // opacity is clamped in computed style
                        // (not parsing/interpolation)
                        test_float_zeroToOne_clamped ],
     "stroke": [ test_color_transition,
                 test_currentcolor_transition ],
     "stroke-dasharray": [ test_dasharray_transition ],
     "stroke-dashoffset": [ test_length_transition, test_percent_transition,
                            test_length_unclamped, test_percent_unclamped, ],
-    "stroke-miterlimit": [ test_float_aboveOne_transition,
-                           test_float_aboveOne_clamped ],
+    "stroke-miterlimit": [ test_float_zeroToOne_transition,
+                           test_float_aboveOne_transition,
+                           test_float_aboveZero_clamped ],
     "stroke-opacity" : [ test_float_zeroToOne_transition,
                          // opacity is clamped in computed style
                          // (not parsing/interpolation)
                          test_float_zeroToOne_clamped ],
     "stroke-width": [ test_length_transition, test_percent_transition,
                       test_length_clamped, test_percent_clamped, ],
     "-moz-tab-size": [ test_float_zeroToOne_transition,
                        test_float_aboveOne_transition, test_length_clamped ],
@@ -1345,38 +1346,38 @@ function test_float_zeroToOne_clamped_or
      "float-valued property " + prop + ": flush before clamping test");
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "1", "");
   (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0",
      "float-valued property " + prop + ": clamping of negatives");
   div.style.setProperty("transition-timing-function", "linear", "");
 }
 
-// Test using float values in the range [1, infinity) (e.g. stroke-miterlimit)
+// Test using float values in the range [1, infinity)
 function test_float_aboveOne_transition(prop) {
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "1", "");
   is(cs.getPropertyValue(prop), "1",
      "float-valued property " + prop + ": computed value before transition");
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "2.1", "");
   is(cs.getPropertyValue(prop), "1.275",
      "float-valued property " + prop + ": interpolation of floats");
   check_distance(prop, "1", "1.275", "2.1");
 }
 
-function test_float_aboveOne_clamped(prop) {
+function test_float_aboveZero_clamped(prop) {
   div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
   div.style.setProperty("transition-property", "none", "");
-  div.style.setProperty(prop, "1", "");
-  is(cs.getPropertyValue(prop), "1",
+  div.style.setProperty(prop, "0", "");
+  is(cs.getPropertyValue(prop), "0",
      "float-valued property " + prop + ": flush before clamping test");
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "5", "");
-  is(cs.getPropertyValue(prop), "1",
+  is(cs.getPropertyValue(prop), "0",
      "float-valued property " + prop + ": clamping of negatives");
   div.style.setProperty("transition-timing-function", "linear", "");
 }
 
 function test_percent_transition(prop) {
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "25%", "");
   var av = cs.getPropertyValue(prop);
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -3012,18 +3012,17 @@ void SVGTextFrame::ReflowSVGNonDisplayTe
   // We had a style change, so we mark this frame as dirty so that the next
   // time it is painted, we reflow the anonymous block frame.
   AddStateBits(NS_FRAME_IS_DIRTY);
 
   // We also need to call InvalidateRenderingObservers, so that if the <text>
   // element is within a <mask>, say, the element referencing the <mask> will
   // be updated, which will then cause this SVGTextFrame to be painted and
   // in doing so cause the anonymous block frame to be reflowed.
-  nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), nsRestyleHint(0),
-                                  nsChangeHint_InvalidateRenderingObservers);
+  SVGObserverUtils::InvalidateRenderingObservers(this);
 
   // Finally, we need to actually reflow the anonymous block frame and update
   // mPositions, in case we are being reflowed immediately after a DOM
   // mutation that needs frame reconstruction.
   MaybeReflowAnonymousBlockChild();
   UpdateGlyphPositioning();
 }
 
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -1290,16 +1290,18 @@ gfxRect nsSVGUtils::PathExtentsToMaxStro
   // For a shape without corners the stroke can only extend half the stroke
   // width from the path in the x/y-axis directions. For shapes with corners
   // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line
   // with stroke-linecaps="square").
   double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5;
 
   // The stroke can extend even further for paths that can be affected by
   // stroke-miterlimit.
+  // We only need to do this if the limit is greater than 1, but it's probably
+  // not worth optimizing for that.
   bool affectedByMiterlimit = aFrame->GetContent()->IsAnyOfSVGElements(
       nsGkAtoms::path, nsGkAtoms::polyline, nsGkAtoms::polygon);
 
   if (affectedByMiterlimit) {
     const nsStyleSVG* style = aFrame->StyleSVG();
     if (style->mStrokeLinejoin == NS_STYLE_STROKE_LINEJOIN_MITER &&
         styleExpansionFactor < style->mStrokeMiterlimit / 2.0) {
       styleExpansionFactor = style->mStrokeMiterlimit / 2.0;
--- a/servo/components/style/properties/longhands/inherited_svg.mako.rs
+++ b/servo/components/style/properties/longhands/inherited_svg.mako.rs
@@ -105,21 +105,21 @@
     "miter round bevel",
     products="gecko",
     animation_value_type="discrete",
     spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty",
 )}
 
 ${helpers.predefined_type(
     "stroke-miterlimit",
-    "GreaterThanOrEqualToOneNumber",
+    "NonNegativeNumber",
     "From::from(4.0)",
     products="gecko",
-    animation_value_type="crate::values::computed::GreaterThanOrEqualToOneNumber",
-    spec="https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty",
+    animation_value_type="crate::values::computed::NonNegativeNumber",
+    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeMiterlimitProperty",
 )}
 
 ${helpers.predefined_type(
     "stroke-opacity",
     "SVGOpacity",
     "Default::default()",
     products="gecko",
     animation_value_type="ComputedValue",
deleted file mode 100644
--- a/testing/web-platform/meta/svg/painting/parsing/stroke-miterlimit-computed.svg.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[stroke-miterlimit-computed.svg]
-  [Property stroke-miterlimit value '0.5' computes to '0.5']
-    expected: FAIL
-
-  [Property stroke-miterlimit value '0' computes to '0']
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/svg/painting/parsing/stroke-miterlimit-valid.svg.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[stroke-miterlimit-valid.svg]
-  [e.style['stroke-miterlimit'\] = "0.5" should set the property value]
-    expected: FAIL
-
-  [e.style['stroke-miterlimit'\] = "0" should set the property value]
-    expected: FAIL
-