Merge mozilla-central to autoland. a=merge CLOSED TREE
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Fri, 18 Jan 2019 11:46:50 +0200
changeset 454416 15a126eab0979e764c96c01c9083207b2c0aa9a0
parent 454415 cfc581f4768c3a9424902541747e805749565e13 (current diff)
parent 454414 3aa256c255f664c500714c34a3b9e353e545d196 (diff)
child 454417 3b442a1b227d70bb7c3d28b763588a8962e52ea8
push id35397
push useropoprus@mozilla.com
push dateSat, 19 Jan 2019 03:35:41 +0000
treeherdermozilla-central@57dc8bbbc38f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge CLOSED TREE
--- 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/toolkit/components/sessionstore/SessionStoreUtils.cpp
+++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp
@@ -2,16 +2,17 @@
  * 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 "js/JSON.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/HTMLSelectElement.h"
 #include "mozilla/dom/HTMLTextAreaElement.h"
 #include "mozilla/dom/SessionStoreUtils.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsContentList.h"
 #include "nsContentUtils.h"
 #include "nsFocusManager.h"
 #include "nsGlobalWindowOuter.h"
 #include "nsIDocShell.h"
 #include "nsIFormControl.h"
 #include "nsIScrollableFrame.h"
--- 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(