Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Fri, 18 Jan 2019 11:40:07 +0200
changeset 511517 3aa256c255f6
parent 511496 88e2eb73aace (current diff)
parent 511516 9ac2f47b3cc2 (diff)
child 511519 15a126eab097
child 511629 bd29a5bac8d2
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
3aa256c255f6 / 66.0a1 / 20190118094042 / files
nightly linux64
3aa256c255f6 / 66.0a1 / 20190118094042 / files
nightly mac
3aa256c255f6 / 66.0a1 / 20190118094042 / files
nightly win32
3aa256c255f6 / 66.0a1 / 20190118094042 / files
nightly win64
3aa256c255f6 / 66.0a1 / 20190118094042 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
mobile/android/geckoview/api.txt
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
modules/libpref/init/all.js
toolkit/components/extensions/parent/ext-downloads.js
toolkit/components/extensions/schemas/downloads.json
toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
--- a/devtools/server/actors/animation-type-longhand.js
+++ b/devtools/server/actors/animation-type-longhand.js
@@ -278,16 +278,20 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
     "-moz-window-transform",
     "-moz-window-transform-origin",
   ])],
   ["coord", new Set([
     "border-bottom-left-radius",
     "border-bottom-right-radius",
     "border-top-left-radius",
     "border-top-right-radius",
+    "border-start-start-radius",
+    "border-start-end-radius",
+    "border-end-start-radius",
+    "border-end-end-radius",
     "bottom",
     "column-gap",
     "column-width",
     "flex-basis",
     "height",
     "left",
     "letter-spacing",
     "line-height",
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -2852,16 +2852,20 @@ exports.CSS_PROPERTIES = {
       "border-block-end-style",
       "border-block-end-width",
       "border-inline-start-color",
       "border-inline-start-style",
       "border-inline-start-width",
       "border-inline-end-color",
       "border-inline-end-style",
       "border-inline-end-width",
+      "border-start-start-radius",
+      "border-start-end-radius",
+      "border-end-start-radius",
+      "border-end-end-radius",
       "margin-block-start",
       "margin-block-end",
       "margin-inline-start",
       "margin-inline-end",
       "padding-block-start",
       "padding-block-end",
       "padding-inline-start",
       "padding-inline-end",
@@ -4123,16 +4127,40 @@ exports.CSS_PROPERTIES = {
       "inherit",
       "initial",
       "rgb",
       "rgba",
       "transparent",
       "unset"
     ]
   },
+  "border-end-end-radius": {
+    "isInherited": false,
+    "subproperties": [
+      "border-end-end-radius"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "unset"
+    ]
+  },
+  "border-end-start-radius": {
+    "isInherited": false,
+    "subproperties": [
+      "border-end-start-radius"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "unset"
+    ]
+  },
   "border-image": {
     "isInherited": false,
     "subproperties": [
       "border-image-outset",
       "border-image-repeat",
       "border-image-slice",
       "border-image-source",
       "border-image-width"
@@ -4752,16 +4780,40 @@ exports.CSS_PROPERTIES = {
     ],
     "supports": [],
     "values": [
       "inherit",
       "initial",
       "unset"
     ]
   },
+  "border-start-end-radius": {
+    "isInherited": false,
+    "subproperties": [
+      "border-start-end-radius"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "unset"
+    ]
+  },
+  "border-start-start-radius": {
+    "isInherited": false,
+    "subproperties": [
+      "border-start-start-radius"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "unset"
+    ]
+  },
   "border-style": {
     "isInherited": false,
     "subproperties": [
       "border-top-style",
       "border-right-style",
       "border-bottom-style",
       "border-left-style"
     ],
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2169,17 +2169,17 @@ static void DOMGCSliceCallback(JSContext
           cs->LogStringMessage(msg.get());
         }
       }
 
       if (!sShuttingDown) {
         if (StaticPrefs::javascript_options_mem_notify() ||
             Telemetry::CanRecordExtended()) {
           nsString json;
-          json.Adopt(aDesc.formatJSON(aCx, PR_Now()));
+          json.Adopt(aDesc.formatJSONTelemetry(aCx, PR_Now()));
           RefPtr<NotifyGCEndRunnable> notify =
               new NotifyGCEndRunnable(std::move(json));
           SystemGroup::Dispatch(TaskCategory::GarbageCollection,
                                 notify.forget());
         }
       }
 
       sCCLockedOut = false;
--- a/gfx/skia/skia/src/ports/SkFontHost_mac.cpp
+++ b/gfx/skia/skia/src/ports/SkFontHost_mac.cpp
@@ -1442,17 +1442,17 @@ void SkScalerContext_Mac::generateImage(
     if (cgPixels == nullptr) {
         return;
     }
 
     // Fix the glyph
     if ((glyph.fMaskFormat == SkMask::kLCD16_Format) ||
         (glyph.fMaskFormat == SkMask::kA8_Format
          && requestSmooth
-         && smooth_behavior() == SmoothBehavior::subpixel))
+         && smooth_behavior() != SmoothBehavior::none))
     {
         const uint8_t* linear = gLinearCoverageFromCGLCDValue.data();
 
         //Note that the following cannot really be integrated into the
         //pre-blend, since we may not be applying the pre-blend; when we aren't
         //applying the pre-blend it means that a filter wants linear anyway.
         //Other code may also be applying the pre-blend, so we'd need another
         //one with this and one without.
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -629,25 +629,26 @@ struct JS_PUBLIC_API GCDescription {
                 gcreason::Reason reason)
       : isZone_(isZone),
         isComplete_(isComplete),
         invocationKind_(kind),
         reason_(reason) {}
 
   char16_t* formatSliceMessage(JSContext* cx) const;
   char16_t* formatSummaryMessage(JSContext* cx) const;
-  char16_t* formatJSON(JSContext* cx, uint64_t timestamp) const;
 
   mozilla::TimeStamp startTime(JSContext* cx) const;
   mozilla::TimeStamp endTime(JSContext* cx) const;
   mozilla::TimeStamp lastSliceStart(JSContext* cx) const;
   mozilla::TimeStamp lastSliceEnd(JSContext* cx) const;
 
-  JS::UniqueChars sliceToJSON(JSContext* cx) const;
-  JS::UniqueChars summaryToJSON(JSContext* cx) const;
+  char16_t* formatJSONTelemetry(JSContext* cx, uint64_t timestamp) const;
+
+  JS::UniqueChars sliceToJSONProfiler(JSContext* cx) const;
+  JS::UniqueChars formatJSONProfiler(JSContext* cx) const;
 
   JS::dbg::GarbageCollectionEvent::Ptr toGCEvent(JSContext* cx) const;
 };
 
 extern JS_PUBLIC_API UniqueChars MinorGcToJSON(JSContext* cx);
 
 typedef void (*GCSliceCallback)(JSContext* cx, GCProgress progress,
                                 const GCDescription& desc);
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -413,32 +413,32 @@ static bool GC(JSContext* cx, unsigned a
     if (arg.isString()) {
       if (!JS_StringEqualsAscii(cx, arg.toString(), "shrinking", &shrinking)) {
         return false;
       }
     }
   }
 
 #ifndef JS_MORE_DETERMINISTIC
-  size_t preBytes = cx->runtime()->gc.usage.gcBytes();
+  size_t preBytes = cx->runtime()->gc.heapSize.gcBytes();
 #endif
 
   if (zone) {
     PrepareForDebugGC(cx->runtime());
   } else {
     JS::PrepareForFullGC(cx);
   }
 
   JSGCInvocationKind gckind = shrinking ? GC_SHRINK : GC_NORMAL;
   JS::NonIncrementalGC(cx, gckind, JS::gcreason::API);
 
   char buf[256] = {'\0'};
 #ifndef JS_MORE_DETERMINISTIC
   SprintfLiteral(buf, "before %zu, after %zu\n", preBytes,
-                 cx->runtime()->gc.usage.gcBytes());
+                 cx->runtime()->gc.heapSize.gcBytes());
 #endif
   return ReturnStringCopy(cx, args, buf);
 }
 
 static bool MinorGC(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   if (args.get(0) == BooleanValue(true)) {
     cx->runtime()->gc.storeBuffer().setAboutToOverflow(
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -32,18 +32,20 @@
 #include "frontend/DoWhileEmitter.h"
 #include "frontend/ElemOpEmitter.h"
 #include "frontend/EmitterScope.h"
 #include "frontend/ExpressionStatementEmitter.h"
 #include "frontend/ForInEmitter.h"
 #include "frontend/ForOfEmitter.h"
 #include "frontend/ForOfLoopControl.h"
 #include "frontend/IfEmitter.h"
+#include "frontend/LabelEmitter.h"  // LabelEmitter
 #include "frontend/ModuleSharedContext.h"
 #include "frontend/NameOpEmitter.h"
+#include "frontend/ObjectEmitter.h"  // PropertyEmitter, ObjectEmitter, ClassEmitter
 #include "frontend/ParseNode.h"
 #include "frontend/Parser.h"
 #include "frontend/PropOpEmitter.h"
 #include "frontend/SwitchEmitter.h"
 #include "frontend/TDZCheckCache.h"
 #include "frontend/TryEmitter.h"
 #include "frontend/WhileEmitter.h"
 #include "js/CompileOptions.h"
@@ -3025,34 +3027,40 @@ bool BytecodeEmitter::emitDefault(ParseN
 
 bool BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun,
                                           HandleAtom name) {
   MOZ_ASSERT(maybeFun->isDirectRHSAnonFunction());
 
   if (maybeFun->isKind(ParseNodeKind::Function)) {
     // Function doesn't have 'name' property at this point.
     // Set function's name at compile time.
-    JSFunction* fun = maybeFun->as<CodeNode>().funbox()->function();
-
-    // The inferred name may already be set if this function is an
-    // interpreted lazy function and we OOM'ed after we set the inferred
-    // name the first time.
-    if (fun->hasInferredName()) {
-      MOZ_ASSERT(fun->isInterpretedLazy());
-      MOZ_ASSERT(fun->inferredName() == name);
-
-      return true;
-    }
-
-    fun->setInferredName(name);
-    return true;
+    return setFunName(maybeFun->as<CodeNode>().funbox()->function(), name);
   }
 
   MOZ_ASSERT(maybeFun->isKind(ParseNodeKind::ClassDecl));
 
+  return emitSetClassConstructorName(name);
+}
+
+bool BytecodeEmitter::setFunName(JSFunction* fun, JSAtom* name) {
+  // The inferred name may already be set if this function is an interpreted
+  // lazy function and we OOM'ed after we set the inferred name the first
+  // time.
+  if (fun->hasInferredName()) {
+    MOZ_ASSERT(fun->isInterpretedLazy());
+    MOZ_ASSERT(fun->inferredName() == name);
+
+    return true;
+  }
+
+  fun->setInferredName(name);
+  return true;
+}
+
+bool BytecodeEmitter::emitSetClassConstructorName(JSAtom* name) {
   uint32_t nameIndex;
   if (!makeAtomIndex(name, &nameIndex)) {
     return false;
   }
   if (!emitIndexOp(JSOP_STRING, nameIndex)) {
     //              [stack] FUN NAME
     return false;
   }
@@ -7493,43 +7501,25 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::e
     default:
       return emitNameIncDec(incDec);
   }
 }
 
 // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
 // the comment on emitSwitch.
 MOZ_NEVER_INLINE bool BytecodeEmitter::emitLabeledStatement(
-    const LabeledStatement* pn) {
-  /*
-   * Emit a JSOP_LABEL instruction. The argument is the offset to the statement
-   * following the labeled statement.
-   */
-  uint32_t index;
-  if (!makeAtomIndex(pn->label(), &index)) {
-    return false;
-  }
-
-  JumpList top;
-  if (!emitJump(JSOP_LABEL, &top)) {
-    return false;
-  }
-
-  /* Emit code for the labeled statement. */
-  LabelControl controlInfo(this, pn->label(), offset());
-
-  if (!emitTree(pn->statement())) {
-    return false;
-  }
-
-  /* Patch the JSOP_LABEL offset. */
-  JumpTarget brk{lastNonJumpTargetOffset()};
-  patchJumpsToTarget(top, brk);
-
-  if (!controlInfo.patchBreaks(this)) {
+    const LabeledStatement* labeledStmt) {
+  LabelEmitter label(this);
+  if (!label.emitLabel(labeledStmt->label())) {
+    return false;
+  }
+  if (!emitTree(labeledStmt->statement())) {
+    return false;
+  }
+  if (!label.emitEnd()) {
     return false;
   }
 
   return true;
 }
 
 bool BytecodeEmitter::emitConditionalExpression(
     ConditionalExpression& conditional,
@@ -7562,279 +7552,285 @@ bool BytecodeEmitter::emitConditionalExp
   if (!cond.emitEnd()) {
     return false;
   }
   MOZ_ASSERT(cond.pushed() == 1);
 
   return true;
 }
 
-bool BytecodeEmitter::emitPropertyList(ListNode* obj,
-                                       MutableHandlePlainObject objp,
+bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe,
                                        PropListType type) {
+  //                [stack] CTOR? OBJ
+
   for (ParseNode* propdef : obj->contents()) {
     if (propdef->is<ClassField>()) {
       // TODO(khyperia): Implement private field access.
       return false;
     }
-    if (!updateSourceCoordNotes(propdef->pn_pos.begin)) {
-      return false;
-    }
 
     // Handle __proto__: v specially because *only* this form, and no other
     // involving "__proto__", performs [[Prototype]] mutation.
     if (propdef->isKind(ParseNodeKind::MutateProto)) {
+      //            [stack] OBJ
       MOZ_ASSERT(type == ObjectLiteral);
+      if (!pe.prepareForProtoValue(Some(propdef->pn_pos.begin))) {
+        //          [stack] OBJ
+        return false;
+      }
       if (!emitTree(propdef->as<UnaryNode>().kid())) {
-        return false;
-      }
-      objp.set(nullptr);
-      if (!emit1(JSOP_MUTATEPROTO)) {
+        //          [stack] OBJ PROTO
+        return false;
+      }
+      if (!pe.emitMutateProto()) {
+        //          [stack] OBJ
         return false;
       }
       continue;
     }
 
     if (propdef->isKind(ParseNodeKind::Spread)) {
       MOZ_ASSERT(type == ObjectLiteral);
-
-      if (!emit1(JSOP_DUP)) {
-        return false;
-      }
-
+      //            [stack] OBJ
+      if (!pe.prepareForSpreadOperand(Some(propdef->pn_pos.begin))) {
+        //          [stack] OBJ OBJ
+        return false;
+      }
       if (!emitTree(propdef->as<UnaryNode>().kid())) {
-        return false;
-      }
-
-      if (!emitCopyDataProperties(CopyOption::Unfiltered)) {
-        return false;
-      }
-
-      objp.set(nullptr);
+        //          [stack] OBJ OBJ VAL
+        return false;
+      }
+      if (!pe.emitSpread()) {
+        //          [stack] OBJ
+        return false;
+      }
       continue;
     }
 
-    bool extraPop = false;
-    if (type == ClassBody && propdef->as<ClassMethod>().isStatic()) {
-      extraPop = true;
-      if (!emit1(JSOP_DUP2)) {
-        return false;
-      }
-      if (!emit1(JSOP_POP)) {
-        return false;
-      }
-    }
-
-    /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */
-    ParseNode* key = propdef->as<BinaryNode>().left();
-    bool isIndex = false;
+    BinaryNode* prop = &propdef->as<BinaryNode>();
+
+    ParseNode* key = prop->left();
+    ParseNode* propVal = prop->right();
+    bool isPropertyAnonFunctionOrClass = propVal->isDirectRHSAnonFunction();
+    JSOp op = propdef->getOp();
+    MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER ||
+               op == JSOP_INITPROP_SETTER);
+
+    auto emitValue = [this, &propVal, &pe]() {
+      //            [stack] CTOR? OBJ CTOR? KEY?
+
+      if (!emitTree(propVal)) {
+        //          [stack] CTOR? OBJ CTOR? KEY? VAL
+        return false;
+      }
+
+      if (propVal->isKind(ParseNodeKind::Function) &&
+          propVal->as<CodeNode>().funbox()->needsHomeObject()) {
+        FunctionBox* funbox = propVal->as<CodeNode>().funbox();
+        MOZ_ASSERT(funbox->function()->allowSuperProperty());
+
+        if (!pe.emitInitHomeObject(funbox->asyncKind())) {
+          //        [stack] CTOR? OBJ CTOR? KEY? FUN
+          return false;
+        }
+      }
+      return true;
+    };
+
+    PropertyEmitter::Kind kind =
+        (type == ClassBody && propdef->as<ClassMethod>().isStatic())
+            ? PropertyEmitter::Kind::Static
+            : PropertyEmitter::Kind::Prototype;
     if (key->isKind(ParseNodeKind::NumberExpr)) {
+      //            [stack] CTOR? OBJ
+      if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) {
+        //          [stack] CTOR? OBJ CTOR?
+        return false;
+      }
       if (!emitNumberOp(key->as<NumericLiteral>().value())) {
-        return false;
-      }
-      isIndex = true;
-    } else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
-               key->isKind(ParseNodeKind::StringExpr)) {
-      // EmitClass took care of constructor already.
+        //          [stack] CTOR? OBJ CTOR? KEY
+        return false;
+      }
+      if (!pe.prepareForIndexPropValue()) {
+        //          [stack] CTOR? OBJ CTOR? KEY
+        return false;
+      }
+      if (!emitValue()) {
+        //          [stack] CTOR? OBJ CTOR? KEY VAL
+        return false;
+      }
+
+      switch (op) {
+        case JSOP_INITPROP:
+          if (!pe.emitInitIndexProp(isPropertyAnonFunctionOrClass)) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
+          break;
+        case JSOP_INITPROP_GETTER:
+          MOZ_ASSERT(!isPropertyAnonFunctionOrClass);
+          if (!pe.emitInitIndexGetter()) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
+          break;
+        case JSOP_INITPROP_SETTER:
+          MOZ_ASSERT(!isPropertyAnonFunctionOrClass);
+          if (!pe.emitInitIndexSetter()) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
+          break;
+        default:
+          MOZ_CRASH("Invalid op");
+      }
+
+      continue;
+    }
+
+    if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
+        key->isKind(ParseNodeKind::StringExpr)) {
+      //            [stack] CTOR? OBJ
+
+      // emitClass took care of constructor already.
       if (type == ClassBody &&
           key->as<NameNode>().atom() == cx->names().constructor &&
           !propdef->as<ClassMethod>().isStatic()) {
         continue;
       }
-    } else {
-      MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName));
-      if (!emitComputedPropertyName(&key->as<UnaryNode>())) {
-        return false;
-      }
-      isIndex = true;
-    }
-
-    /* Emit code for the property initializer. */
-    ParseNode* propVal = propdef->as<BinaryNode>().right();
-    if (!emitTree(propVal)) {
-      return false;
-    }
-
-    JSOp op = propdef->getOp();
-    MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER ||
-               op == JSOP_INITPROP_SETTER);
-
-    FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER
-                                        ? FunctionPrefixKind::Get
-                                        : op == JSOP_INITPROP_SETTER
-                                              ? FunctionPrefixKind::Set
-                                              : FunctionPrefixKind::None;
-
-    if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) {
-      objp.set(nullptr);
-    }
-
-    if (propVal->isKind(ParseNodeKind::Function) &&
-        propVal->as<CodeNode>().funbox()->needsHomeObject()) {
-      FunctionBox* funbox = propVal->as<CodeNode>().funbox();
-      MOZ_ASSERT(funbox->function()->allowSuperProperty());
-      bool isAsync = funbox->isAsync();
-      if (isAsync) {
-        if (!emit1(JSOP_SWAP)) {
-          return false;
+
+      if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) {
+        //          [stack] CTOR? OBJ CTOR?
+        return false;
+      }
+      if (!emitValue()) {
+        //          [stack] CTOR? OBJ CTOR? VAL
+        return false;
+      }
+
+      RootedFunction anonFunction(cx);
+      if (isPropertyAnonFunctionOrClass) {
+        MOZ_ASSERT(op == JSOP_INITPROP);
+
+        if (propVal->isKind(ParseNodeKind::Function)) {
+          // When the value is function, we set the function's name
+          // at the compile-time, instead of emitting SETFUNNAME.
+          FunctionBox* funbox = propVal->as<CodeNode>().funbox();
+          anonFunction = funbox->function();
+        } else {
+          // Only object literal can have a property where key is
+          // name and value is an anonymous class.
+          //
+          //   ({ foo: class {} });
+          MOZ_ASSERT(type == ObjectLiteral);
+          MOZ_ASSERT(propVal->isKind(ParseNodeKind::ClassDecl));
         }
       }
-      if (!emitDupAt(1 + isIndex + isAsync)) {
-        return false;
-      }
-      if (!emit1(JSOP_INITHOMEOBJECT)) {
-        return false;
-      }
-      if (isAsync) {
-        if (!emit1(JSOP_POP)) {
-          return false;
-        }
-      }
-    }
-
-    // Class methods are not enumerable.
-    if (type == ClassBody) {
+
+      RootedAtom keyAtom(cx, key->as<NameNode>().atom());
       switch (op) {
         case JSOP_INITPROP:
-          op = JSOP_INITHIDDENPROP;
+          if (!pe.emitInitProp(keyAtom, isPropertyAnonFunctionOrClass,
+                               anonFunction)) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
           break;
         case JSOP_INITPROP_GETTER:
-          op = JSOP_INITHIDDENPROP_GETTER;
+          MOZ_ASSERT(!isPropertyAnonFunctionOrClass);
+          if (!pe.emitInitGetter(keyAtom)) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
           break;
         case JSOP_INITPROP_SETTER:
-          op = JSOP_INITHIDDENPROP_SETTER;
+          MOZ_ASSERT(!isPropertyAnonFunctionOrClass);
+          if (!pe.emitInitSetter(keyAtom)) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
           break;
         default:
           MOZ_CRASH("Invalid op");
       }
-    }
-
-    if (isIndex) {
-      objp.set(nullptr);
-      switch (op) {
-        case JSOP_INITPROP:
-          op = JSOP_INITELEM;
-          break;
-        case JSOP_INITHIDDENPROP:
-          op = JSOP_INITHIDDENELEM;
-          break;
-        case JSOP_INITPROP_GETTER:
-          op = JSOP_INITELEM_GETTER;
-          break;
-        case JSOP_INITHIDDENPROP_GETTER:
-          op = JSOP_INITHIDDENELEM_GETTER;
-          break;
-        case JSOP_INITPROP_SETTER:
-          op = JSOP_INITELEM_SETTER;
-          break;
-        case JSOP_INITHIDDENPROP_SETTER:
-          op = JSOP_INITHIDDENELEM_SETTER;
-          break;
-        default:
-          MOZ_CRASH("Invalid op");
-      }
-      if (propVal->isDirectRHSAnonFunction()) {
-        if (!emitDupAt(1)) {
-          return false;
-        }
-        if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) {
+
+      continue;
+    }
+
+    MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName));
+
+    //              [stack] CTOR? OBJ
+
+    if (!pe.prepareForComputedPropKey(Some(propdef->pn_pos.begin), kind)) {
+      //            [stack] CTOR? OBJ CTOR?
+      return false;
+    }
+    if (!emitTree(key->as<UnaryNode>().kid())) {
+      //            [stack] CTOR? OBJ CTOR? KEY
+      return false;
+    }
+    if (!pe.prepareForComputedPropValue()) {
+      //            [stack] CTOR? OBJ CTOR? KEY
+      return false;
+    }
+    if (!emitValue()) {
+      //            [stack] CTOR? OBJ CTOR? KEY VAL
+      return false;
+    }
+
+    switch (op) {
+      case JSOP_INITPROP:
+        if (!pe.emitInitComputedProp(isPropertyAnonFunctionOrClass)) {
+          //        [stack] CTOR? OBJ
           return false;
         }
-      }
-      if (!emit1(op)) {
-        return false;
-      }
-    } else {
-      MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) ||
-                 key->isKind(ParseNodeKind::StringExpr));
-
-      uint32_t index;
-      if (!makeAtomIndex(key->as<NameNode>().atom(), &index)) {
-        return false;
-      }
-
-      if (objp) {
-        MOZ_ASSERT(type == ObjectLiteral);
-        MOZ_ASSERT(!IsHiddenInitOp(op));
-        MOZ_ASSERT(!objp->inDictionaryMode());
-        Rooted<jsid> id(cx, AtomToId(key->as<NameNode>().atom()));
-        if (!NativeDefineDataProperty(cx, objp, id, UndefinedHandleValue,
-                                      JSPROP_ENUMERATE)) {
+        break;
+      case JSOP_INITPROP_GETTER:
+        MOZ_ASSERT(isPropertyAnonFunctionOrClass);
+        if (!pe.emitInitComputedGetter()) {
+          //        [stack] CTOR? OBJ
           return false;
         }
-        if (objp->inDictionaryMode()) {
-          objp.set(nullptr);
-        }
-      }
-
-      if (propVal->isDirectRHSAnonFunction()) {
-        MOZ_ASSERT(prefixKind == FunctionPrefixKind::None);
-
-        RootedAtom keyName(cx, key->as<NameNode>().atom());
-        if (!setOrEmitSetFunName(propVal, keyName)) {
+        break;
+      case JSOP_INITPROP_SETTER:
+        MOZ_ASSERT(isPropertyAnonFunctionOrClass);
+        if (!pe.emitInitComputedSetter()) {
+          //        [stack] CTOR? OBJ
           return false;
         }
-      }
-      if (!emitIndex32(op, index)) {
-        return false;
-      }
-    }
-
-    if (extraPop) {
-      if (!emit1(JSOP_POP)) {
-        return false;
-      }
+        break;
+      default:
+        MOZ_CRASH("Invalid op");
     }
   }
   return true;
 }
 
 // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
 // the comment on emitSwitch.
 MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ListNode* objNode) {
   if (!objNode->hasNonConstInitializer() && objNode->head() &&
       checkSingletonContext()) {
     return emitSingletonInitialiser(objNode);
   }
 
-  /*
-   * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing
-   * a new object and defining (in source order) each property on the object
-   * (or mutating the object's [[Prototype]], in the case of __proto__).
-   */
-  ptrdiff_t offset = this->offset();
-  if (!emitNewInit()) {
-    return false;
-  }
-
-  // Try to construct the shape of the object as we go, so we can emit a
-  // JSOP_NEWOBJECT with the final shape instead.
-  // In the case of computed property names and indices, we cannot fix the
-  // shape at bytecode compile time. When the shape cannot be determined,
-  // |obj| is nulled out.
-
-  // No need to do any guessing for the object kind, since we know the upper
-  // bound of how many properties we plan to have.
-  gc::AllocKind kind = gc::GetGCObjectKind(objNode->count());
-  RootedPlainObject obj(
-      cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
-  if (!obj) {
-    return false;
-  }
-
-  if (!emitPropertyList(objNode, &obj, ObjectLiteral)) {
-    return false;
-  }
-
-  if (obj) {
-    // The object survived and has a predictable shape: update the original
-    // bytecode.
-    if (!replaceNewInitWithNewObject(obj, offset)) {
-      return false;
-    }
+  //                [stack]
+
+  ObjectEmitter oe(this);
+  if (!oe.emitObject(objNode->count())) {
+    //              [stack] OBJ
+    return false;
+  }
+  if (!emitPropertyList(objNode, oe, ObjectLiteral)) {
+    //              [stack] OBJ
+    return false;
+  }
+  if (!oe.emitEnd()) {
+    //              [stack] OBJ
+    return false;
   }
 
   return true;
 }
 
 bool BytecodeEmitter::replaceNewInitWithNewObject(JSObject* obj,
                                                   ptrdiff_t offset) {
   ObjectBox* objbox = parser->newObjectBox(obj);
@@ -8402,299 +8398,139 @@ bool BytecodeEmitter::emitFunctionBody(P
       return false;
     }
   }
 
   return true;
 }
 
 bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) {
-  NameOpEmitter noe(this, name->name(), NameOpEmitter::Kind::Initialize);
+  return emitLexicalInitialization(name->name());
+}
+
+bool BytecodeEmitter::emitLexicalInitialization(JSAtom* name) {
+  NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize);
   if (!noe.prepareForRhs()) {
     return false;
   }
 
   // The caller has pushed the RHS to the top of the stack. Assert that the
   // name is lexical and no BIND[G]NAME ops were emitted.
   MOZ_ASSERT(noe.loc().isLexical());
   MOZ_ASSERT(!noe.emittedBindOp());
 
   if (!noe.emitAssignment()) {
     return false;
   }
 
   return true;
 }
 
-// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
-// (BindingClassDeclarationEvaluation).
-bool BytecodeEmitter::emitClass(ClassNode* classNode) {
-  ClassNames* names = classNode->names();
-  ParseNode* heritageExpression = classNode->heritage();
-  ListNode* classMembers = classNode->memberList();
-  CodeNode* constructor = nullptr;
-  for (ParseNode* mn : classMembers->contents()) {
+static MOZ_ALWAYS_INLINE CodeNode* FindConstructor(JSContext* cx,
+                                                   ListNode* classMethods) {
+  for (ParseNode* mn : classMethods->contents()) {
     if (mn->is<ClassField>()) {
       // TODO(khyperia): Implement private field access.
-      return false;
-    }
+      continue;
+    }
+
     ClassMethod& method = mn->as<ClassMethod>();
     ParseNode& methodName = method.name();
     if (!method.isStatic() &&
         (methodName.isKind(ParseNodeKind::ObjectPropertyName) ||
          methodName.isKind(ParseNodeKind::StringExpr)) &&
         methodName.as<NameNode>().atom() == cx->names().constructor) {
-      constructor = &method.method();
-      break;
-    }
-  }
-
-  bool savedStrictness = sc->setLocalStrictMode(true);
-
-  Maybe<TDZCheckCache> tdzCache;
-  Maybe<EmitterScope> emitterScope;
+      return &method.method();
+    }
+  }
+
+  return nullptr;
+}
+
+// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
+// (BindingClassDeclarationEvaluation).
+bool BytecodeEmitter::emitClass(ClassNode* classNode) {
+  ClassNames* names = classNode->names();
+  ParseNode* heritageExpression = classNode->heritage();
+  ListNode* classMembers = classNode->memberList();
+  CodeNode* constructor = FindConstructor(cx, classMembers);
+
+  //                [stack]
+
+  ClassEmitter ce(this);
+  RootedAtom innerName(cx);
+  ClassEmitter::Kind kind = ClassEmitter::Kind::Expression;
   if (names) {
-    tdzCache.emplace(this);
-    emitterScope.emplace(this);
-    if (!emitterScope->enterLexical(this, ScopeKind::Lexical,
-                                    classNode->scopeBindings())) {
-      return false;
-    }
-  }
-
-  // Pseudocode for class declarations:
-  //
-  //     class extends BaseExpression {
-  //       constructor() { ... }
-  //       ...
-  //       }
-  //
-  //
-  //   if defined <BaseExpression> {
-  //     let heritage = BaseExpression;
-  //
-  //     if (heritage !== null) {
-  //       funProto = heritage;
-  //       objProto = heritage.prototype;
-  //     } else {
-  //       funProto = %FunctionPrototype%;
-  //       objProto = null;
-  //     }
-  //   } else {
-  //     objProto = %ObjectPrototype%;
-  //   }
-  //
-  //   let homeObject = ObjectCreate(objProto);
-  //
-  //   if defined <constructor> {
-  //     if defined <BaseExpression> {
-  //       cons = DefineMethod(<constructor>, proto=homeObject,
-  //                           funProto=funProto);
-  //     } else {
-  //       cons = DefineMethod(<constructor>, proto=homeObject);
-  //     }
-  //   } else {
-  //     if defined <BaseExpression> {
-  //       cons = DefaultDerivedConstructor(proto=homeObject,
-  //                                        funProto=funProto);
-  //     } else {
-  //       cons = DefaultConstructor(proto=homeObject);
-  //     }
-  //   }
-  //
-  //   cons.prototype = homeObject;
-  //   homeObject.constructor = cons;
-  //
-  //   EmitPropertyList(...)
+    innerName = names->innerBinding()->name();
+    MOZ_ASSERT(innerName);
+
+    if (names->outerBinding()) {
+      MOZ_ASSERT(names->outerBinding()->name());
+      MOZ_ASSERT(names->outerBinding()->name() == innerName);
+      kind = ClassEmitter::Kind::Declaration;
+    }
+
+    if (!ce.emitScopeForNamedClass(classNode->scopeBindings())) {
+      //            [stack]
+      return false;
+    }
+  }
 
   // This is kind of silly. In order to the get the home object defined on
   // the constructor, we have to make it second, but we want the prototype
   // on top for EmitPropertyList, because we expect static properties to be
   // rarer. The result is a few more swaps than we would like. Such is life.
-  if (heritageExpression) {
-    InternalIfEmitter ifThenElse(this);
-
+  bool isDerived = !!heritageExpression;
+  if (isDerived) {
     if (!emitTree(heritageExpression)) {
-      //            [stack] ... HERITAGE
-      return false;
-    }
-
-    // Heritage must be null or a non-generator constructor
-    if (!emit1(JSOP_CHECKCLASSHERITAGE)) {
-      //            [stack] ... HERITAGE
-      return false;
-    }
-
-    // [IF] (heritage !== null)
-    if (!emit1(JSOP_DUP)) {
-      //            [stack] ... HERITAGE HERITAGE
-      return false;
-    }
-    if (!emit1(JSOP_NULL)) {
-      //            [stack] ... HERITAGE HERITAGE NULL
-      return false;
-    }
-    if (!emit1(JSOP_STRICTNE)) {
-      //            [stack] ... HERITAGE NE
-      return false;
-    }
-
-    // [THEN] funProto = heritage, objProto = heritage.prototype
-    if (!ifThenElse.emitThenElse()) {
-      return false;
-    }
-    if (!emit1(JSOP_DUP)) {
-      //            [stack] ... HERITAGE HERITAGE
-      return false;
-    }
-    if (!emitAtomOp(cx->names().prototype, JSOP_GETPROP)) {
-      //            [stack] ... HERITAGE PROTO
-      return false;
-    }
-
-    // [ELSE] funProto = %FunctionPrototype%, objProto = null
-    if (!ifThenElse.emitElse()) {
-      return false;
-    }
-    if (!emit1(JSOP_POP)) {
-      //            [stack] ...
-      return false;
-    }
-    if (!emit2(JSOP_BUILTINPROTO, JSProto_Function)) {
-      //            [stack] ... PROTO
-      return false;
-    }
-    if (!emit1(JSOP_NULL)) {
-      //            [stack] ... PROTO NULL
-      return false;
-    }
-
-    // [ENDIF]
-    if (!ifThenElse.emitEnd()) {
-      return false;
-    }
-
-    if (!emit1(JSOP_OBJWITHPROTO)) {
-      //            [stack] ... HERITAGE HOMEOBJ
-      return false;
-    }
-    if (!emit1(JSOP_SWAP)) {
-      //            [stack] ... HOMEOBJ HERITAGE
+      //            [stack] HERITAGE
+      return false;
+    }
+    if (!ce.emitDerivedClass(innerName)) {
+      //            [stack] HERITAGE HOMEOBJ
       return false;
     }
   } else {
-    if (!emitNewInit()) {
-      //            [stack] ... HOMEOBJ
+    if (!ce.emitClass(innerName)) {
+      //            [stack] HOMEOBJ
       return false;
     }
   }
 
   // Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE
   // is not used, an implicit value of %FunctionPrototype% is implied.
-
   if (constructor) {
-    if (!emitFunction(constructor, !!heritageExpression)) {
-      //            [stack] ... HOMEOBJ CONSTRUCTOR
-      return false;
-    }
-    if (constructor->funbox()->needsHomeObject()) {
-      if (!emitDupAt(1)) {
-        //          [stack] ... HOMEOBJ CONSTRUCTOR HOMEOBJ
-        return false;
-      }
-      if (!emit1(JSOP_INITHOMEOBJECT)) {
-        //          [stack] ... HOMEOBJ CONSTRUCTOR
-        return false;
-      }
+    bool needsHomeObject = constructor->funbox()->needsHomeObject();
+    // HERITAGE is consumed inside emitFunction.
+    if (!emitFunction(constructor, isDerived)) {
+      //            [stack] HOMEOBJ CTOR
+      return false;
+    }
+    if (!ce.emitInitConstructor(needsHomeObject)) {
+      //            [stack] CTOR HOMEOBJ
+      return false;
     }
   } else {
-    // In the case of default class constructors, emit the start and end
-    // offsets in the source buffer as source notes so that when we
-    // actually make the constructor during execution, we can give it the
-    // correct toString output.
-    ptrdiff_t classStart = ptrdiff_t(classNode->pn_pos.begin);
-    ptrdiff_t classEnd = ptrdiff_t(classNode->pn_pos.end);
-    if (!newSrcNote3(SRC_CLASS_SPAN, classStart, classEnd)) {
-      return false;
-    }
-
-    JSAtom* name = names ? names->innerBinding()->as<NameNode>().atom()
-                         : cx->names().empty;
-    if (heritageExpression) {
-      if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) {
-        //          [stack] ... HOMEOBJ CONSTRUCTOR
-        return false;
-      }
-    } else {
-      if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) {
-        //          [stack] ... HOMEOBJ CONSTRUCTOR
-        return false;
-      }
-    }
-  }
-
-  if (!emit1(JSOP_SWAP)) {
-    //              [stack] ... CONSTRUCTOR HOMEOBJ
-    return false;
-  }
-
-  if (!emit1(JSOP_DUP2)) {
-    //              [stack] ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR HOMEOBJ
-    return false;
-  }
-  if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) {
-    //              [stack] ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR
-    return false;
-  }
-  if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) {
-    //              [stack] ... CONSTRUCTOR HOMEOBJ
-    return false;
-  }
-
-  RootedPlainObject obj(cx);
-  if (!emitPropertyList(classMembers, &obj, ClassBody)) {
-    //              [stack] ... CONSTRUCTOR HOMEOBJ
-    return false;
-  }
-
-  if (!emit1(JSOP_POP)) {
-    //              [stack] ... CONSTRUCTOR
-    return false;
-  }
-
-  if (names) {
-    NameNode* innerName = names->innerBinding();
-    if (!emitLexicalInitialization(innerName)) {
-      //            [stack] ... CONSTRUCTOR
-      return false;
-    }
-
-    // Pop the inner scope.
-    if (!emitterScope->leave(this)) {
-      return false;
-    }
-    emitterScope.reset();
-
-    if (NameNode* outerName = names->outerBinding()) {
-      if (!emitLexicalInitialization(outerName)) {
-        //          [stack] ... CONSTRUCTOR
-        return false;
-      }
-      // Only class statements make outer bindings, and they do not leave
-      // themselves on the stack.
-      if (!emit1(JSOP_POP)) {
-        //          [stack] ...
-        return false;
-      }
-    }
-  }
-
-  // The CONSTRUCTOR is left on stack if this is an expression.
-
-  MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness));
+    if (!ce.emitInitDefaultConstructor(Some(classNode->pn_pos.begin),
+                                       Some(classNode->pn_pos.end))) {
+      //            [stack] CTOR HOMEOBJ
+      return false;
+    }
+  }
+  if (!emitPropertyList(classMembers, ce, ClassBody)) {
+    //              [stack] CTOR HOMEOBJ
+    return false;
+  }
+  if (!ce.emitEnd(kind)) {
+    //              [stack] # class declaration
+    //              [stack]
+    //              [stack] # class expression
+    //              [stack] CTOR
+    return false;
+  }
 
   return true;
 }
 
 bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) {
   MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportDefaultStmt));
 
   ParseNode* nameNode = exportNode->left();
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -107,16 +107,17 @@ struct CGResumeOffsetList {
 // sequences.  See bug 1390526.
 typedef Vector<jsbytecode, 64> BytecodeVector;
 typedef Vector<jssrcnote, 64> SrcNotesVector;
 
 class CallOrNewEmitter;
 class ElemOpEmitter;
 class EmitterScope;
 class NestableControl;
+class PropertyEmitter;
 class TDZCheckCache;
 
 struct MOZ_STACK_CLASS BytecodeEmitter {
   SharedContext* const
       sc; /* context shared between parsing and bytecode generation */
 
   JSContext* const cx;
 
@@ -599,18 +600,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
                                                   bool needsProto = false);
   MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ListNode* objNode);
 
   MOZ_MUST_USE bool replaceNewInitWithNewObject(JSObject* obj,
                                                 ptrdiff_t offset);
 
   MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList);
 
-  MOZ_MUST_USE bool emitPropertyList(ListNode* obj,
-                                     MutableHandlePlainObject objp,
+  MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe,
                                      PropListType type);
 
   // To catch accidental misuse, emitUint16Operand/emit3 assert that they are
   // not used to unconditionally emit JSOP_GETLOCAL. Variable access should
   // instead be emitted using EmitVarOp. In special cases, when the caller
   // definitely knows that a given local slot is unaliased, this function may be
   // used as a non-asserting version of emitUint16Operand.
   MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot);
@@ -682,17 +682,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   MOZ_MUST_USE bool emitElemOp(PropertyByValue* elem, JSOp op);
   MOZ_MUST_USE bool emitElemIncDec(UnaryNode* incDec);
 
   MOZ_MUST_USE bool emitCatch(BinaryNode* catchClause);
   MOZ_MUST_USE bool emitIf(TernaryNode* ifNode);
   MOZ_MUST_USE bool emitWith(BinaryNode* withNode);
 
   MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLabeledStatement(
-      const LabeledStatement* pn);
+      const LabeledStatement* labeledStmt);
   MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLexicalScope(
       LexicalScopeNode* lexicalScope);
   MOZ_MUST_USE bool emitLexicalScopeBody(
       ParseNode* body, EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
   MOZ_NEVER_INLINE MOZ_MUST_USE bool emitSwitch(SwitchStatement* switchStmt);
   MOZ_NEVER_INLINE MOZ_MUST_USE bool emitTry(TryNode* tryNode);
 
   MOZ_MUST_USE bool emitGoSub(JumpList* jump);
@@ -778,16 +778,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   // that value on the stack with the value defined by |defaultExpr|.
   // |pattern| is a lhs node of the default expression.  If it's an
   // identifier and |defaultExpr| is an anonymous function, |SetFunctionName|
   // is called at compile time.
   MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern);
 
   MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name);
 
+  MOZ_MUST_USE bool setFunName(JSFunction* fun, JSAtom* name);
+  MOZ_MUST_USE bool emitSetClassConstructorName(JSAtom* name);
   MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern);
 
   MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj);
   MOZ_MUST_USE bool emitTemplateString(ListNode* templateString);
   MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp compoundOp,
                                    ParseNode* rhs);
 
   MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode);
@@ -846,16 +848,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   MOZ_MUST_USE bool emitBreak(PropertyName* label);
   MOZ_MUST_USE bool emitContinue(PropertyName* label);
 
   MOZ_MUST_USE bool emitFunctionFormalParametersAndBody(ListNode* paramsBody);
   MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody);
   MOZ_MUST_USE bool emitInitializeFunctionSpecialNames();
   MOZ_MUST_USE bool emitFunctionBody(ParseNode* funBody);
   MOZ_MUST_USE bool emitLexicalInitialization(NameNode* name);
+  MOZ_MUST_USE bool emitLexicalInitialization(JSAtom* name);
 
   // Emit bytecode for the spread operator.
   //
   // emitSpread expects the current index (I) of the array, the array itself
   // and the iterator to be on the stack in that order (iterator on the bottom).
   // It will pop the iterator and I, then iterate over the iterator by calling
   // |.next()| and put the results into the I-th element of array with
   // incrementing I, then push the result I (it will be original I +
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/LabelEmitter.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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 "frontend/LabelEmitter.h"
+
+#include "mozilla/Assertions.h"  // MOZ_ASSERT
+
+#include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
+#include "vm/Opcodes.h"                // JSOP_*
+
+using namespace js;
+using namespace js::frontend;
+
+bool LabelEmitter::emitLabel(JSAtom* name) {
+  MOZ_ASSERT(state_ == State::Start);
+
+  // Emit a JSOP_LABEL instruction. The operand is the offset to the
+  // statement following the labeled statement.
+  uint32_t index;
+  if (!bce_->makeAtomIndex(name, &index)) {
+    return false;
+  }
+  if (!bce_->emitJump(JSOP_LABEL, &top_)) {
+    return false;
+  }
+
+  controlInfo_.emplace(bce_, name, bce_->offset());
+
+#ifdef DEBUG
+  state_ = State::Label;
+#endif
+  return true;
+}
+
+bool LabelEmitter::emitEnd() {
+  MOZ_ASSERT(state_ == State::Label);
+
+  // Patch the JSOP_LABEL offset.
+  JumpTarget brk{bce_->lastNonJumpTargetOffset()};
+  bce_->patchJumpsToTarget(top_, brk);
+
+  // Patch the break/continue to this label.
+  if (!controlInfo_->patchBreaks(bce_)) {
+    return false;
+  }
+
+  controlInfo_.reset();
+
+#ifdef DEBUG
+  state_ = State::End;
+#endif
+  return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/LabelEmitter.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+#ifndef frontend_LabelEmitter_h
+#define frontend_LabelEmitter_h
+
+#include "mozilla/Attributes.h"  // MOZ_MUST_USE, MOZ_STACK_CLASS
+#include "mozilla/Maybe.h"       // Maybe
+
+#include "frontend/BytecodeControlStructures.h"  // LabelControl
+#include "frontend/JumpList.h"                   // JumpList
+
+class JSAtom;
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting labeled statement.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `label: expr;`
+//     LabelEmitter le(this);
+//     le.emitLabel(name_of_label);
+//     emit(expr);
+//     le.emitEnd();
+//
+class MOZ_STACK_CLASS LabelEmitter {
+  BytecodeEmitter* bce_;
+
+  // The offset of the JSOP_LABEL.
+  JumpList top_;
+
+  mozilla::Maybe<LabelControl> controlInfo_;
+
+#ifdef DEBUG
+  // The state of this emitter.
+  //
+  // +-------+ emitLabel +-------+ emitEnd +-----+
+  // | Start |---------->| Label |-------->| End |
+  // +-------+           +-------+         +-----+
+  enum class State {
+    // The initial state.
+    Start,
+
+    // After calling emitLabel.
+    Label,
+
+    // After calling emitEnd.
+    End
+  };
+  State state_ = State::Start;
+#endif
+
+ public:
+  explicit LabelEmitter(BytecodeEmitter* bce) : bce_(bce) {}
+
+  MOZ_MUST_USE bool emitLabel(JSAtom* name);
+  MOZ_MUST_USE bool emitEnd();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_LabelEmitter_h */
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ObjectEmitter.cpp
@@ -0,0 +1,813 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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 "frontend/ObjectEmitter.h"
+
+#include "mozilla/Assertions.h"  // MOZ_ASSERT
+
+#include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
+#include "frontend/IfEmitter.h"        // IfEmitter
+#include "frontend/SharedContext.h"    // SharedContext
+#include "frontend/SourceNotes.h"      // SRC_*
+#include "gc/AllocKind.h"              // AllocKind
+#include "js/Id.h"                     // jsid
+#include "js/Value.h"                  // UndefinedHandleValue
+#include "vm/BytecodeUtil.h"           // IsHiddenInitOp
+#include "vm/JSContext.h"              // JSContext
+#include "vm/NativeObject.h"           // NativeDefineDataProperty
+#include "vm/ObjectGroup.h"            // TenuredObject
+#include "vm/Opcodes.h"                // JSOP_*
+#include "vm/Runtime.h"                // JSAtomState (cx->names())
+
+#include "gc/ObjectKind-inl.h"  // GetGCObjectKind
+#include "vm/JSAtom-inl.h"      // AtomToId
+#include "vm/JSObject-inl.h"    // NewBuiltinClassInstance
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce)
+    : bce_(bce), obj_(bce->cx) {}
+
+bool PropertyEmitter::prepareForProtoValue(const Maybe<uint32_t>& keyPos) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+
+  //                [stack] CTOR? OBJ CTOR?
+
+  if (keyPos) {
+    if (!bce_->updateSourceCoordNotes(*keyPos)) {
+      return false;
+    }
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::ProtoValue;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitMutateProto() {
+  MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue);
+
+  //                [stack] OBJ PROTO
+
+  if (!bce_->emit1(JSOP_MUTATEPROTO)) {
+    //              [stack] OBJ
+    return false;
+  }
+
+  obj_ = nullptr;
+#ifdef DEBUG
+  propertyState_ = PropertyState::Init;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::prepareForSpreadOperand(
+    const Maybe<uint32_t>& spreadPos) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+
+  //                [stack] OBJ
+
+  if (spreadPos) {
+    if (!bce_->updateSourceCoordNotes(*spreadPos)) {
+      return false;
+    }
+  }
+  if (!bce_->emit1(JSOP_DUP)) {
+    //              [stack] OBJ OBJ
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::SpreadOperand;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitSpread() {
+  MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand);
+
+  //                [stack] OBJ OBJ VAL
+
+  if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) {
+    //              [stack] OBJ
+    return false;
+  }
+
+  obj_ = nullptr;
+#ifdef DEBUG
+  propertyState_ = PropertyState::Init;
+#endif
+  return true;
+}
+
+MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp(
+    const Maybe<uint32_t>& keyPos, bool isStatic, bool isIndexOrComputed) {
+  isStatic_ = isStatic;
+  isIndexOrComputed_ = isIndexOrComputed;
+
+  //                [stack] CTOR? OBJ
+
+  if (keyPos) {
+    if (!bce_->updateSourceCoordNotes(*keyPos)) {
+      return false;
+    }
+  }
+
+  if (isStatic_) {
+    if (!bce_->emit1(JSOP_DUP2)) {
+      //            [stack] CTOR HOMEOBJ CTOR HOMEOBJ
+      return false;
+    }
+    if (!bce_->emit1(JSOP_POP)) {
+      //            [stack] CTOR HOMEOBJ CTOR
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool PropertyEmitter::prepareForPropValue(const Maybe<uint32_t>& keyPos,
+                                          Kind kind /* = Kind::Prototype */) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+
+  //                [stack] CTOR? OBJ
+
+  if (!prepareForProp(keyPos,
+                      /* isStatic_ = */ kind == Kind::Static,
+                      /* isIndexOrComputed = */ false)) {
+    //              [stack] CTOR? OBJ CTOR?
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::PropValue;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::prepareForIndexPropKey(
+    const Maybe<uint32_t>& keyPos, Kind kind /* = Kind::Prototype */) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+
+  //                [stack] CTOR? OBJ
+
+  obj_ = nullptr;
+
+  if (!prepareForProp(keyPos,
+                      /* isStatic_ = */ kind == Kind::Static,
+                      /* isIndexOrComputed = */ true)) {
+    //              [stack] CTOR? OBJ CTOR?
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::IndexKey;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::prepareForIndexPropValue() {
+  MOZ_ASSERT(propertyState_ == PropertyState::IndexKey);
+
+  //                [stack] CTOR? OBJ CTOR? KEY
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::IndexValue;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::prepareForComputedPropKey(
+    const Maybe<uint32_t>& keyPos, Kind kind /* = Kind::Prototype */) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+
+  //                [stack] CTOR? OBJ
+
+  obj_ = nullptr;
+
+  if (!prepareForProp(keyPos,
+                      /* isStatic_ = */ kind == Kind::Static,
+                      /* isIndexOrComputed = */ true)) {
+    //              [stack] CTOR? OBJ CTOR?
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::ComputedKey;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::prepareForComputedPropValue() {
+  MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey);
+
+  //                [stack] CTOR? OBJ CTOR? KEY
+
+  if (!bce_->emit1(JSOP_TOID)) {
+    //              [stack] CTOR? OBJ CTOR? KEY
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::ComputedValue;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitInitHomeObject(
+    FunctionAsyncKind kind /* = FunctionAsyncKind::SyncFunction */) {
+  MOZ_ASSERT(propertyState_ == PropertyState::PropValue ||
+             propertyState_ == PropertyState::IndexValue ||
+             propertyState_ == PropertyState::ComputedValue);
+
+  //                [stack] CTOR? HOMEOBJ CTOR? KEY? FUN
+
+  bool isAsync = kind == FunctionAsyncKind::AsyncFunction;
+  if (isAsync) {
+    //              [stack] CTOR? HOMEOBJ CTOR? KEY? UNWRAPPED WRAPPED
+    if (!bce_->emit1(JSOP_SWAP)) {
+      //            [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED UNWRAPPED
+      return false;
+    }
+  }
+
+  // There are the following values on the stack conditionally, between
+  // HOMEOBJ and FUN:
+  //   * the 2nd CTOR if isStatic_
+  //   * KEY if isIndexOrComputed_
+  //   * WRAPPED if isAsync
+  //
+  // JSOP_INITHOMEOBJECT uses one of the following:
+  //   * HOMEOBJ if !isStatic_
+  //     (`super.foo` points the super prototype property)
+  //   * the 2nd CTOR if isStatic_
+  //     (`super.foo` points the super constructor property)
+  if (!bce_->emitDupAt(1 + isIndexOrComputed_ + isAsync)) {
+    //              [stack] # non-static method
+    //              [stack] CTOR? HOMEOBJ CTOR KEY? WRAPPED? FUN CTOR
+    //              [stack] # static method
+    //              [stack] CTOR? HOMEOBJ KEY? WRAPPED? FUN HOMEOBJ
+    return false;
+  }
+  if (!bce_->emit1(JSOP_INITHOMEOBJECT)) {
+    //              [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED? FUN
+    return false;
+  }
+  if (isAsync) {
+    if (!bce_->emit1(JSOP_POP)) {
+      //            [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED
+      return false;
+    }
+  }
+
+#ifdef DEBUG
+  if (propertyState_ == PropertyState::PropValue) {
+    propertyState_ = PropertyState::InitHomeObj;
+  } else if (propertyState_ == PropertyState::IndexValue) {
+    propertyState_ = PropertyState::InitHomeObjForIndex;
+  } else {
+    propertyState_ = PropertyState::InitHomeObjForComputed;
+  }
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitInitProp(
+    JS::Handle<JSAtom*> key, bool isPropertyAnonFunctionOrClass /* = false */,
+    JS::Handle<JSFunction*> anonFunction /* = nullptr */) {
+  return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key,
+                  isPropertyAnonFunctionOrClass, anonFunction);
+}
+
+bool PropertyEmitter::emitInitGetter(JS::Handle<JSAtom*> key) {
+  return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER,
+                  key, false, nullptr);
+}
+
+bool PropertyEmitter::emitInitSetter(JS::Handle<JSAtom*> key) {
+  return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER,
+                  key, false, nullptr);
+}
+
+bool PropertyEmitter::emitInitIndexProp(
+    bool isPropertyAnonFunctionOrClass /* = false */) {
+  return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM,
+                                 FunctionPrefixKind::None,
+                                 isPropertyAnonFunctionOrClass);
+}
+
+bool PropertyEmitter::emitInitIndexGetter() {
+  return emitInitIndexOrComputed(
+      isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER,
+      FunctionPrefixKind::Get, false);
+}
+
+bool PropertyEmitter::emitInitIndexSetter() {
+  return emitInitIndexOrComputed(
+      isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER,
+      FunctionPrefixKind::Set, false);
+}
+
+bool PropertyEmitter::emitInitComputedProp(
+    bool isPropertyAnonFunctionOrClass /* = false */) {
+  return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM,
+                                 FunctionPrefixKind::None,
+                                 isPropertyAnonFunctionOrClass);
+}
+
+bool PropertyEmitter::emitInitComputedGetter() {
+  return emitInitIndexOrComputed(
+      isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER,
+      FunctionPrefixKind::Get, true);
+}
+
+bool PropertyEmitter::emitInitComputedSetter() {
+  return emitInitIndexOrComputed(
+      isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER,
+      FunctionPrefixKind::Set, true);
+}
+
+bool PropertyEmitter::emitInit(JSOp op, JS::Handle<JSAtom*> key,
+                               bool isPropertyAnonFunctionOrClass,
+                               JS::Handle<JSFunction*> anonFunction) {
+  MOZ_ASSERT(propertyState_ == PropertyState::PropValue ||
+             propertyState_ == PropertyState::InitHomeObj);
+
+  MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP ||
+             op == JSOP_INITPROP_GETTER || op == JSOP_INITHIDDENPROP_GETTER ||
+             op == JSOP_INITPROP_SETTER || op == JSOP_INITHIDDENPROP_SETTER);
+
+  //                [stack] CTOR? OBJ CTOR? VAL
+
+  uint32_t index;
+  if (!bce_->makeAtomIndex(key, &index)) {
+    return false;
+  }
+
+  if (obj_) {
+    MOZ_ASSERT(!IsHiddenInitOp(op));
+    MOZ_ASSERT(!obj_->inDictionaryMode());
+    JS::Rooted<JS::PropertyKey> propKey(bce_->cx, AtomToId(key));
+    if (!NativeDefineDataProperty(bce_->cx, obj_, propKey, UndefinedHandleValue,
+                                  JSPROP_ENUMERATE)) {
+      return false;
+    }
+    if (obj_->inDictionaryMode()) {
+      obj_ = nullptr;
+    }
+  }
+
+  if (isPropertyAnonFunctionOrClass) {
+    MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP);
+
+    if (anonFunction) {
+      if (!bce_->setFunName(anonFunction, key)) {
+        return false;
+      }
+    } else {
+      // NOTE: This is setting the constructor's name of the class which is
+      //       the property value.  Not of the enclosing class.
+      if (!bce_->emitSetClassConstructorName(key)) {
+        //          [stack] CTOR? OBJ CTOR? FUN
+        return false;
+      }
+    }
+  }
+
+  if (!bce_->emitIndex32(op, index)) {
+    //              [stack] CTOR? OBJ CTOR?
+    return false;
+  }
+
+  if (!emitPopClassConstructor()) {
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::Init;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitInitIndexOrComputed(
+    JSOp op, FunctionPrefixKind prefixKind,
+    bool isPropertyAnonFunctionOrClass) {
+  MOZ_ASSERT(propertyState_ == PropertyState::IndexValue ||
+             propertyState_ == PropertyState::InitHomeObjForIndex ||
+             propertyState_ == PropertyState::ComputedValue ||
+             propertyState_ == PropertyState::InitHomeObjForComputed);
+
+  MOZ_ASSERT(op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM ||
+             op == JSOP_INITELEM_GETTER || op == JSOP_INITHIDDENELEM_GETTER ||
+             op == JSOP_INITELEM_SETTER || op == JSOP_INITHIDDENELEM_SETTER);
+
+  //                [stack] CTOR? OBJ CTOR? KEY VAL
+
+  if (isPropertyAnonFunctionOrClass) {
+    if (!bce_->emitDupAt(1)) {
+      //            [stack] CTOR? OBJ CTOR? KEY FUN FUN
+      return false;
+    }
+    if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) {
+      //            [stack] CTOR? OBJ CTOR? KEY FUN
+      return false;
+    }
+  }
+
+  if (!bce_->emit1(op)) {
+    //              [stack] CTOR? OBJ CTOR?
+    return false;
+  }
+
+  if (!emitPopClassConstructor()) {
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::Init;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitPopClassConstructor() {
+  if (isStatic_) {
+    //              [stack] CTOR HOMEOBJ CTOR
+
+    if (!bce_->emit1(JSOP_POP)) {
+      //            [stack] CTOR HOMEOBJ
+      return false;
+    }
+  }
+
+  return true;
+}
+
+ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {}
+
+bool ObjectEmitter::emitObject(size_t propertyCount) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(objectState_ == ObjectState::Start);
+
+  //                [stack]
+
+  // Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing
+  // a new object and defining (in source order) each property on the object
+  // (or mutating the object's [[Prototype]], in the case of __proto__).
+  top_ = bce_->offset();
+  if (!bce_->emitNewInit()) {
+    //              [stack] OBJ
+    return false;
+  }
+
+  // Try to construct the shape of the object as we go, so we can emit a
+  // JSOP_NEWOBJECT with the final shape instead.
+  // In the case of computed property names and indices, we cannot fix the
+  // shape at bytecode compile time. When the shape cannot be determined,
+  // |obj| is nulled out.
+
+  // No need to do any guessing for the object kind, since we know the upper
+  // bound of how many properties we plan to have.
+  gc::AllocKind kind = gc::GetGCObjectKind(propertyCount);
+  obj_ = NewBuiltinClassInstance<PlainObject>(bce_->cx, kind, TenuredObject);
+  if (!obj_) {
+    return false;
+  }
+
+#ifdef DEBUG
+  objectState_ = ObjectState::Object;
+#endif
+  return true;
+}
+
+bool ObjectEmitter::emitEnd() {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+  MOZ_ASSERT(objectState_ == ObjectState::Object);
+
+  //                [stack] OBJ
+
+  if (obj_) {
+    // The object survived and has a predictable shape: update the original
+    // bytecode.
+    if (!bce_->replaceNewInitWithNewObject(obj_, top_)) {
+      //            [stack] OBJ
+      return false;
+    }
+  }
+
+#ifdef DEBUG
+  objectState_ = ObjectState::End;
+#endif
+  return true;
+}
+
+AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) {
+  savedStrictness_ = sc_->setLocalStrictMode(true);
+}
+
+AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() {
+  if (sc_) {
+    restore();
+  }
+}
+
+void AutoSaveLocalStrictMode::restore() {
+  MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_));
+  sc_ = nullptr;
+}
+
+ClassEmitter::ClassEmitter(BytecodeEmitter* bce)
+    : PropertyEmitter(bce), strictMode_(bce->sc), name_(bce->cx) {
+  isClass_ = true;
+}
+
+bool ClassEmitter::emitScopeForNamedClass(
+    JS::Handle<LexicalScope::Data*> scopeBindings) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(classState_ == ClassState::Start);
+
+  tdzCacheForInnerName_.emplace(bce_);
+  innerNameScope_.emplace(bce_);
+  if (!innerNameScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) {
+    return false;
+  }
+
+#ifdef DEBUG
+  classState_ = ClassState::Scope;
+#endif
+  return true;
+}
+
+bool ClassEmitter::emitClass(JS::Handle<JSAtom*> name) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(classState_ == ClassState::Start ||
+             classState_ == ClassState::Scope);
+
+  //                [stack]
+
+  setName(name);
+  isDerived_ = false;
+
+  if (!bce_->emitNewInit()) {
+    //              [stack] HOMEOBJ
+    return false;
+  }
+
+#ifdef DEBUG
+  classState_ = ClassState::Class;
+#endif
+  return true;
+}
+
+bool ClassEmitter::emitDerivedClass(JS::Handle<JSAtom*> name) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(classState_ == ClassState::Start ||
+             classState_ == ClassState::Scope);
+
+  //                [stack]
+
+  setName(name);
+  isDerived_ = true;
+
+  InternalIfEmitter ifThenElse(bce_);
+
+  // Heritage must be null or a non-generator constructor
+  if (!bce_->emit1(JSOP_CHECKCLASSHERITAGE)) {
+    //              [stack] HERITAGE
+    return false;
+  }
+
+  // [IF] (heritage !== null)
+  if (!bce_->emit1(JSOP_DUP)) {
+    //              [stack] HERITAGE HERITAGE
+    return false;
+  }
+  if (!bce_->emit1(JSOP_NULL)) {
+    //              [stack] HERITAGE HERITAGE NULL
+    return false;
+  }
+  if (!bce_->emit1(JSOP_STRICTNE)) {
+    //              [stack] HERITAGE NE
+    return false;
+  }
+
+  // [THEN] funProto = heritage, objProto = heritage.prototype
+  if (!ifThenElse.emitThenElse()) {
+    return false;
+  }
+  if (!bce_->emit1(JSOP_DUP)) {
+    //              [stack] HERITAGE HERITAGE
+    return false;
+  }
+  if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_GETPROP)) {
+    //              [stack] HERITAGE PROTO
+    return false;
+  }
+
+  // [ELSE] funProto = %FunctionPrototype%, objProto = null
+  if (!ifThenElse.emitElse()) {
+    return false;
+  }
+  if (!bce_->emit1(JSOP_POP)) {
+    //              [stack]
+    return false;
+  }
+  if (!bce_->emit2(JSOP_BUILTINPROTO, JSProto_Function)) {
+    //              [stack] PROTO
+    return false;
+  }
+  if (!bce_->emit1(JSOP_NULL)) {
+    //              [stack] PROTO NULL
+    return false;
+  }
+
+  // [ENDIF]
+  if (!ifThenElse.emitEnd()) {
+    return false;
+  }
+
+  if (!bce_->emit1(JSOP_OBJWITHPROTO)) {
+    //              [stack] HERITAGE HOMEOBJ
+    return false;
+  }
+  if (!bce_->emit1(JSOP_SWAP)) {
+    //              [stack] HOMEOBJ HERITAGE
+    return false;
+  }
+
+#ifdef DEBUG
+  classState_ = ClassState::Class;
+#endif
+  return true;
+}
+
+void ClassEmitter::setName(JS::Handle<JSAtom*> name) {
+  name_ = name;
+  if (!name_) {
+    name_ = bce_->cx->names().empty;
+  }
+}
+
+bool ClassEmitter::emitInitConstructor(bool needsHomeObject) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(classState_ == ClassState::Class);
+
+  //                [stack] HOMEOBJ CTOR
+
+  if (needsHomeObject) {
+    if (!bce_->emitDupAt(1)) {
+      //            [stack] HOMEOBJ CTOR HOMEOBJ
+      return false;
+    }
+    if (!bce_->emit1(JSOP_INITHOMEOBJECT)) {
+      //            [stack] HOMEOBJ CTOR
+      return false;
+    }
+  }
+
+  if (!initProtoAndCtor()) {
+    //              [stack] CTOR HOMEOBJ
+    return false;
+  }
+
+#ifdef DEBUG
+  classState_ = ClassState::InitConstructor;
+#endif
+  return true;
+}
+
+bool ClassEmitter::emitInitDefaultConstructor(const Maybe<uint32_t>& classStart,
+                                              const Maybe<uint32_t>& classEnd) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(classState_ == ClassState::Class);
+
+  if (classStart && classEnd) {
+    // In the case of default class constructors, emit the start and end
+    // offsets in the source buffer as source notes so that when we
+    // actually make the constructor during execution, we can give it the
+    // correct toString output.
+    if (!bce_->newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(*classStart),
+                           ptrdiff_t(*classEnd))) {
+      return false;
+    }
+  }
+
+  if (isDerived_) {
+    //              [stack] HERITAGE PROTO
+    if (!bce_->emitAtomOp(name_, JSOP_DERIVEDCONSTRUCTOR)) {
+      //            [stack] HOMEOBJ CTOR
+      return false;
+    }
+  } else {
+    //              [stack] HOMEOBJ
+    if (!bce_->emitAtomOp(name_, JSOP_CLASSCONSTRUCTOR)) {
+      //            [stack] HOMEOBJ CTOR
+      return false;
+    }
+  }
+
+  if (!initProtoAndCtor()) {
+    //              [stack] CTOR HOMEOBJ
+    return false;
+  }
+
+#ifdef DEBUG
+  classState_ = ClassState::InitConstructor;
+#endif
+  return true;
+}
+
+bool ClassEmitter::initProtoAndCtor() {
+  //                [stack] HOMEOBJ CTOR
+
+  if (!bce_->emit1(JSOP_SWAP)) {
+    //              [stack] CTOR HOMEOBJ
+    return false;
+  }
+  if (!bce_->emit1(JSOP_DUP2)) {
+    //              [stack] CTOR HOMEOBJ CTOR HOMEOBJ
+    return false;
+  }
+  if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_INITLOCKEDPROP)) {
+    //              [stack] CTOR HOMEOBJ CTOR
+    return false;
+  }
+  if (!bce_->emitAtomOp(bce_->cx->names().constructor, JSOP_INITHIDDENPROP)) {
+    //              [stack] CTOR HOMEOBJ
+    return false;
+  }
+
+  return true;
+}
+
+bool ClassEmitter::emitEnd(Kind kind) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+  MOZ_ASSERT(classState_ == ClassState::InitConstructor);
+
+  //                [stack] CTOR HOMEOBJ
+
+  if (!bce_->emit1(JSOP_POP)) {
+    //              [stack] CTOR
+    return false;
+  }
+
+  if (name_ != bce_->cx->names().empty) {
+    MOZ_ASSERT(tdzCacheForInnerName_.isSome());
+    MOZ_ASSERT(innerNameScope_.isSome());
+
+    if (!bce_->emitLexicalInitialization(name_)) {
+      //            [stack] CTOR
+      return false;
+    }
+
+    if (!innerNameScope_->leave(bce_)) {
+      return false;
+    }
+    innerNameScope_.reset();
+
+    if (kind == Kind::Declaration) {
+      if (!bce_->emitLexicalInitialization(name_)) {
+        //          [stack] CTOR
+        return false;
+      }
+      // Only class statements make outer bindings, and they do not leave
+      // themselves on the stack.
+      if (!bce_->emit1(JSOP_POP)) {
+        //          [stack]
+        return false;
+      }
+    }
+
+    tdzCacheForInnerName_.reset();
+  } else {
+    //              [stack] CTOR
+
+    MOZ_ASSERT(tdzCacheForInnerName_.isNothing());
+  }
+
+  //                [stack] # class declaration
+  //                [stack]
+  //                [stack] # class expression
+  //                [stack] CTOR
+
+  strictMode_.restore();
+
+#ifdef DEBUG
+  classState_ = ClassState::End;
+#endif
+  return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ObjectEmitter.h
@@ -0,0 +1,722 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+#ifndef frontend_ObjectEmitter_h
+#define frontend_ObjectEmitter_h
+
+#include "mozilla/Attributes.h"  // MOZ_MUST_USE, MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII
+#include "mozilla/Maybe.h"  // Maybe
+
+#include <stddef.h>  // size_t, ptrdiff_t
+#include <stdint.h>  // uint32_t
+
+#include "frontend/EmitterScope.h"   // EmitterScope
+#include "frontend/TDZCheckCache.h"  // TDZCheckCache
+#include "js/RootingAPI.h"           // JS::Handle, JS::Rooted
+#include "vm/BytecodeUtil.h"         // JSOp
+#include "vm/JSAtom.h"               // JSAtom
+#include "vm/JSFunction.h"           // JSFunction, FunctionPrefixKind
+#include "vm/JSScript.h"             // FunctionAsyncKind
+#include "vm/NativeObject.h"         // PlainObject
+#include "vm/Scope.h"                // LexicalScope
+
+namespace js {
+
+namespace frontend {
+
+struct BytecodeEmitter;
+class SharedContext;
+
+// Class for emitting bytecode for object and class properties.
+// See ObjectEmitter and ClassEmitter for usage.
+class MOZ_STACK_CLASS PropertyEmitter {
+ public:
+  enum class Kind {
+    // Prototype property.
+    Prototype,
+
+    // Class static property.
+    Static
+  };
+
+ protected:
+  BytecodeEmitter* bce_;
+
+  // True if the object is class.
+  // Set by ClassEmitter.
+  bool isClass_ = false;
+
+  // True if the property is class static method.
+  bool isStatic_ = false;
+
+  // True if the property has computed or index key.
+  bool isIndexOrComputed_ = false;
+
+  // An object which keeps the shape of this object literal.
+  // This fields is reset to nullptr whenever the object literal turns out to
+  // have at least one numeric, computed, spread or __proto__ property, or
+  // the object becomes dictionary mode.
+  // This field is used only in ObjectEmitter.
+  JS::Rooted<PlainObject*> obj_;
+
+#ifdef DEBUG
+  // The state of this emitter.
+  //
+  // +-------+
+  // | Start |-+
+  // +-------+ |
+  //           |
+  // +---------+
+  // |
+  // |  +------------------------------------------------------------+
+  // |  |                                                            |
+  // |  | [normal property/method/accessor]                          |
+  // |  v   prepareForPropValue  +-----------+              +------+ |
+  // +->+----------------------->| PropValue |-+         +->| Init |-+
+  //    |                        +-----------+ |         |  +------+
+  //    |                                      |         |
+  //    |  +----------------------------------+          +-----------+
+  //    |  |                                                         |
+  //    |  +-+---------------------------------------+               |
+  //    |    |                                       |               |
+  //    |    | [method with super]                   |               |
+  //    |    |   emitInitHomeObject +-------------+  v               |
+  //    |    +--------------------->| InitHomeObj |->+               |
+  //    |                           +-------------+  |               |
+  //    |                                            |               |
+  //    |    +-------------------------------------- +               |
+  //    |    |                                                       |
+  //    |    | emitInitProp                                          |
+  //    |    | emitInitGetter                                        |
+  //    |    | emitInitSetter                                        |
+  //    |    +------------------------------------------------------>+
+  //    |                                                            ^
+  //    | [index property/method/accessor]                           |
+  //    |   prepareForIndexPropKey  +----------+                     |
+  //    +-------------------------->| IndexKey |-+                   |
+  //    |                           +----------+ |                   |
+  //    |                                        |                   |
+  //    |  +-------------------------------------+                   |
+  //    |  |                                                         |
+  //    |  | prepareForIndexPropValue +------------+                 |
+  //    |  +------------------------->| IndexValue |-+               |
+  //    |                             +------------+ |               |
+  //    |                                            |               |
+  //    |    +---------------------------------------+               |
+  //    |    |                                                       |
+  //    |    +-+--------------------------------------------------+  |
+  //    |      |                                                  |  |
+  //    |      | [method with super]                              |  |
+  //    |      |   emitInitHomeObject +---------------------+     v  |
+  //    |      +--------------------->| InitHomeObjForIndex |---->+  |
+  //    |                             +---------------------+     |  |
+  //    |                                                         |  |
+  //    |      +--------------------------------------------------+  |
+  //    |      |                                                     |
+  //    |      | emitInitIndexProp                                   |
+  //    |      | emitInitIndexGetter                                 |
+  //    |      | emitInitIndexSetter                                 |
+  //    |      +---------------------------------------------------->+
+  //    |                                                            |
+  //    | [computed property/method/accessor]                        |
+  //    |   prepareForComputedPropKey  +-------------+               |
+  //    +----------------------------->| ComputedKey |-+             |
+  //    |                              +-------------+ |             |
+  //    |                                              |             |
+  //    |  +-------------------------------------------+             |
+  //    |  |                                                         |
+  //    |  | prepareForComputedPropValue +---------------+           |
+  //    |  +---------------------------->| ComputedValue |-+         |
+  //    |                                +---------------+ |         |
+  //    |                                                  |         |
+  //    |    +---------------------------------------------+         |
+  //    |    |                                                       |
+  //    |    +-+--------------------------------------------------+  |
+  //    |      |                                                  |  |
+  //    |      | [method with super]                              |  |
+  //    |      |   emitInitHomeObject +------------------------+  v  |
+  //    |      +--------------------->| InitHomeObjForComputed |->+  |
+  //    |                             +------------------------+  |  |
+  //    |                                                         |  |
+  //    |      +--------------------------------------------------+  |
+  //    |      |                                                     |
+  //    |      | emitInitComputedProp                                |
+  //    |      | emitInitComputedGetter                              |
+  //    |      | emitInitComputedSetter                              |
+  //    |      +---------------------------------------------------->+
+  //    |                                                            ^
+  //    |                                                            |
+  //    | [__proto__]                                                |
+  //    |   prepareForProtoValue  +------------+ emitMutateProto     |
+  //    +------------------------>| ProtoValue |-------------------->+
+  //    |                         +------------+                     ^
+  //    |                                                            |
+  //    | [...prop]                                                  |
+  //    |   prepareForSpreadOperand +---------------+ emitSpread     |
+  //    +-------------------------->| SpreadOperand |----------------+
+  //                                +---------------+
+  enum class PropertyState {
+    // The initial state.
+    Start,
+
+    // After calling prepareForPropValue.
+    PropValue,
+
+    // After calling emitInitHomeObject, from PropValue.
+    InitHomeObj,
+
+    // After calling prepareForIndexPropKey.
+    IndexKey,
+
+    // prepareForIndexPropValue.
+    IndexValue,
+
+    // After calling emitInitHomeObject, from IndexValue.
+    InitHomeObjForIndex,
+
+    // After calling prepareForComputedPropKey.
+    ComputedKey,
+
+    // prepareForComputedPropValue.
+    ComputedValue,
+
+    // After calling emitInitHomeObject, from ComputedValue.
+    InitHomeObjForComputed,
+
+    // After calling prepareForProtoValue.
+    ProtoValue,
+
+    // After calling prepareForSpreadOperand.
+    SpreadOperand,
+
+    // After calling one of emitInitProp, emitInitGetter, emitInitSetter,
+    // emitInitIndexOrComputedProp, emitInitIndexOrComputedGetter,
+    // emitInitIndexOrComputedSetter, emitMutateProto, or emitSpread.
+    Init,
+  };
+  PropertyState propertyState_ = PropertyState::Start;
+#endif
+
+ public:
+  explicit PropertyEmitter(BytecodeEmitter* bce);
+
+  // Parameters are the offset in the source code for each character below:
+  //
+  // { __proto__: protoValue }
+  //   ^
+  //   |
+  //   keyPos
+  MOZ_MUST_USE bool prepareForProtoValue(
+      const mozilla::Maybe<uint32_t>& keyPos);
+  MOZ_MUST_USE bool emitMutateProto();
+
+  // { ...obj }
+  //   ^
+  //   |
+  //   spreadPos
+  MOZ_MUST_USE bool prepareForSpreadOperand(
+      const mozilla::Maybe<uint32_t>& spreadPos);
+  MOZ_MUST_USE bool emitSpread();
+
+  // { key: value }
+  //   ^
+  //   |
+  //   keyPos
+  MOZ_MUST_USE bool prepareForPropValue(const mozilla::Maybe<uint32_t>& keyPos,
+                                        Kind kind = Kind::Prototype);
+
+  // { 1: value }
+  //   ^
+  //   |
+  //   keyPos
+  MOZ_MUST_USE bool prepareForIndexPropKey(
+      const mozilla::Maybe<uint32_t>& keyPos, Kind kind = Kind::Prototype);
+  MOZ_MUST_USE bool prepareForIndexPropValue();
+
+  // { [ key ]: value }
+  //   ^
+  //   |
+  //   keyPos
+  MOZ_MUST_USE bool prepareForComputedPropKey(
+      const mozilla::Maybe<uint32_t>& keyPos, Kind kind = Kind::Prototype);
+  MOZ_MUST_USE bool prepareForComputedPropValue();
+
+  MOZ_MUST_USE bool emitInitHomeObject(
+      FunctionAsyncKind kind = FunctionAsyncKind::SyncFunction);
+
+  // @param key
+  //        Property key
+  // @param isPropertyAnonFunctionOrClass
+  //        True if the property value is an anonymous function or
+  //        an anonymous class
+  // @param anonFunction
+  //        The anonymous function object for property value
+  MOZ_MUST_USE bool emitInitProp(
+      JS::Handle<JSAtom*> key, bool isPropertyAnonFunctionOrClass = false,
+      JS::Handle<JSFunction*> anonFunction = nullptr);
+  MOZ_MUST_USE bool emitInitGetter(JS::Handle<JSAtom*> key);
+  MOZ_MUST_USE bool emitInitSetter(JS::Handle<JSAtom*> key);
+
+  MOZ_MUST_USE bool emitInitIndexProp(
+      bool isPropertyAnonFunctionOrClass = false);
+  MOZ_MUST_USE bool emitInitIndexGetter();
+  MOZ_MUST_USE bool emitInitIndexSetter();
+
+  MOZ_MUST_USE bool emitInitComputedProp(
+      bool isPropertyAnonFunctionOrClass = false);
+  MOZ_MUST_USE bool emitInitComputedGetter();
+  MOZ_MUST_USE bool emitInitComputedSetter();
+
+ private:
+  MOZ_MUST_USE MOZ_ALWAYS_INLINE bool prepareForProp(
+      const mozilla::Maybe<uint32_t>& keyPos, bool isStatic, bool isComputed);
+
+  // @param op
+  //        Opcode for initializing property
+  // @param prefixKind
+  //        None, Get, or Set
+  // @param key
+  //        Atom of the property if the property key is not computed
+  // @param isPropertyAnonFunctionOrClass
+  //        True if the property is either an anonymous function or an
+  //        anonymous class
+  // @param anonFunction
+  //        Anonymous function object for the property
+  MOZ_MUST_USE bool emitInit(JSOp op, JS::Handle<JSAtom*> key,
+                             bool isPropertyAnonFunctionOrClass,
+                             JS::Handle<JSFunction*> anonFunction);
+  MOZ_MUST_USE bool emitInitIndexOrComputed(JSOp op,
+                                            FunctionPrefixKind prefixKind,
+                                            bool isPropertyAnonFunctionOrClass);
+
+  MOZ_MUST_USE bool emitPopClassConstructor();
+};
+
+// Class for emitting bytecode for object literal.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `{}`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(0);
+//     oe.emitEnd();
+//
+//   `{ prop: 10 }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(1);
+//
+//     oe.prepareForPropValue(Some(offset_of_prop));
+//     emit(10);
+//     oe.emitInitProp(atom_of_prop);
+//
+//     oe.emitEnd();
+//
+//   `{ prop: function() {} }`, when property value is anonymous function
+//     ObjectEmitter oe(this);
+//     oe.emitObject(1);
+//
+//     oe.prepareForPropValue(Some(offset_of_prop));
+//     emit(function);
+//     oe.emitInitProp(atom_of_prop, true, function_object);
+//
+//     oe.emitEnd();
+//
+//   `{ get prop() { ... }, set prop(v) { ... } }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(2);
+//
+//     oe.prepareForPropValue(Some(offset_of_prop));
+//     emit(function_for_getter);
+//     oe.emitInitGetter(atom_of_prop);
+//
+//     oe.prepareForPropValue(Some(offset_of_prop));
+//     emit(function_for_setter);
+//     oe.emitInitSetter(atom_of_prop);
+//
+//     oe.emitEnd();
+//
+//   `{ 1: 10, get 2() { ... }, set 3(v) { ... } }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(3);
+//
+//     oe.prepareForIndexPropKey(Some(offset_of_prop));
+//     emit(1);
+//     oe.prepareForIndexPropValue();
+//     emit(10);
+//     oe.emitInitIndexedProp(atom_of_prop);
+//
+//     oe.prepareForIndexPropKey(Some(offset_of_opening_bracket));
+//     emit(2);
+//     oe.prepareForIndexPropValue();
+//     emit(function_for_getter);
+//     oe.emitInitIndexGetter();
+//
+//     oe.prepareForIndexPropKey(Some(offset_of_opening_bracket));
+//     emit(3);
+//     oe.prepareForIndexPropValue();
+//     emit(function_for_setter);
+//     oe.emitInitIndexSetter();
+//
+//     oe.emitEnd();
+//
+//   `{ [prop1]: 10, get [prop2]() { ... }, set [prop3](v) { ... } }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(3);
+//
+//     oe.prepareForComputedPropKey(Some(offset_of_opening_bracket));
+//     emit(prop1);
+//     oe.prepareForComputedPropValue();
+//     emit(10);
+//     oe.emitInitComputedProp();
+//
+//     oe.prepareForComputedPropKey(Some(offset_of_opening_bracket));
+//     emit(prop2);
+//     oe.prepareForComputedPropValue();
+//     emit(function_for_getter);
+//     oe.emitInitComputedGetter();
+//
+//     oe.prepareForComputedPropKey(Some(offset_of_opening_bracket));
+//     emit(prop3);
+//     oe.prepareForComputedPropValue();
+//     emit(function_for_setter);
+//     oe.emitInitComputedSetter();
+//
+//     oe.emitEnd();
+//
+//   `{ __proto__: obj }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(1);
+//     oe.prepareForProtoValue(Some(offset_of___proto__));
+//     emit(obj);
+//     oe.emitMutateProto();
+//     oe.emitEnd();
+//
+//   `{ ...obj }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(1);
+//     oe.prepareForSpreadOperand(Some(offset_of_triple_dots));
+//     emit(obj);
+//     oe.emitSpread();
+//     oe.emitEnd();
+//
+class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter {
+ private:
+  // The offset of JSOP_NEWINIT, which is replced by JSOP_NEWOBJECT later
+  // when the object is known to have a fixed shape.
+  ptrdiff_t top_ = 0;
+
+#ifdef DEBUG
+  // The state of this emitter.
+  //
+  // +-------+ emitObject +--------+
+  // | Start |----------->| Object |-+
+  // +-------+            +--------+ |
+  //                                 |
+  //   +-----------------------------+
+  //   |
+  //   | (do PropertyEmitter operation)  emitEnd  +-----+
+  //   +-------------------------------+--------->| End |
+  //                                              +-----+
+  enum class ObjectState {
+    // The initial state.
+    Start,
+
+    // After calling emitObject.
+    Object,
+
+    // After calling emitEnd.
+    End,
+  };
+  ObjectState objectState_ = ObjectState::Start;
+#endif
+
+ public:
+  explicit ObjectEmitter(BytecodeEmitter* bce);
+
+  MOZ_MUST_USE bool emitObject(size_t propertyCount);
+  MOZ_MUST_USE bool emitEnd();
+};
+
+// Save and restore the strictness.
+// Used by class declaration/expression to temporarily enable strict mode.
+class MOZ_RAII AutoSaveLocalStrictMode {
+  SharedContext* sc_;
+  bool savedStrictness_;
+
+ public:
+  explicit AutoSaveLocalStrictMode(SharedContext* sc);
+  ~AutoSaveLocalStrictMode();
+
+  // Force restore the strictness now.
+  void restore();
+};
+
+// Class for emitting bytecode for JS class.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `class {}`
+//     ClassEmitter ce(this);
+//     ce.emitClass();
+//
+//     ce.emitInitDefaultConstructor(Some(offset_of_class),
+//                                   Some(offset_of_closing_bracket));
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `class { constructor() { ... } }`
+//     ClassEmitter ce(this);
+//     ce.emitClass();
+//
+//     emit(function_for_constructor);
+//     ce.emitInitConstructor(/* needsHomeObject = */ false);
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `class X { constructor() { ... } }`
+//     ClassEmitter ce(this);
+//     ce.emitScopeForNamedClass(scopeBindingForName);
+//     ce.emitClass(atom_of_X);
+//
+//     ce.emitInitDefaultConstructor(Some(offset_of_class),
+//                                   Some(offset_of_closing_bracket));
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `class X { constructor() { ... } }`
+//     ClassEmitter ce(this);
+//     ce.emitScopeForNamedClass(scopeBindingForName);
+//     ce.emitClass(atom_of_X);
+//
+//     emit(function_for_constructor);
+//     ce.emitInitConstructor(/* needsHomeObject = */ false);
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `class X extends Y { constructor() { ... } }`
+//     ClassEmitter ce(this);
+//     ce.emitScopeForNamedClass(scopeBindingForName);
+//
+//     emit(Y);
+//     ce.emitDerivedClass(atom_of_X);
+//
+//     emit(function_for_constructor);
+//     ce.emitInitConstructor(/* needsHomeObject = */ false);
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `class X extends Y { constructor() { ... super.f(); ... } }`
+//     ClassEmitter ce(this);
+//     ce.emitScopeForNamedClass(scopeBindingForName);
+//
+//     emit(Y);
+//     ce.emitDerivedClass(atom_of_X);
+//
+//     emit(function_for_constructor);
+//     // pass true if constructor contains super.prop access
+//     ce.emitInitConstructor(/* needsHomeObject = */ true);
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `m() {}` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForPropValue(Some(offset_of_m));
+//     emit(function_for_m);
+//     ce.emitInitProp(atom_of_m);
+//
+//   `m() { super.f(); }` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForPropValue(Some(offset_of_m));
+//     emit(function_for_m);
+//     ce.emitInitHomeObject();
+//     ce.emitInitProp(atom_of_m);
+//
+//   `async m() { super.f(); }` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForPropValue(Some(offset_of_m));
+//     emit(function_for_m);
+//     ce.emitInitHomeObject(FunctionAsyncKind::Async);
+//     ce.emitInitProp(atom_of_m);
+//
+//   `get p() { super.f(); }` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForPropValue(Some(offset_of_p));
+//     emit(function_for_p);
+//     ce.emitInitHomeObject();
+//     ce.emitInitGetter(atom_of_m);
+//
+//   `static m() {}` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForPropValue(Some(offset_of_m),
+//                            PropertyEmitter::Kind::Static);
+//     emit(function_for_m);
+//     ce.emitInitProp(atom_of_m);
+//
+//   `static get [p]() { super.f(); }` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForComputedPropValue(Some(offset_of_m),
+//                                    PropertyEmitter::Kind::Static);
+//     emit(p);
+//     ce.prepareForComputedPropValue();
+//     emit(function_for_m);
+//     ce.emitInitHomeObject();
+//     ce.emitInitComputedGetter();
+//
+class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter {
+ public:
+  enum class Kind {
+    // Class expression.
+    Expression,
+
+    // Class declaration.
+    Declaration,
+  };
+
+ private:
+  // Pseudocode for class declarations:
+  //
+  //     class extends BaseExpression {
+  //       constructor() { ... }
+  //       ...
+  //       }
+  //
+  //
+  //   if defined <BaseExpression> {
+  //     let heritage = BaseExpression;
+  //
+  //     if (heritage !== null) {
+  //       funProto = heritage;
+  //       objProto = heritage.prototype;
+  //     } else {
+  //       funProto = %FunctionPrototype%;
+  //       objProto = null;
+  //     }
+  //   } else {
+  //     objProto = %ObjectPrototype%;
+  //   }
+  //
+  //   let homeObject = ObjectCreate(objProto);
+  //
+  //   if defined <constructor> {
+  //     if defined <BaseExpression> {
+  //       cons = DefineMethod(<constructor>, proto=homeObject,
+  //       funProto=funProto);
+  //     } else {
+  //       cons = DefineMethod(<constructor>, proto=homeObject);
+  //     }
+  //   } else {
+  //     if defined <BaseExpression> {
+  //       cons = DefaultDerivedConstructor(proto=homeObject,
+  //       funProto=funProto);
+  //     } else {
+  //       cons = DefaultConstructor(proto=homeObject);
+  //     }
+  //   }
+  //
+  //   cons.prototype = homeObject;
+  //   homeObject.constructor = cons;
+  //
+  //   EmitPropertyList(...)
+
+  bool isDerived_ = false;
+
+  mozilla::Maybe<TDZCheckCache> tdzCacheForInnerName_;
+  mozilla::Maybe<EmitterScope> innerNameScope_;
+  AutoSaveLocalStrictMode strictMode_;
+
+#ifdef DEBUG
+  // The state of this emitter.
+  //
+  // +-------+
+  // | Start |-+------------------------------------>+-+
+  // +-------+ |                                     ^ |
+  //           | [named class]                       | |
+  //           |   emitScopeForNamedClass  +-------+ | |
+  //           +-------------------------->| Scope |-+ |
+  //                                       +-------+   |
+  //                                                   |
+  //   +-----------------------------------------------+
+  //   |
+  //   |   emitClass           +-------+
+  //   +-+----------------->+->| Class |-+
+  //     |                  ^  +-------+ |
+  //     | emitDerivedClass |            |
+  //     +------------------+            |
+  //                                     |
+  //     +-------------------------------+
+  //     |
+  //     |
+  //     |   emitInitConstructor           +-----------------+
+  //     +-+--------------------------->+->| InitConstructor |-+
+  //       |                            ^  +-----------------+ |
+  //       | emitInitDefaultConstructor |                      |
+  //       +----------------------------+                      |
+  //                                                           |
+  //       +---------------------------------------------------+
+  //       |
+  //       | (do PropertyEmitter operation)  emitEnd  +-----+
+  //       +-------------------------------+--------->| End |
+  //                                                  +-----+
+  enum class ClassState {
+    // The initial state.
+    Start,
+
+    // After calling emitScopeForNamedClass.
+    Scope,
+
+    // After calling emitClass or emitDerivedClass.
+    Class,
+
+    // After calling emitInitConstructor or emitInitDefaultConstructor.
+    InitConstructor,
+
+    // After calling emitEnd.
+    End,
+  };
+  ClassState classState_ = ClassState::Start;
+#endif
+
+  JS::Rooted<JSAtom*> name_;
+
+ public:
+  explicit ClassEmitter(BytecodeEmitter* bce);
+
+  MOZ_MUST_USE bool emitScopeForNamedClass(
+      JS::Handle<LexicalScope::Data*> scopeBindings);
+
+  // @param name
+  //        Name of the class (nullptr if this is anonymous class)
+  MOZ_MUST_USE bool emitClass(JS::Handle<JSAtom*> name);
+  MOZ_MUST_USE bool emitDerivedClass(JS::Handle<JSAtom*> name);
+
+  // @param needsHomeObject
+  //        True if the constructor contains `super.foo`
+  MOZ_MUST_USE bool emitInitConstructor(bool needsHomeObject);
+
+  // Parameters are the offset in the source code for each character below:
+  //
+  //   class X { foo() {} }
+  //   ^                  ^
+  //   |                  |
+  //   |                  classEnd
+  //   |
+  //   classStart
+  //
+  MOZ_MUST_USE bool emitInitDefaultConstructor(
+      const mozilla::Maybe<uint32_t>& classStart,
+      const mozilla::Maybe<uint32_t>& classEnd);
+
+  MOZ_MUST_USE bool emitEnd(Kind kind);
+
+ private:
+  void setName(JS::Handle<JSAtom*> name);
+  MOZ_MUST_USE bool initProtoAndCtor();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ObjectEmitter_h */
--- a/js/src/frontend/moz.build
+++ b/js/src/frontend/moz.build
@@ -36,18 +36,20 @@ UNIFIED_SOURCES += [
     'EmitterScope.cpp',
     'ExpressionStatementEmitter.cpp',
     'FoldConstants.cpp',
     'ForInEmitter.cpp',
     'ForOfEmitter.cpp',
     'ForOfLoopControl.cpp',
     'IfEmitter.cpp',
     'JumpList.cpp',
+    'LabelEmitter.cpp',
     'NameFunctions.cpp',
     'NameOpEmitter.cpp',
+    'ObjectEmitter.cpp',
     'ParseContext.cpp',
     'ParseNode.cpp',
     'PropOpEmitter.cpp',
     'SharedContext.cpp',
     'SwitchEmitter.cpp',
     'TDZCheckCache.cpp',
     'TokenStream.cpp',
     'TryEmitter.cpp',
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -344,17 +344,17 @@ bool GCRuntime::gcIfNeededAtAllocation(J
   if (cx->hasAnyPendingInterrupt()) {
     gcIfRequested();
   }
 
   // If we have grown past our GC heap threshold while in the middle of
   // an incremental GC, we're growing faster than we're GCing, so stop
   // the world and do a full, non-incremental GC right now, if possible.
   if (isIncrementalGCInProgress() &&
-      cx->zone()->usage.gcBytes() > cx->zone()->threshold.gcTriggerBytes()) {
+      cx->zone()->zoneSize.gcBytes() > cx->zone()->threshold.gcTriggerBytes()) {
     PrepareZoneForGC(cx->zone());
     gc(GC_NORMAL, JS::gcreason::INCREMENTAL_TOO_SLOW);
   }
 
   return true;
 }
 
 template <typename T>
@@ -562,21 +562,21 @@ bool GCRuntime::wantBackgroundAllocation
 
 Arena* GCRuntime::allocateArena(Chunk* chunk, Zone* zone, AllocKind thingKind,
                                 ShouldCheckThresholds checkThresholds,
                                 const AutoLockGC& lock) {
   MOZ_ASSERT(chunk->hasAvailableArenas());
 
   // Fail the allocation if we are over our heap size limits.
   if ((checkThresholds != ShouldCheckThresholds::DontCheckThresholds) &&
-      (usage.gcBytes() >= tunables.gcMaxBytes()))
+      (heapSize.gcBytes() >= tunables.gcMaxBytes()))
     return nullptr;
 
   Arena* arena = chunk->allocateArena(rt, zone, thingKind, lock);
-  zone->usage.addGCArena();
+  zone->zoneSize.addGCArena();
 
   // Trigger an incremental slice if needed.
   if (checkThresholds != ShouldCheckThresholds::DontCheckThresholds) {
     maybeAllocTriggerZoneGC(zone, lock);
   }
 
   return arena;
 }
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -887,27 +887,27 @@ void Chunk::updateChunkListAfterFree(JSR
     rt->gc.availableChunks(lock).remove(this);
     decommitAllArenas();
     MOZ_ASSERT(info.numArenasFreeCommitted == 0);
     rt->gc.recycleChunk(this, lock);
   }
 }
 
 void GCRuntime::releaseArena(Arena* arena, const AutoLockGC& lock) {
-  arena->zone->usage.removeGCArena();
+  arena->zone->zoneSize.removeGCArena();
   arena->chunk()->releaseArena(rt, arena, lock);
 }
 
 GCRuntime::GCRuntime(JSRuntime* rt)
     : rt(rt),
       systemZone(nullptr),
       atomsZone(nullptr),
       stats_(rt),
       marker(rt),
-      usage(nullptr),
+      heapSize(nullptr),
       rootsHash(256),
       nextCellUniqueId_(LargestTaggedNullCellPointer +
                         1),  // Ensure disjoint from null tagged pointers.
       numArenasFreeCommitted(0),
       verifyPreData(nullptr),
       chunkAllocationSinceLastGC(false),
       lastGCTime(ReallyNow()),
       mode(TuningDefaults::Mode),
@@ -1374,17 +1374,17 @@ bool GCRuntime::setParameter(JSGCParamKe
     case JSGC_COMPACTING_ENABLED:
       compactingEnabled = value != 0;
       break;
     default:
       if (!tunables.setParameter(key, value, lock)) {
         return false;
       }
       for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
-        zone->threshold.updateAfterGC(zone->usage.gcBytes(), GC_NORMAL,
+        zone->threshold.updateAfterGC(zone->zoneSize.gcBytes(), GC_NORMAL,
                                       tunables, schedulingState, lock);
       }
   }
 
   return true;
 }
 
 bool GCSchedulingTunables::setParameter(JSGCParamKey key, uint32_t value,
@@ -1602,17 +1602,17 @@ void GCRuntime::resetParameter(JSGCParam
       mode = TuningDefaults::Mode;
       break;
     case JSGC_COMPACTING_ENABLED:
       compactingEnabled = TuningDefaults::CompactingEnabled;
       break;
     default:
       tunables.resetParameter(key, lock);
       for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
-        zone->threshold.updateAfterGC(zone->usage.gcBytes(), GC_NORMAL,
+        zone->threshold.updateAfterGC(zone->zoneSize.gcBytes(), GC_NORMAL,
                                       tunables, schedulingState, lock);
       }
   }
 }
 
 void GCSchedulingTunables::resetParameter(JSGCParamKey key,
                                           const AutoLockGC& lock) {
   switch (key) {
@@ -1680,17 +1680,17 @@ void GCSchedulingTunables::resetParamete
 
 uint32_t GCRuntime::getParameter(JSGCParamKey key, const AutoLockGC& lock) {
   switch (key) {
     case JSGC_MAX_BYTES:
       return uint32_t(tunables.gcMaxBytes());
     case JSGC_MAX_MALLOC_BYTES:
       return mallocCounter.maxBytes();
     case JSGC_BYTES:
-      return uint32_t(usage.gcBytes());
+      return uint32_t(heapSize.gcBytes());
     case JSGC_MODE:
       return uint32_t(mode);
     case JSGC_UNUSED_CHUNKS:
       return uint32_t(emptyChunks(lock).count());
     case JSGC_TOTAL_CHUNKS:
       return uint32_t(fullChunks(lock).count() + availableChunks(lock).count() +
                       emptyChunks(lock).count());
     case JSGC_SLICE_TIME_BUDGET:
@@ -3275,17 +3275,17 @@ void GCRuntime::maybeAllocTriggerZoneGC(
   if (!CurrentThreadCanAccessRuntime(rt)) {
     // Zones in use by a helper thread can't be collected.
     MOZ_ASSERT(zone->usedByHelperThread() || zone->isAtomsZone());
     return;
   }
 
   MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
 
-  size_t usedBytes = zone->usage.gcBytes();
+  size_t usedBytes = zone->zoneSize.gcBytes();
   size_t thresholdBytes = zone->threshold.gcTriggerBytes();
 
   if (usedBytes >= thresholdBytes) {
     // The threshold has been surpassed, immediately trigger a GC, which
     // will be done non-incrementally.
     triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER, usedBytes, thresholdBytes);
     return;
   }
@@ -3373,17 +3373,17 @@ void GCRuntime::maybeGC(Zone* zone) {
 #endif
 
   if (gcIfRequested()) {
     return;
   }
 
   float threshold = zone->threshold.eagerAllocTrigger(
       schedulingState.inHighFrequencyGCMode());
-  float usedBytes = zone->usage.gcBytes();
+  float usedBytes = zone->zoneSize.gcBytes();
   if (usedBytes > 1024 * 1024 && usedBytes >= threshold &&
       !isIncrementalGCInProgress() && !isBackgroundSweeping()) {
     stats().recordTrigger(usedBytes, threshold);
     PrepareZoneForGC(zone);
     startGC(GC_NORMAL, JS::gcreason::EAGER_ALLOC_TRIGGER);
   }
 }
 
@@ -5777,17 +5777,17 @@ IncrementalProgress GCRuntime::endSweepi
 
   /* Free LIFO blocks on a background thread if possible. */
   startBackgroundFree();
 
   /* Update the GC state for zones we have swept. */
   for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next()) {
     AutoLockGC lock(rt);
     zone->changeGCState(Zone::Sweep, Zone::Finished);
-    zone->threshold.updateAfterGC(zone->usage.gcBytes(), invocationKind,
+    zone->threshold.updateAfterGC(zone->zoneSize.gcBytes(), invocationKind,
                                   tunables, schedulingState, lock);
     zone->updateAllGCMallocCountersOnGCEnd(lock);
     zone->arenas.unmarkPreMarkedFreeCells();
   }
 
   /*
    * Start background thread to sweep zones if required, sweeping the atoms
    * zone last if present.
@@ -7253,17 +7253,17 @@ GCRuntime::IncrementalResult GCRuntime::
   }
 
   AbortReason resetReason = AbortReason::None;
   for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
     if (!zone->canCollect()) {
       continue;
     }
 
-    if (zone->usage.gcBytes() >= zone->threshold.gcTriggerBytes()) {
+    if (zone->zoneSize.gcBytes() >= zone->threshold.gcTriggerBytes()) {
       CheckZoneIsScheduled(zone, reason, "GC bytes");
       budget.makeUnlimited();
       stats().nonincremental(AbortReason::GCBytesTrigger);
       if (zone->wasGCStarted() && zone->gcState() > Zone::Sweep) {
         resetReason = AbortReason::GCBytesTrigger;
       }
     }
 
@@ -7309,17 +7309,17 @@ class AutoScheduleZonesForGC {
       // To avoid resets, continue to collect any zones that were being
       // collected in a previous slice.
       if (gc->isIncrementalGCInProgress() && zone->wasGCStarted()) {
         zone->scheduleGC();
       }
 
       // This is a heuristic to reduce the total number of collections.
       bool inHighFrequencyMode = gc->schedulingState.inHighFrequencyGCMode();
-      if (zone->usage.gcBytes() >=
+      if (zone->zoneSize.gcBytes() >=
           zone->threshold.eagerAllocTrigger(inHighFrequencyMode)) {
         zone->scheduleGC();
       }
 
       // This ensures we collect zones that have reached the malloc limit.
       if (zone->shouldTriggerGCForTooMuchMalloc()) {
         zone->scheduleGC();
       }
@@ -8131,17 +8131,17 @@ void GCRuntime::mergeRealms(Realm* sourc
     MOZ_ASSERT(r.get() == source);
   }
 
   // Merge the allocator, stats and UIDs in source's zone into target's zone.
   target->zone()->arenas.adoptArenas(&source->zone()->arenas,
                                      targetZoneIsCollecting);
   target->zone()->addTenuredAllocsSinceMinorGC(
       source->zone()->getAndResetTenuredAllocsSinceMinorGC());
-  target->zone()->usage.adopt(source->zone()->usage);
+  target->zone()->zoneSize.adopt(source->zone()->zoneSize);
   target->zone()->adoptUniqueIds(source->zone());
   target->zone()->adoptMallocBytes(source->zone());
 
   // Merge other info in source's zone into target's zone.
   target->zone()->types.typeLifoAlloc().transferFrom(
       &source->zone()->types.typeLifoAlloc());
   MOZ_RELEASE_ASSERT(source->zone()->types.sweepTypeLifoAlloc.ref().isEmpty());
 
@@ -8587,19 +8587,20 @@ char16_t* JS::GCDescription::formatSumma
 
 JS::dbg::GarbageCollectionEvent::Ptr JS::GCDescription::toGCEvent(
     JSContext* cx) const {
   return JS::dbg::GarbageCollectionEvent::Create(
       cx->runtime(), cx->runtime()->gc.stats(),
       cx->runtime()->gc.majorGCCount());
 }
 
-char16_t* JS::GCDescription::formatJSON(JSContext* cx,
-                                        uint64_t timestamp) const {
-  UniqueChars cstr = cx->runtime()->gc.stats().renderJsonMessage(timestamp);
+char16_t* JS::GCDescription::formatJSONTelemetry(JSContext* cx,
+                                                 uint64_t timestamp) const {
+  UniqueChars cstr = cx->runtime()->gc.stats().renderJsonMessage(timestamp,
+      gcstats::Statistics::JSONUse::TELEMETRY);
 
   size_t nchars = strlen(cstr.get());
   UniqueTwoByteChars out(js_pod_malloc<char16_t>(nchars + 1));
   if (!out) {
     return nullptr;
   }
   out.get()[nchars] = 0;
 
@@ -8618,24 +8619,25 @@ TimeStamp JS::GCDescription::endTime(JSC
 TimeStamp JS::GCDescription::lastSliceStart(JSContext* cx) const {
   return cx->runtime()->gc.stats().slices().back().start;
 }
 
 TimeStamp JS::GCDescription::lastSliceEnd(JSContext* cx) const {
   return cx->runtime()->gc.stats().slices().back().end;
 }
 
-JS::UniqueChars JS::GCDescription::sliceToJSON(JSContext* cx) const {
+JS::UniqueChars JS::GCDescription::sliceToJSONProfiler(JSContext* cx) const {
   size_t slices = cx->runtime()->gc.stats().slices().length();
   MOZ_ASSERT(slices > 0);
   return cx->runtime()->gc.stats().renderJsonSlice(slices - 1);
 }
 
-JS::UniqueChars JS::GCDescription::summaryToJSON(JSContext* cx) const {
-  return cx->runtime()->gc.stats().renderJsonMessage(0, false);
+JS::UniqueChars JS::GCDescription::formatJSONProfiler(JSContext* cx) const {
+  return cx->runtime()->gc.stats().renderJsonMessage(0,
+      js::gcstats::Statistics::JSONUse::PROFILER);
 }
 
 JS_PUBLIC_API JS::UniqueChars JS::MinorGcToJSON(JSContext* cx) {
   JSRuntime* rt = cx->runtime();
   return rt->gc.stats().renderNurseryJson(rt);
 }
 
 JS_PUBLIC_API JS::GCSliceCallback JS::SetGCSliceCallback(
@@ -8716,17 +8718,17 @@ uint64_t js::gc::NextCellUniqueId(JSRunt
 }
 
 namespace js {
 namespace gc {
 namespace MemInfo {
 
 static bool GCBytesGetter(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
-  args.rval().setNumber(double(cx->runtime()->gc.usage.gcBytes()));
+  args.rval().setNumber(double(cx->runtime()->gc.heapSize.gcBytes()));
   return true;
 }
 
 static bool GCMaxBytesGetter(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   args.rval().setNumber(double(cx->runtime()->gc.tunables.gcMaxBytes()));
   return true;
 }
@@ -8765,17 +8767,17 @@ static bool MajorGCCountGetter(JSContext
 static bool MinorGCCountGetter(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   args.rval().setNumber(double(cx->runtime()->gc.minorGCCount()));
   return true;
 }
 
 static bool ZoneGCBytesGetter(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
-  args.rval().setNumber(double(cx->zone()->usage.gcBytes()));
+  args.rval().setNumber(double(cx->zone()->zoneSize.gcBytes()));
   return true;
 }
 
 static bool ZoneGCTriggerBytesGetter(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   args.rval().setNumber(double(cx->zone()->threshold.gcTriggerBytes()));
   return true;
 }
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -709,18 +709,18 @@ class GCRuntime {
 
  public:
   gcstats::Statistics& stats() { return stats_.ref(); }
 
   GCMarker marker;
 
   Vector<JS::GCCellPtr, 0, SystemAllocPolicy> unmarkGrayStack;
 
-  /* Track heap usage for this runtime. */
-  HeapUsage usage;
+  /* Track heap size for this runtime. */
+  HeapSize heapSize;
 
   /* GC scheduling state and parameters. */
   GCSchedulingTunables tunables;
   GCSchedulingState schedulingState;
 
   // State used for managing atom mark bitmaps in each zone.
   AtomMarkingRuntime atomMarking;
 
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -837,36 +837,36 @@ static_assert(
     js::gc::ChunkStoreBufferOffset ==
         offsetof(Chunk, trailer) + offsetof(ChunkTrailer, storeBuffer),
     "The hardcoded API storeBuffer offset must match the actual offset.");
 
 /*
  * Tracks the used sizes for owned heap data and automatically maintains the
  * memory usage relationship between GCRuntime and Zones.
  */
-class HeapUsage {
+class HeapSize {
   /*
    * A heap usage that contains our parent's heap usage, or null if this is
    * the top-level usage container.
    */
-  HeapUsage* const parent_;
+  HeapSize* const parent_;
 
   /*
    * The approximate number of bytes in use on the GC heap, to the nearest
    * ArenaSize. This does not include any malloc data. It also does not
    * include not-actively-used addresses that are still reserved at the OS
    * level for GC usage. It is atomic because it is updated by both the active
    * and GC helper threads.
    */
   mozilla::Atomic<size_t, mozilla::ReleaseAcquire,
                   mozilla::recordreplay::Behavior::DontPreserve>
       gcBytes_;
 
  public:
-  explicit HeapUsage(HeapUsage* parent) : parent_(parent), gcBytes_(0) {}
+  explicit HeapSize(HeapSize* parent) : parent_(parent), gcBytes_(0) {}
 
   size_t gcBytes() const { return gcBytes_; }
 
   void addGCArena() {
     gcBytes_ += ArenaSize;
     if (parent_) {
       parent_->addGCArena();
     }
@@ -875,17 +875,17 @@ class HeapUsage {
     MOZ_ASSERT(gcBytes_ >= ArenaSize);
     gcBytes_ -= ArenaSize;
     if (parent_) {
       parent_->removeGCArena();
     }
   }
 
   /* Pair to adoptArenas. Adopts the attendant usage statistics. */
-  void adopt(HeapUsage& other) {
+  void adopt(HeapSize& other) {
     gcBytes_ += other.gcBytes_;
     other.gcBytes_ = 0;
   }
 };
 
 inline void Arena::checkAddress() const {
   mozilla::DebugOnly<uintptr_t> addr = uintptr_t(this);
   MOZ_ASSERT(addr);
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -823,17 +823,17 @@ void js::Nursery::collect(JS::gcreason::
   stats().setStat(gcstats::STAT_NURSERY_STRING_REALMS_DISABLED,
                   numNurseryStringRealmsDisabled);
   stats().setStat(gcstats::STAT_STRINGS_TENURED, numStringsTenured);
   endProfile(ProfileKey::Pretenure);
 
   // We ignore gcMaxBytes when allocating for minor collection. However, if we
   // overflowed, we disable the nursery. The next time we allocate, we'll fail
   // because gcBytes >= gcMaxBytes.
-  if (rt->gc.usage.gcBytes() >= tunables().gcMaxBytes()) {
+  if (rt->gc.heapSize.gcBytes() >= tunables().gcMaxBytes()) {
     disable();
   }
   // Disable the nursery if the user changed the configuration setting.  The
   // nursery can only be re-enabled by resetting the configurationa and
   // restarting firefox.
   if (chunkCountLimit_ == 0) {
     disable();
   }
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -322,17 +322,17 @@ UniqueChars Statistics::formatCompactSum
 
   SprintfLiteral(buffer,
                  "Zones: %d of %d (-%d); Compartments: %d of %d (-%d); "
                  "HeapSize: %.3f MiB; "
                  "HeapChange (abs): %+d (%d); ",
                  zoneStats.collectedZoneCount, zoneStats.zoneCount,
                  zoneStats.sweptZoneCount, zoneStats.collectedCompartmentCount,
                  zoneStats.compartmentCount, zoneStats.sweptCompartmentCount,
-                 double(preBytes) / bytesPerMiB,
+                 double(preHeapSize) / bytesPerMiB,
                  counts[COUNT_NEW_CHUNK] - counts[COUNT_DESTROY_CHUNK],
                  counts[COUNT_NEW_CHUNK] + counts[COUNT_DESTROY_CHUNK]);
   if (!fragments.append(DuplicateString(buffer))) {
     return UniqueChars(nullptr);
   }
 
   MOZ_ASSERT_IF(counts[COUNT_ARENA_RELOCATED], gckind == GC_SHRINK);
   if (gckind == GC_SHRINK) {
@@ -444,17 +444,17 @@ UniqueChars Statistics::formatDetailedDe
       buffer, format, ExplainInvocationKind(gckind),
       ExplainReason(slices_[0].reason), nonincremental() ? "no - " : "yes",
       nonincremental() ? ExplainAbortReason(nonincrementalReason_) : "",
       zoneStats.collectedZoneCount, zoneStats.zoneCount,
       zoneStats.sweptZoneCount, zoneStats.collectedCompartmentCount,
       zoneStats.compartmentCount, zoneStats.sweptCompartmentCount,
       getCount(COUNT_MINOR_GC), getCount(COUNT_STOREBUFFER_OVERFLOW),
       mmu20 * 100., mmu50 * 100., t(sccTotal), t(sccLongest),
-      double(preBytes) / bytesPerMiB,
+      double(preHeapSize) / bytesPerMiB,
       getCount(COUNT_NEW_CHUNK) - getCount(COUNT_DESTROY_CHUNK),
       getCount(COUNT_NEW_CHUNK) + getCount(COUNT_DESTROY_CHUNK),
       double(ArenaSize * getCount(COUNT_ARENA_RELOCATED)) / bytesPerMiB,
       thresholdBuffer);
 
   return DuplicateString(buffer);
 }
 
@@ -580,18 +580,17 @@ void Statistics::writeLogMessage(const c
     vfprintf(gcDebugFile, fmt, args);
     fprintf(gcDebugFile, "\n");
     fflush(gcDebugFile);
   }
   va_end(args);
 }
 #endif
 
-UniqueChars Statistics::renderJsonMessage(uint64_t timestamp,
-                                          bool includeSlices) const {
+UniqueChars Statistics::renderJsonMessage(uint64_t timestamp, Statistics::JSONUse use) const {
   /*
    * The format of the JSON message is specified by the GCMajorMarkerPayload
    * type in perf.html
    * https://github.com/devtools-html/perf.html/blob/master/src/types/markers.js#L62
    *
    * All the properties listed here are created within the timings property
    * of the GCMajor marker.
    */
@@ -602,19 +601,19 @@ UniqueChars Statistics::renderJsonMessag
   Sprinter printer(nullptr, false);
   if (!printer.init()) {
     return UniqueChars(nullptr);
   }
   JSONPrinter json(printer);
 
   json.beginObject();
   json.property("status", "completed");    // JSON Key #1
-  formatJsonDescription(timestamp, json);  // #2-22
+  formatJsonDescription(timestamp, json, use);  // #2-22
 
-  if (includeSlices) {
+  if (use == Statistics::JSONUse::TELEMETRY) {
     json.beginListProperty("slices_list");  // #23
     for (unsigned i = 0; i < slices_.length(); i++) {
       formatJsonSlice(i, json);
     }
     json.endList();
   }
 
   json.beginObjectProperty("totals");  // #24
@@ -622,20 +621,21 @@ UniqueChars Statistics::renderJsonMessag
   json.endObject();
 
   json.endObject();
 
   return printer.release();
 }
 
 void Statistics::formatJsonDescription(uint64_t timestamp,
-                                       JSONPrinter& json) const {
+                                       JSONPrinter& json,
+                                       JSONUse use) const {
   // If you change JSON properties here, please update:
   // Telemetry ping code:
-  //   toolkit/components/telemetry/GCTelemetry.jsm
+  //   toolkit/components/telemetry/other/GCTelemetry.jsm
   // Telemetry documentation:
   //   toolkit/components/telemetry/docs/data/main-ping.rst
   // Telemetry tests:
   //   toolkit/components/telemetry/tests/browser/browser_TelemetryGC.js,
   //   toolkit/components/telemetry/tests/unit/test_TelemetryGC.js
   // Perf.html:
   //   https://github.com/devtools-html/perf.html
   //
@@ -671,17 +671,21 @@ void Statistics::formatJsonDescription(u
   json.property("scc_sweep_total", sccTotal, JSONPrinter::MILLISECONDS);  // #14
   json.property("scc_sweep_max_pause", sccLongest,
                 JSONPrinter::MILLISECONDS);  // #15
 
   if (nonincrementalReason_ != AbortReason::None) {
     json.property("nonincremental_reason",
                   ExplainAbortReason(nonincrementalReason_));  // #16
   }
-  json.property("allocated_bytes", preBytes);  // #17
+  json.property("allocated_bytes", preHeapSize);  // #17
+  if (use == Statistics::JSONUse::PROFILER) {
+    json.property("post_heap_size", postHeapSize);
+  }
+
   uint32_t addedChunks = getCount(COUNT_NEW_CHUNK);
   if (addedChunks) {
     json.property("added_chunks", addedChunks);  // #18
   }
   uint32_t removedChunks = getCount(COUNT_DESTROY_CHUNK);
   if (removedChunks) {
     json.property("removed_chunks", removedChunks);  // #19
   }
@@ -689,17 +693,17 @@ void Statistics::formatJsonDescription(u
   json.property("minor_gc_number", startingMinorGCNumber);  // #21
   json.property("slice_number", startingSliceNumber);       // #22
 }
 
 void Statistics::formatJsonSliceDescription(unsigned i, const SliceData& slice,
                                             JSONPrinter& json) const {
   // If you change JSON properties here, please update:
   // Telemetry ping code:
-  //   toolkit/components/telemetry/GCTelemetry.jsm
+  //   toolkit/components/telemetry/other/GCTelemetry.jsm
   // Telemetry documentation:
   //   toolkit/components/telemetry/docs/data/main-ping.rst
   // Telemetry tests:
   //   toolkit/components/telemetry/tests/browser/browser_TelemetryGC.js,
   //   toolkit/components/telemetry/tests/unit/test_TelemetryGC.js
   // Perf.html:
   //   https://github.com/devtools-html/perf.html
   //
@@ -737,17 +741,18 @@ void Statistics::formatJsonPhaseTimes(co
 }
 
 Statistics::Statistics(JSRuntime* rt)
     : runtime(rt),
       gcTimerFile(nullptr),
       gcDebugFile(nullptr),
       nonincrementalReason_(gc::AbortReason::None),
       allocsSinceMinorGC({0, 0}),
-      preBytes(0),
+      preHeapSize(0),
+      postHeapSize(0),
       thresholdTriggered(false),
       triggerAmount(0.0),
       triggerThreshold(0.0),
       startingMinorGCNumber(0),
       startingMajorGCNumber(0),
       startingSliceNumber(0),
       maxPauseInInterval(0),
       sliceCallback(nullptr),
@@ -966,24 +971,25 @@ void Statistics::printStats() {
 }
 
 void Statistics::beginGC(JSGCInvocationKind kind) {
   slices_.clearAndFree();
   sccTimes.clearAndFree();
   gckind = kind;
   nonincrementalReason_ = gc::AbortReason::None;
 
-  preBytes = runtime->gc.usage.gcBytes();
+  preHeapSize = runtime->gc.heapSize.gcBytes();
   startingMajorGCNumber = runtime->gc.majorGCCount();
   startingSliceNumber = runtime->gc.gcNumber();
 }
 
 void Statistics::endGC() {
   TimeDuration sccTotal, sccLongest;
   sccDurations(&sccTotal, &sccLongest);
+  postHeapSize = runtime->gc.heapSize.gcBytes();
 
   runtime->addTelemetry(JS_TELEMETRY_GC_IS_ZONE_GC,
                         !zoneStats.isFullCollection());
   TimeDuration markTotal = SumPhase(PhaseKind::MARK, phaseTimes);
   TimeDuration markRootsTotal = SumPhase(PhaseKind::MARK_ROOTS, phaseTimes);
   double markTime = t(markTotal);
   size_t markCount = runtime->gc.marker.getMarkCount();
   double markRate = markCount / markTime;
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -281,20 +281,24 @@ struct Statistics {
   void maybePrintProfileHeaders();
 
   // Print header line for profile times.
   void printProfileHeader();
 
   // Print total profile times on shutdown.
   void printTotalProfileTimes();
 
-  // Return JSON for a whole major GC, optionally including detailed
-  // per-slice data.
-  UniqueChars renderJsonMessage(uint64_t timestamp,
-                                bool includeSlices = true) const;
+  enum JSONUse {
+    TELEMETRY,
+    PROFILER
+  };
+
+  // Return JSON for a whole major GC.  If use == PROFILER then
+  // detailed per-slice data and some other fields will be included.
+  UniqueChars renderJsonMessage(uint64_t timestamp, JSONUse use) const;
 
   // Return JSON for the timings of just the given slice.
   UniqueChars renderJsonSlice(size_t sliceNum) const;
 
   // Return JSON for the previous nursery collection.
   UniqueChars renderNurseryJson(JSRuntime* rt) const;
 
 #ifdef DEBUG
@@ -351,18 +355,19 @@ struct Statistics {
    * These events cannot be kept in the above array, we need to take their
    * address.
    */
   struct {
     uint32_t nursery;
     uint32_t tenured;
   } allocsSinceMinorGC;
 
-  /* Allocated space before the GC started. */
-  size_t preBytes;
+  /* Heap size before and after the GC ran. */
+  size_t preHeapSize;
+  size_t postHeapSize;
 
   /* If the GC was triggered by exceeding some threshold, record the
    * threshold and the value that exceeded it. */
   bool thresholdTriggered;
   double triggerAmount;
   double triggerThreshold;
 
   /* GC numbers as of the beginning of the collection. */
@@ -434,17 +439,17 @@ struct Statistics {
       const PhaseTimeTable& phaseTimes) const;
 
   UniqueChars formatDetailedDescription() const;
   UniqueChars formatDetailedSliceDescription(unsigned i,
                                              const SliceData& slice) const;
   UniqueChars formatDetailedPhaseTimes(const PhaseTimeTable& phaseTimes) const;
   UniqueChars formatDetailedTotals() const;
 
-  void formatJsonDescription(uint64_t timestamp, JSONPrinter&) const;
+  void formatJsonDescription(uint64_t timestamp, JSONPrinter&, JSONUse) const;
   void formatJsonSliceDescription(unsigned i, const SliceData& slice,
                                   JSONPrinter&) const;
   void formatJsonPhaseTimes(const PhaseTimeTable& phaseTimes,
                             JSONPrinter&) const;
   void formatJsonSlice(size_t sliceNum, JSONPrinter&) const;
 
   double computeMMU(TimeDuration resolution) const;
 
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -47,17 +47,17 @@ JS::Zone::Zone(JSRuntime* rt)
       gcWeakKeys_(this, SystemAllocPolicy(), rt->randomHashCodeScrambler()),
       typeDescrObjects_(this, this),
       markedAtoms_(this),
       atomCache_(this),
       externalStringCache_(this),
       functionToStringCache_(this),
       keepAtomsCount(this, 0),
       purgeAtomsDeferred(this, 0),
-      usage(&rt->gc.usage),
+      zoneSize(&rt->gc.heapSize),
       threshold(),
       gcDelayBytes(0),
       tenuredStrings(this, 0),
       allocNurseryStrings(this, true),
       propertyTree_(this, this),
       baseShapes_(this, this),
       initialShapes_(this, this),
       nurseryShapes_(this),
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -539,18 +539,18 @@ class Zone : public JS::shadow::Zone,
   js::ExternalStringCache& externalStringCache() {
     return externalStringCache_.ref();
   };
 
   js::FunctionToStringCache& functionToStringCache() {
     return functionToStringCache_.ref();
   }
 
-  // Track heap usage under this Zone.
-  js::gc::HeapUsage usage;
+  // Track heap size under this Zone.
+  js::gc::HeapSize zoneSize;
 
   // Thresholds used to trigger GC.
   js::gc::ZoneHeapThreshold threshold;
 
   // Amount of data to allocate before triggering a new incremental slice for
   // the current GC.
   js::UnprotectedData<size_t> gcDelayBytes;
 
--- a/js/src/jsapi-tests/testGCHooks.cpp
+++ b/js/src/jsapi-tests/testGCHooks.cpp
@@ -19,17 +19,17 @@ static void NonIncrementalGCSliceCallbac
   MOZ_RELEASE_ASSERT(gSliceCallbackCount < mozilla::ArrayLength(expect));
   MOZ_RELEASE_ASSERT(progress == expect[gSliceCallbackCount++]);
   MOZ_RELEASE_ASSERT(desc.isZone_ == false);
   MOZ_RELEASE_ASSERT(desc.invocationKind_ == GC_NORMAL);
   MOZ_RELEASE_ASSERT(desc.reason_ == JS::gcreason::API);
   if (progress == GC_CYCLE_END) {
     mozilla::UniquePtr<char16_t> summary(desc.formatSummaryMessage(cx));
     mozilla::UniquePtr<char16_t> message(desc.formatSliceMessage(cx));
-    mozilla::UniquePtr<char16_t> json(desc.formatJSON(cx, 0));
+    mozilla::UniquePtr<char16_t> json(desc.formatJSONTelemetry(cx, 0));
   }
 }
 
 BEGIN_TEST(testGCSliceCallback) {
   gSliceCallbackCount = 0;
   JS::SetGCSliceCallback(cx, NonIncrementalGCSliceCallback);
   JS_GC(cx);
   JS::SetGCSliceCallback(cx, nullptr);
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1410,10 +1410,10 @@ JS_FRIEND_API JS::Value js::MaybeGetScri
   if (!object->is<ScriptSourceObject>()) {
     return UndefinedValue();
   }
 
   return object->as<ScriptSourceObject>().canonicalPrivate();
 }
 
 JS_FRIEND_API uint64_t js::GetGCHeapUsageForObjectZone(JSObject* obj) {
-  return obj->zone()->usage.gcBytes();
+  return obj->zone()->zoneSize.gcBytes();
 }
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/1520798-2.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<head>
+  <script>
+    function go() {
+      document.getElementById("tweakMe").style.overflowAnchor = "none";
+    }
+  </script>
+</head>
+<body onload="go()">
+  <div style="position:fixed">
+    <div id="tweakMe">Hi</div>
+  </div>
+</body>
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -719,8 +719,9 @@ load 1493741.html
 load 1494380.html
 load 1505817.html
 pref(layout.css.column-span.enabled,true) load 1506216.html
 pref(layout.css.column-span.enabled,true) load 1506306.html
 pref(layout.css.column-span.enabled,true) load 1507196.html
 pref(layout.css.column-span.enabled,true) load 1517033.html
 pref(layout.css.column-span.enabled,true) load 1517297.html
 load 1520798-1.xul
+load 1520798-2.html
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -1587,16 +1587,104 @@ var gCSSProperties = {
       "calc(3*25px) 5px",
       "5px calc(3*25px)",
       "calc(20%) calc(3*25px)",
       "calc(25px*3)",
       "calc(3*25px + 50%)",
             ],
     invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ]
   },
+  "border-start-start-radius": {
+    domProp: "borderStartStartRadius",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    applies_to_first_letter: true,
+    logical: true,
+    prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"},
+    initial_values: [ "0", "0px", "calc(-2px)" ],
+    other_values: [ "0%", "3%", "1px", "2em", // circular
+            "3% 2%", "1px 4px", "2em 2pt", // elliptical
+      "calc(-1%)",
+      "calc(2px)",
+      "calc(50%)",
+      "calc(3*25px)",
+      "calc(3*25px) 5px",
+      "5px calc(3*25px)",
+      "calc(20%) calc(3*25px)",
+      "calc(25px*3)",
+      "calc(3*25px + 50%)",
+            ],
+    invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ]
+  },
+  "border-start-end-radius": {
+    domProp: "borderStartEndRadius",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    applies_to_first_letter: true,
+    logical: true,
+    prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"},
+    initial_values: [ "0", "0px", "calc(-2px)" ],
+    other_values: [ "0%", "3%", "1px", "2em", // circular
+            "3% 2%", "1px 4px", "2em 2pt", // elliptical
+      "calc(-1%)",
+      "calc(2px)",
+      "calc(50%)",
+      "calc(3*25px)",
+      "calc(3*25px) 5px",
+      "5px calc(3*25px)",
+      "calc(20%) calc(3*25px)",
+      "calc(25px*3)",
+      "calc(3*25px + 50%)",
+            ],
+    invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ]
+  },
+  "border-end-start-radius": {
+    domProp: "borderEndStartRadius",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    applies_to_first_letter: true,
+    logical: true,
+    prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"},
+    initial_values: [ "0", "0px", "calc(-2px)" ],
+    other_values: [ "0%", "3%", "1px", "2em", // circular
+            "3% 2%", "1px 4px", "2em 2pt", // elliptical
+      "calc(-1%)",
+      "calc(2px)",
+      "calc(50%)",
+      "calc(3*25px)",
+      "calc(3*25px) 5px",
+      "5px calc(3*25px)",
+      "calc(20%) calc(3*25px)",
+      "calc(25px*3)",
+      "calc(3*25px + 50%)",
+            ],
+    invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ]
+  },
+  "border-end-end-radius": {
+    domProp: "borderEndEndRadius",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    applies_to_first_letter: true,
+    logical: true,
+    prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"},
+    initial_values: [ "0", "0px", "calc(-2px)" ],
+    other_values: [ "0%", "3%", "1px", "2em", // circular
+            "3% 2%", "1px 4px", "2em 2pt", // elliptical
+      "calc(-1%)",
+      "calc(2px)",
+      "calc(50%)",
+      "calc(3*25px)",
+      "calc(3*25px) 5px",
+      "5px calc(3*25px)",
+      "calc(20%) calc(3*25px)",
+      "calc(25px*3)",
+      "calc(3*25px + 50%)",
+            ],
+    invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ]
+  },
   "border-inline-start": {
     domProp: "borderInlineStart",
     inherited: false,
     type: CSS_TYPE_TRUE_SHORTHAND,
     subproperties: [ "border-inline-start-color", "border-inline-start-style", "border-inline-start-width" ],
     initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ],
     other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ],
     invalid_values: [ "5%", "5", "5 green solid" ]
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -59,16 +59,20 @@ function any_unit_to_num(str)
 var FUNC_NEGATIVE = "cubic-bezier(0.25, -2, 0.75, 1)";
 var FUNC_OVERONE = "cubic-bezier(0.25, 0, 0.75, 3)";
 
 var supported_properties = {
     "border-bottom-left-radius": [ test_radius_transition ],
     "border-bottom-right-radius": [ test_radius_transition ],
     "border-top-left-radius": [ test_radius_transition ],
     "border-top-right-radius": [ test_radius_transition ],
+    "border-start-start-radius": [ test_radius_transition ],
+    "border-start-end-radius": [ test_radius_transition ],
+    "border-end-start-radius": [ test_radius_transition ],
+    "border-end-end-radius": [ test_radius_transition ],
     "-moz-box-flex": [ test_float_zeroToOne_transition,
                        test_float_aboveOne_transition,
                        test_float_zeroToOne_clamped ],
     "box-shadow": [ test_shadow_transition ],
     "column-count": [ test_pos_integer_or_auto_transition,
                       test_integer_at_least_one_clamping ],
     "column-rule-color": [ test_color_transition,
                            test_currentcolor_transition ],
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -616,16 +616,17 @@ package org.mozilla.geckoview {
   }
 
   public static interface GeckoSession.TextInputDelegate.RestartReason implements java.lang.annotation.Annotation {
   }
 
   public static interface GeckoSession.TrackingProtectionDelegate {
     method @android.support.annotation.UiThread public void onTrackerBlocked(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable java.lang.String, int);
     field public static final int CATEGORY_AD = 1;
+    field public static final int CATEGORY_AD_EXT = 64;
     field public static final int CATEGORY_ALL = 31;
     field public static final int CATEGORY_ANALYTIC = 2;
     field public static final int CATEGORY_CONTENT = 8;
     field public static final int CATEGORY_NONE = 0;
     field public static final int CATEGORY_SOCIAL = 4;
     field public static final int CATEGORY_TEST = 16;
   }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -3811,17 +3811,17 @@ public class GeckoSession implements Par
      * GeckoSession applications implement this interface to handle tracking
      * protection events.
      **/
     public interface TrackingProtectionDelegate {
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(flag = true,
                 value = { CATEGORY_NONE, CATEGORY_AD, CATEGORY_ANALYTIC,
                           CATEGORY_SOCIAL, CATEGORY_CONTENT, CATEGORY_ALL,
-                          CATEGORY_TEST })
+                          CATEGORY_TEST, CATEGORY_AD_EXT })
         /* package */ @interface Category {}
 
         static final int CATEGORY_NONE = 0;
         /**
          * Block advertisement trackers.
          */
         static final int CATEGORY_AD = 1 << 0;
         /**
@@ -3839,16 +3839,20 @@ public class GeckoSession implements Par
         /**
          * Block Gecko test trackers (used for tests).
          */
         static final int CATEGORY_TEST = 1 << 4;
         /**
          * Block all known trackers.
          */
         static final int CATEGORY_ALL = (1 << 5) - 1;
+        /**
+         * Experimental: Block advertisements.
+         */
+        static final int CATEGORY_AD_EXT = 1 << 6;
 
         /**
          * A tracking element has been blocked from loading.
          * Set blocked tracker categories via GeckoRuntimeSettings and enable
          * tracking protection via GeckoSessionSettings.
          *
         * @param session The GeckoSession that initiated the callback.
         * @param uri The URI of the blocked element.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java
@@ -9,16 +9,23 @@ package org.mozilla.geckoview;
 import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
 
 /* package */ class TrackingProtection {
     private static final String TEST = "test-track-simple";
     private static final String AD = "ads-track-digest256";
     private static final String ANALYTIC = "analytics-track-digest256";
     private static final String SOCIAL = "social-track-digest256";
     private static final String CONTENT = "content-track-digest256";
+    private static final String[] AD_EXT = new String[] {
+        "fanboy-annoyance-digest256",
+        "fanboy-social-digest256",
+        "easylist-digest25",
+        "easyprivacy-digest25",
+        "adguard-digest25"
+    };
 
     /* package */ static String buildPrefValue(int categories) {
         StringBuilder builder = new StringBuilder();
 
         if ((categories == TrackingProtectionDelegate.CATEGORY_NONE)) {
             return "";
         }
         if ((categories & TrackingProtectionDelegate.CATEGORY_TEST) != 0) {
@@ -31,16 +38,21 @@ import org.mozilla.geckoview.GeckoSessio
             builder.append(ANALYTIC).append(',');
         }
         if ((categories & TrackingProtectionDelegate.CATEGORY_SOCIAL) != 0) {
             builder.append(SOCIAL).append(',');
         }
         if ((categories & TrackingProtectionDelegate.CATEGORY_CONTENT) != 0) {
             builder.append(CONTENT).append(',');
         }
+        if ((categories & TrackingProtectionDelegate.CATEGORY_AD_EXT) != 0) {
+            for (final String l: AD_EXT) {
+                builder.append(l).append(',');
+            }
+        }
         // Trim final ','.
         return builder.substring(0, builder.length() - 1);
     }
 
     /* package */ static int listToCategory(final String list) {
         int category = 0;
         if (list.indexOf(TEST) != -1) {
             category |= TrackingProtectionDelegate.CATEGORY_TEST;
@@ -52,11 +64,17 @@ import org.mozilla.geckoview.GeckoSessio
             category |= TrackingProtectionDelegate.CATEGORY_ANALYTIC;
         }
         if (list.indexOf(SOCIAL) != -1) {
             category |= TrackingProtectionDelegate.CATEGORY_SOCIAL;
         }
         if (list.indexOf(CONTENT) != -1) {
             category |= TrackingProtectionDelegate.CATEGORY_CONTENT;
         }
+        for (final String l: AD_EXT) {
+            if (list.indexOf(l) != -1) {
+                category |= TrackingProtectionDelegate.CATEGORY_AD_EXT;
+                break;
+            }
+        }
         return category;
     }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -21,16 +21,18 @@ description: GeckoView API Changelog.
 [66.3]: ../GeckoSessionSettings.html
 
 - Added GeckoRuntimeSetting for enabling desktop viewport. Desktop viewport is
   no longer set by `USER_AGENT_MODE_DESKTOP` and must be set separately.
 
 - Added `@UiThread` to `GeckoSession.releaseSession` and `GeckoSession.setSession`
 
 ## v65
+- Added experimental ad-blocking category to `GeckoSession.TrackingProtectionDelegate`.
+
 - Moved [`CompositorController`][65.1], [`DynamicToolbarAnimator`][65.2],
   [`OverscrollEdgeEffect`][65.3], [`PanZoomController`][65.4] from
   `org.mozilla.gecko.gfx` to [`org.mozilla.geckoview`][65.5]
 
 [65.1]: ../CompositorController.html
 [65.2]: ../DynamicToolbarAnimator.html
 [65.3]: ../OverscrollEdgeEffect.html
 [65.4]: ../PanZoomController.html
@@ -108,9 +110,9 @@ description: GeckoView API Changelog.
 [65.23]: ../GeckoSession.FinderResult.html
 
 - Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
   [`GeckoResult<String>`][65.25].
 
 [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: ../GeckoResult.html
 
-[api-version]: e7a6a3ed65c75f7cb278b693adfa09cae5238ca2
+[api-version]: 45d1d8774e913a3077d7c489274184fd301f14fc
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5566,17 +5566,17 @@ pref("urlclassifier.trackingTable", "tes
 pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");
 
 pref("urlclassifier.features.fingerprinting.blacklistTables", "");
 pref("urlclassifier.features.fingerprinting.whitelistTables", "");
 pref("urlclassifier.features.cryptomining.blacklistTables", "");
 pref("urlclassifier.features.cryptomining.whitelistTables", "");
 
 // These tables will never trigger a gethash call.
-pref("urlclassifier.disallow_completions", "test-malware-simple,test-harmful-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,goog-passwordwhite-proto,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256");
+pref("urlclassifier.disallow_completions", "test-malware-simple,test-harmful-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,goog-passwordwhite-proto,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256,fanboy-annoyance-digest256,fanboy-social-digest256,easylist-digest256,easyprivacy-digest256,adguard-digest256");
 
 // Number of random entries to send with a gethash request
 pref("urlclassifier.gethashnoise", 4);
 
 // Gethash timeout for Safe Browsing
 pref("urlclassifier.gethash.timeout_ms", 5000);
 // Update server response timeout for Safe Browsing
 pref("urlclassifier.update.response_timeout_ms", 30000);
@@ -5635,17 +5635,17 @@ pref("browser.safebrowsing.provider.goog
 pref("browser.safebrowsing.provider.google4.advisoryName", "Google Safe Browsing");
 pref("browser.safebrowsing.provider.google4.dataSharingURL", "https://safebrowsing.googleapis.com/v4/threatHits?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%&$httpMethod=POST");
 pref("browser.safebrowsing.provider.google4.dataSharing.enabled", false);
 
 pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
 
 // Mozilla Safe Browsing provider (for tracking protection and plugin blocking)
 pref("browser.safebrowsing.provider.mozilla.pver", "2.2");
-pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256");
+pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256,fanboy-annoyance-digest256,fanboy-social-digest256,easylist-digest256,easyprivacy-digest256,adguard-digest256");
 pref("browser.safebrowsing.provider.mozilla.updateURL", "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
 pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
 // Set to a date in the past to force immediate download in new profiles.
 pref("browser.safebrowsing.provider.mozilla.nextupdatetime", "1");
 // Block lists for tracking protection. The name values will be used as the keys
 // to lookup the localized name in preferences.properties.
 pref("browser.safebrowsing.provider.mozilla.lists.base", "moz-std");
 pref("browser.safebrowsing.provider.mozilla.lists.content", "moz-full");
--- a/mozglue/misc/interceptor/PatcherDetour.h
+++ b/mozglue/misc/interceptor/PatcherDetour.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_interceptor_PatcherDetour_h
 #define mozilla_interceptor_PatcherDetour_h
 
 #include "mozilla/interceptor/PatcherBase.h"
 #include "mozilla/interceptor/Trampoline.h"
 
 #include "mozilla/ScopeExit.h"
 #include "mozilla/TypedEnumBits.h"
+#include "mozilla/Unused.h"
 
 #define COPY_CODES(NBYTES)                          \
   do {                                              \
     tramp.CopyFrom(origBytes.GetAddress(), NBYTES); \
     origBytes += NBYTES;                            \
   } while (0)
 
 namespace mozilla {
@@ -132,16 +133,17 @@ class WindowsDllDetourPatcher final : pu
         if (!Clear10BytePatch(origBytes)) {
           continue;
         }
       } else {
         MOZ_ASSERT_UNREACHABLE("Unrecognized patch!");
         continue;
       }
 #elif defined(_M_ARM64)
+      Unused << opcode1;
       MOZ_RELEASE_ASSERT(false, "Shouldn't get here");
 #else
 #error "Unknown processor type"
 #endif
     }
 
     this->mVMPolicy.Clear();
   }
--- a/servo/components/style/logical_geometry.rs
+++ b/servo/components/style/logical_geometry.rs
@@ -167,16 +167,59 @@ impl WritingMode {
         match (self.is_vertical(), self.is_vertical_lr()) {
             (false, _) => PhysicalSide::Bottom,
             (true, true) => PhysicalSide::Right,
             (true, false) => PhysicalSide::Left,
         }
     }
 
     #[inline]
+    fn physical_sides_to_corner(block_side: PhysicalSide, inline_side: PhysicalSide) -> PhysicalCorner {
+        match (block_side, inline_side) {
+            (PhysicalSide::Top, PhysicalSide::Left) |
+            (PhysicalSide::Left, PhysicalSide::Top) => PhysicalCorner::TopLeft,
+            (PhysicalSide::Top, PhysicalSide::Right) |
+            (PhysicalSide::Right, PhysicalSide::Top) => PhysicalCorner::TopRight,
+            (PhysicalSide::Bottom, PhysicalSide::Right) |
+            (PhysicalSide::Right, PhysicalSide::Bottom) => PhysicalCorner::BottomRight,
+            (PhysicalSide::Bottom, PhysicalSide::Left) |
+            (PhysicalSide::Left, PhysicalSide::Bottom) => PhysicalCorner::BottomLeft,
+            _ => unreachable!("block and inline sides must be orthogonal")
+        }
+    }
+
+    #[inline]
+    pub fn start_start_physical_corner(&self) -> PhysicalCorner {
+        WritingMode::physical_sides_to_corner(
+            self.block_start_physical_side(),
+            self.inline_start_physical_side())
+    }
+
+    #[inline]
+    pub fn start_end_physical_corner(&self) -> PhysicalCorner {
+        WritingMode::physical_sides_to_corner(
+            self.block_start_physical_side(),
+            self.inline_end_physical_side())
+    }
+
+    #[inline]
+    pub fn end_start_physical_corner(&self) -> PhysicalCorner {
+        WritingMode::physical_sides_to_corner(
+            self.block_end_physical_side(),
+            self.inline_start_physical_side())
+    }
+
+    #[inline]
+    pub fn end_end_physical_corner(&self) -> PhysicalCorner {
+        WritingMode::physical_sides_to_corner(
+            self.block_end_physical_side(),
+            self.inline_end_physical_side())
+    }
+
+    #[inline]
     pub fn block_flow_direction(&self) -> BlockFlowDirection {
         match (self.is_vertical(), self.is_vertical_lr()) {
             (false, _) => BlockFlowDirection::TopToBottom,
             (true, true) => BlockFlowDirection::LeftToRight,
             (true, false) => BlockFlowDirection::RightToLeft,
         }
     }
 
@@ -1309,8 +1352,16 @@ impl<T: Copy + Add<T, Output = T> + Sub<
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum PhysicalSide {
     Top,
     Right,
     Bottom,
     Left,
 }
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum PhysicalCorner {
+    TopLeft,
+    TopRight,
+    BottomRight,
+    BottomLeft,
+}
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -3,20 +3,23 @@
 # file, You can obtain one at https://mozilla.org/MPL/2.0/.
 
 import re
 
 PHYSICAL_SIDES = ["top", "right", "bottom", "left"]
 LOGICAL_SIDES = ["block-start", "block-end", "inline-start", "inline-end"]
 PHYSICAL_SIZES = ["width", "height"]
 LOGICAL_SIZES = ["block-size", "inline-size"]
+PHYSICAL_CORNERS = ["top-left", "top-right", "bottom-right", "bottom-left"]
+LOGICAL_CORNERS = ["start-start", "start-end", "end-start", "end-end"]
 
 # bool is True when logical
 ALL_SIDES = [(side, False) for side in PHYSICAL_SIDES] + [(side, True) for side in LOGICAL_SIDES]
 ALL_SIZES = [(size, False) for size in PHYSICAL_SIZES] + [(size, True) for size in LOGICAL_SIZES]
+ALL_CORNERS = [(corner, False) for corner in PHYSICAL_CORNERS] + [(corner, True) for corner in LOGICAL_CORNERS]
 
 SYSTEM_FONT_LONGHANDS = """font_family font_size font_style
                            font_variant_caps font_stretch font_kerning
                            font_variant_position font_weight
                            font_size_adjust font_variant_alternates
                            font_variant_ligatures font_variant_east_asian
                            font_variant_numeric font_language_override
                            font_feature_settings font_variation_settings
@@ -234,22 +237,24 @@ class Longhand(object):
     def type():
         return "longhand"
 
     # For a given logical property return all the physical
     # property names corresponding to it.
     def all_physical_mapped_properties(self):
         assert self.logical
         logical_side = None
-        for s in LOGICAL_SIDES + LOGICAL_SIZES:
+        for s in LOGICAL_SIDES + LOGICAL_SIZES + LOGICAL_CORNERS:
             if s in self.name:
                 assert not logical_side
                 logical_side = s
         assert logical_side
-        physical = PHYSICAL_SIDES if logical_side in LOGICAL_SIDES else PHYSICAL_SIZES
+        physical = PHYSICAL_SIDES if logical_side in LOGICAL_SIDES else \
+                   PHYSICAL_SIZES if logical_side in LOGICAL_SIZES else \
+                   LOGICAL_CORNERS
         return [self.name.replace(logical_side, physical_side).replace("inset-", "")
                 for physical_side in physical]
 
     def experimental(self, product):
         if product == "gecko":
             return bool(self.gecko_pref)
         return bool(self.servo_pref)
 
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -1,15 +1,15 @@
 /* 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 https://mozilla.org/MPL/2.0/. */
 
 <%!
     from data import Keyword, to_rust_ident, to_camel_case
-    from data import LOGICAL_SIDES, PHYSICAL_SIDES, LOGICAL_SIZES, SYSTEM_FONT_LONGHANDS
+    from data import LOGICAL_CORNERS, PHYSICAL_CORNERS, LOGICAL_SIDES, PHYSICAL_SIDES, LOGICAL_SIZES, SYSTEM_FONT_LONGHANDS
 %>
 
 <%def name="predefined_type(name, type, initial_value, parse_method='parse',
             needs_context=True, vector=False,
             computed_type=None, initial_specified_value=None,
             allow_quirks=False, allow_empty=False, **kwargs)">
     <%def name="predefined_type_inner(name, type, initial_value, parse_method)">
         #[allow(unused_imports)]
@@ -837,34 +837,47 @@
         }
     </%call>
 </%def>
 
 <%def name="logical_setter_helper(name)">
     <%
         side = None
         size = None
+        corner = None
         maybe_side = [s for s in LOGICAL_SIDES if s in name]
         maybe_size = [s for s in LOGICAL_SIZES if s in name]
+        maybe_corner = [s for s in LOGICAL_CORNERS if s in name]
         if len(maybe_side) == 1:
             side = maybe_side[0]
         elif len(maybe_size) == 1:
             size = maybe_size[0]
+        elif len(maybe_corner) == 1:
+            corner = maybe_corner[0]
         def phys_ident(side, phy_side):
             return to_rust_ident(name.replace(side, phy_side).replace("inset-", ""))
     %>
     % if side is not None:
         use crate::logical_geometry::PhysicalSide;
         match wm.${to_rust_ident(side)}_physical_side() {
             % for phy_side in PHYSICAL_SIDES:
                 PhysicalSide::${phy_side.title()} => {
                     ${caller.inner(physical_ident=phys_ident(side, phy_side))}
                 }
             % endfor
         }
+    % elif corner is not None:
+        use crate::logical_geometry::PhysicalCorner;
+        match wm.${to_rust_ident(corner)}_physical_corner() {
+            % for phy_corner in PHYSICAL_CORNERS:
+                PhysicalCorner::${to_camel_case(phy_corner)} => {
+                    ${caller.inner(physical_ident=phys_ident(corner, phy_corner))}
+                }
+            % endfor
+        }
     % elif size is not None:
         <%
             # (horizontal, vertical)
             physical_size = ("height", "width")
             if size == "inline-size":
                 physical_size = ("width", "height")
         %>
         if wm.is_vertical() {
--- a/servo/components/style/properties/longhands/border.mako.rs
+++ b/servo/components/style/properties/longhands/border.mako.rs
@@ -1,14 +1,14 @@
 /* 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 https://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
-<% from data import Keyword, Method, PHYSICAL_SIDES, ALL_SIDES, maybe_moz_logical_alias %>
+<% from data import Keyword, Method, ALL_CORNERS, PHYSICAL_SIDES, ALL_SIDES, maybe_moz_logical_alias %>
 
 <% data.new_style_struct("Border", inherited=False,
                    additional_methods=[Method("border_" + side + "_has_nonzero_width",
                                               "bool") for side in ["top", "right", "bottom", "left"]]) %>
 <%
     def maybe_logical_spec(side, kind):
         if side[1]: # if it is logical
             return "https://drafts.csswg.org/css-logical-props/#propdef-border-%s-%s" % (side[0], kind)
@@ -65,27 +65,37 @@
     Keyword('border-style',
     "none solid double dotted dashed hidden groove ridge inset outset",
     gecko_enum_prefix="StyleBorderStyle",
     gecko_inexhaustive=True),
     type="crate::values::specified::BorderStyle",
 )}
 
 // FIXME(#4126): when gfx supports painting it, make this Size2D<LengthPercentage>
-% for corner in ["top-left", "top-right", "bottom-right", "bottom-left"]:
+% for corner in ALL_CORNERS:
+    <%
+        corner_name = corner[0]
+        is_logical = corner[1]
+        if is_logical:
+            prefixes = None
+        else:
+            prefixes = "webkit"
+    %>
     ${helpers.predefined_type(
-        "border-" + corner + "-radius",
+        "border-%s-radius" % corner_name,
         "BorderCornerRadius",
         "computed::BorderCornerRadius::zero()",
         "parse",
-        extra_prefixes="webkit",
-        spec="https://drafts.csswg.org/css-backgrounds/#border-%s-radius" % corner,
+        extra_prefixes=prefixes,
+        spec=maybe_logical_spec(corner, "radius"),
         boxed=True,
         flags="APPLIES_TO_FIRST_LETTER",
         animation_value_type="BorderCornerRadius",
+        logical_group="border-radius",
+        logical=is_logical,
     )}
 % endfor
 
 ${helpers.single_keyword(
     "box-decoration-break",
     "slice clone",
     gecko_enum_prefix="StyleBoxDecorationBreak",
     spec="https://drafts.csswg.org/css-break/#propdef-box-decoration-break",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-logical/logical-box-border-radius.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>CSS Logical Properties: flow-relative border-radius</title>
+<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com" />
+<link rel="help" href="https://drafts.csswg.org/css-logical-1/#border-radius-properties">
+<meta name="assert" content="This test checks the interaction of the flow-relative border-*-radius properties with the physical ones in different writing modes." />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {runTests, createCornerPropertyGroup} from "./resources/test-box-properties.js";
+runTests(createCornerPropertyGroup("border-*-radius", {
+  type: "length",
+  prerequisites: {"border-style": "solid"},
+}));
+</script>
--- a/testing/web-platform/tests/css/css-logical/resources/test-box-properties.js
+++ b/testing/web-platform/tests/css/css-logical/resources/test-box-properties.js
@@ -62,16 +62,51 @@ export function createBoxPropertyGroup(p
   }
   shorthands[property.replace("-*", "")] =
     ["top", "right", "bottom", "left"].map(physicalSide => physical[physicalSide]);
   const type = [].concat(descriptor.type);
   return {logical, physical, shorthands, type, prerequisites, property};
 }
 
 /**
+ * Creates a group physical and logical box-corner properties.
+ *
+ * @param {string} property
+ *        A string representing the property names, like "border-*-radius".
+ * @param {Object} descriptor
+ * @param {string|string[]} descriptor.type
+ *        Describes the kind of values accepted by the property, like "length".
+ *        Must be a key or a collection of keys from the `testValues` object.
+ * @param {Object={}} descriptor.prerequisites
+ *        Represents property declarations that are needed by `property` to work.
+ *        For example, border-width properties require a border style.
+ */
+export function createCornerPropertyGroup(property, descriptor) {
+  const logical = {};
+  const physical = {};
+  const shorthands = {};
+  for (const logicalCorner of ["start-start", "start-end", "end-start", "end-end"]) {
+    const prop = property.replace("*", logicalCorner);
+    const [block_side, inline_side] = logicalCorner.split("-");
+    const b = "block" + block_side.charAt(0).toUpperCase() + block_side.slice(1);
+    const i = "inline" + inline_side.charAt(0).toUpperCase() + inline_side.slice(1);
+    const index = b + "-" + i; // e.g. "blockStart-inlineEnd"
+    logical[index] = prop;
+  }
+  let prerequisites = "";
+  for (const physicalCorner of ["top-left", "top-right", "bottom-left", "bottom-right"]) {
+    const prop = property.replace("*", physicalCorner);
+    physical[physicalCorner] = prop;
+    prerequisites += makeDeclaration(descriptor.prerequisites, physicalCorner);
+  }
+  const type = [].concat(descriptor.type);
+  return {logical, physical, shorthands, type, prerequisites, property};
+}
+
+/**
  * Creates a group of physical and logical sizing properties.
  *
  * @param {string} prefix
  *        One of "", "max-" or "min-".
  */
 export function createSizingPropertyGroup(prefix) {
   return {
     logical: {
@@ -96,16 +131,17 @@ export function createSizingPropertyGrou
  */
 export function runTests(group) {
   const values = testValues[group.type[0]].map(function(_, i) {
     return group.type.map(type => testValues[type][i]).join(" ");
   });
   const logicals = Object.values(group.logical);
   const physicals = Object.values(group.physical);
   const shorthands = group.shorthands ? Object.entries(group.shorthands) : null;
+  const is_corner = group.property == "border-*-radius";
 
   test(function() {
     const expected = [];
     for (const [i, logicalProp] of logicals.entries()) {
       testElement.style.setProperty(logicalProp, values[i]);
       expected.push([logicalProp, values[i]]);
     }
     testCSSValues("logical properties in inline style", testElement.style, expected);
@@ -136,17 +172,32 @@ export function runTests(group) {
   }
 
   for (const writingMode of writingModes) {
     for (const style of writingMode.styles) {
       const writingModeDecl = makeDeclaration(style);
 
       const associated = {};
       for (const [logicalSide, logicalProp] of Object.entries(group.logical)) {
-        const physicalProp = group.physical[writingMode[logicalSide]];
+        let physicalProp;
+        if (is_corner) {
+          const [ block_side, inline_side] = logicalSide.split("-");
+          const physicalSide1 = writingMode[block_side];
+          const physicalSide2 = writingMode[inline_side];
+          let physicalCorner;
+          // mirror "left-top" to "top-left" etc
+          if (["top", "bottom"].includes(physicalSide1)) {
+            physicalCorner = physicalSide1 + "-" + physicalSide2;
+          } else {
+            physicalCorner = physicalSide2 + "-" + physicalSide1;
+          }
+          physicalProp = group.physical[physicalCorner];
+        } else {
+          physicalProp = group.physical[writingMode[logicalSide]];
+        }
         associated[logicalProp] = physicalProp;
         associated[physicalProp] = logicalProp;
       }
 
       // Test that logical properties are converted to their physical
       // equivalent correctly when all in the group are present on a single
       // declaration, with no overwriting of previous properties and
       // no physical properties present.  We put the writing mode properties
--- a/toolkit/components/extensions/parent/ext-downloads.js
+++ b/toolkit/components/extensions/parent/ext-downloads.js
@@ -261,18 +261,18 @@ const downloadQuery = query => {
     return ExtensionCommon.normalizeTime(arg).getTime();
   }
 
   const startedBefore = normalizeDownloadTime(query.startedBefore, true);
   const startedAfter = normalizeDownloadTime(query.startedAfter, false);
   // const endedBefore = normalizeDownloadTime(query.endedBefore, true);
   // const endedAfter = normalizeDownloadTime(query.endedAfter, false);
 
-  const totalBytesGreater = query.totalBytesGreater;
-  const totalBytesLess = query.totalBytesLess != null ? query.totalBytesLess : Number.MAX_VALUE;
+  const totalBytesGreater = query.totalBytesGreater !== null ? query.totalBytesGreater : -1;
+  const totalBytesLess = query.totalBytesLess !== null ? query.totalBytesLess : Number.MAX_VALUE;
 
   // Handle options for which we can have a regular expression and/or
   // an explicit value to match.
   function makeMatch(regex, value, field) {
     if (value == null && regex == null) {
       return input => true;
     }
 
@@ -318,17 +318,17 @@ const downloadQuery = query => {
       }
     } else if (item.startTime > startedBefore || item.startTime < startedAfter) {
       return false;
     }
 
     // todo endedBefore, endedAfter
 
     if (item.totalBytes == -1) {
-      if (query.totalBytesGreater != null || query.totalBytesLess != null) {
+      if (query.totalBytesGreater !== null || query.totalBytesLess !== null) {
         return false;
       }
     } else if (item.totalBytes <= totalBytesGreater || item.totalBytes >= totalBytesLess) {
       return false;
     }
 
     // todo: include danger
     const SIMPLE_ITEMS = ["id", "mime", "startTime", "endTime", "state",
--- a/toolkit/components/extensions/schemas/downloads.json
+++ b/toolkit/components/extensions/schemas/downloads.json
@@ -107,17 +107,18 @@
             "type": "boolean"
           },
           "danger": {
             "$ref": "DangerType",
             "description": "Indication of whether this download is thought to be safe or known to be suspicious."
           },
           "mime": {
             "description": "The file's MIME type.",
-            "type": "string"
+            "type": "string",
+            "optional": true
           },
           "startTime": {
             "description": "Number of milliseconds between the unix epoch and when this download began.",
             "type": "string"
           },
           "endTime": {
             "description": "Number of milliseconds between the unix epoch and when this download ended.",
             "optional": true,
@@ -252,18 +253,17 @@
           "endedAfter": {
             "description": "Limits results to downloads that ended after the given ms since the epoch.",
             "optional": true,
             "$ref": "DownloadTime"
           },
           "totalBytesGreater": {
             "description": "Limits results to downloads whose totalBytes is greater than the given integer.",
             "optional": true,
-            "type": "number",
-            "default": -1
+            "type": "number"
           },
           "totalBytesLess": {
             "description": "Limits results to downloads whose totalBytes is less than the given integer.",
             "optional": true,
             "type": "number"
           },
           "filenameRegex": {
             "description": "Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose <code>filename</code> matches the given regular expression.",
--- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
@@ -1,12 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/);
+
 ChromeUtils.import("resource://gre/modules/Downloads.jsm");
 
 const server = createHttpServer();
 server.registerDirectory("/data/", do_get_file("data"));
 
 const BASE = `http://localhost:${server.identity.primaryPort}/data`;
 const TXT_FILE = "file_download.txt";
 const TXT_URL = BASE + "/" + TXT_FILE;
@@ -419,8 +421,54 @@ add_task(async function test_search() {
   await checkBadSearch({endedAfter: "i am not a time"}, /Type error/, "query.endedAfter is not a valid time");
   await checkBadSearch({urlRegex: "["}, /Invalid urlRegex/, "query.urlRegexp is not a valid regular expression");
   await checkBadSearch({filenameRegex: "["}, /Invalid filenameRegex/, "query.filenameRegexp is not a valid regular expression");
   await checkBadSearch({orderBy: "startTime"}, /Expected array/, "query.orderBy is not an array");
   await checkBadSearch({orderBy: ["bogus"]}, /Invalid orderBy field/, "query.orderBy references a non-existent field");
 
   await extension.unload();
 });
+
+// Test that downloads with totalBytes of -1 (ie, that have not yet started)
+// work properly.  See bug 1519762 for details of a past regression in
+// this area.
+add_task(async function test_inprogress() {
+  let resume, resumePromise = new Promise(resolve => { resume = resolve; });
+  server.registerPathHandler("/slow", async (request, response) => {
+    response.processAsync();
+    await resumePromise;
+    response.setHeader("Content-type", "text/plain");
+    response.write("");
+    response.finish();
+  });
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["downloads"],
+    },
+    background() {
+      browser.test.onMessage.addListener(async (msg, url) => {
+        let id = await browser.downloads.download({url});
+        let full = await browser.downloads.search({id});
+
+        browser.test.assertEq(full.length, 1,
+                              "Found new download in search results");
+        browser.test.assertEq(full[0].totalBytes, -1,
+                              "New download still has totalBytes == -1");
+
+        browser.downloads.onChanged.addListener(info => {
+          if (info.id == id && info.state.current == "complete") {
+            browser.test.notifyPass("done");
+          }
+        });
+
+        browser.test.sendMessage("started");
+      });
+    },
+  });
+
+  await extension.startup();
+  extension.sendMessage("go", `${BASE}/slow`);
+  await extension.awaitMessage("started");
+  resume();
+  await extension.awaitFinish("done");
+  await extension.unload();
+});
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -795,22 +795,22 @@ void CycleCollectedJSRuntime::TraverseNa
   MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
 
 #ifdef MOZ_GECKO_PROFILER
   if (profiler_thread_is_being_profiled()) {
     if (aProgress == JS::GC_CYCLE_END) {
       profiler_add_marker(
           "GCMajor", MakeUnique<GCMajorMarkerPayload>(
                          aDesc.startTime(aContext), aDesc.endTime(aContext),
-                         aDesc.summaryToJSON(aContext)));
+                         aDesc.formatJSONProfiler(aContext)));
     } else if (aProgress == JS::GC_SLICE_END) {
       profiler_add_marker("GCSlice", MakeUnique<GCSliceMarkerPayload>(
                                          aDesc.lastSliceStart(aContext),
                                          aDesc.lastSliceEnd(aContext),
-                                         aDesc.sliceToJSON(aContext)));
+                                         aDesc.sliceToJSONProfiler(aContext)));
     }
   }
 #endif
 
   if (aProgress == JS::GC_CYCLE_END &&
       JS::dbg::FireOnGarbageCollectionHookRequired(aContext)) {
     JS::gcreason::Reason reason = aDesc.reason_;
     Unused << NS_WARN_IF(