diff --git a/devtools/server/actors/animation-type-longhand.js b/devtools/server/actors/animation-type-longhand.js index 32ec268bae6f..3e170445249c 100644 --- a/devtools/server/actors/animation-type-longhand.js +++ b/devtools/server/actors/animation-type-longhand.js @@ -283,6 +283,10 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [ "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", diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index c63167b6ce80..2c99513199b9 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -2857,6 +2857,10 @@ exports.CSS_PROPERTIES = { "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", @@ -4128,6 +4132,30 @@ exports.CSS_PROPERTIES = { "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": [ @@ -4757,6 +4785,30 @@ exports.CSS_PROPERTIES = { "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": [ diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index bdd944f53377..18fa634dc597 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -2174,7 +2174,7 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, 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 notify = new NotifyGCEndRunnable(std::move(json)); SystemGroup::Dispatch(TaskCategory::GarbageCollection, diff --git a/gfx/skia/skia/src/ports/SkFontHost_mac.cpp b/gfx/skia/skia/src/ports/SkFontHost_mac.cpp index 7814d2cff09d..df7866d9bc37 100644 --- a/gfx/skia/skia/src/ports/SkFontHost_mac.cpp +++ b/gfx/skia/skia/src/ports/SkFontHost_mac.cpp @@ -1447,7 +1447,7 @@ void SkScalerContext_Mac::generateImage(const SkGlyph& 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(); diff --git a/js/public/GCAPI.h b/js/public/GCAPI.h index 4abbc8b5f08f..41c94e3f31d4 100644 --- a/js/public/GCAPI.h +++ b/js/public/GCAPI.h @@ -634,15 +634,16 @@ struct JS_PUBLIC_API GCDescription { 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; }; diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 488d717ef606..bab1e61c3e6a 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -418,7 +418,7 @@ static bool GC(JSContext* cx, unsigned argc, Value* vp) { } #ifndef JS_MORE_DETERMINISTIC - size_t preBytes = cx->runtime()->gc.usage.gcBytes(); + size_t preBytes = cx->runtime()->gc.heapSize.gcBytes(); #endif if (zone) { @@ -433,7 +433,7 @@ static bool GC(JSContext* cx, unsigned argc, Value* vp) { 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); } diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index d865b9c50a29..90873674c20e 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -37,8 +37,10 @@ #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" @@ -3030,24 +3032,30 @@ bool BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, 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().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().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; @@ -7498,33 +7506,15 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::emitIncOrDec(UnaryNode* 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)) { + const LabeledStatement* labeledStmt) { + LabelEmitter label(this); + if (!label.emitLabel(labeledStmt->label())) { return false; } - - JumpList top; - if (!emitJump(JSOP_LABEL, &top)) { + if (!emitTree(labeledStmt->statement())) { 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)) { + if (!label.emitEnd()) { return false; } @@ -7567,27 +7557,31 @@ bool BytecodeEmitter::emitConditionalExpression( 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()) { // 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 (!emitTree(propdef->as().kid())) { + if (!pe.prepareForProtoValue(Some(propdef->pn_pos.begin))) { + // [stack] OBJ return false; } - objp.set(nullptr); - if (!emit1(JSOP_MUTATEPROTO)) { + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ PROTO + return false; + } + if (!pe.emitMutateProto()) { + // [stack] OBJ return false; } continue; @@ -7595,193 +7589,216 @@ bool BytecodeEmitter::emitPropertyList(ListNode* obj, if (propdef->isKind(ParseNodeKind::Spread)) { MOZ_ASSERT(type == ObjectLiteral); - - if (!emit1(JSOP_DUP)) { + // [stack] OBJ + if (!pe.prepareForSpreadOperand(Some(propdef->pn_pos.begin))) { + // [stack] OBJ OBJ return false; } - if (!emitTree(propdef->as().kid())) { + // [stack] OBJ OBJ VAL return false; } - - if (!emitCopyDataProperties(CopyOption::Unfiltered)) { + if (!pe.emitSpread()) { + // [stack] OBJ return false; } - - objp.set(nullptr); continue; } - bool extraPop = false; - if (type == ClassBody && propdef->as().isStatic()) { - extraPop = true; - if (!emit1(JSOP_DUP2)) { + BinaryNode* prop = &propdef->as(); + + 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 (!emit1(JSOP_POP)) { + + if (propVal->isKind(ParseNodeKind::Function) && + propVal->as().funbox()->needsHomeObject()) { + FunctionBox* funbox = propVal->as().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().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().value())) { + // [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; } - /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ - ParseNode* key = propdef->as().left(); - bool isIndex = false; - if (key->isKind(ParseNodeKind::NumberExpr)) { - if (!emitNumberOp(key->as().value())) { - return false; - } - isIndex = true; - } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || - key->isKind(ParseNodeKind::StringExpr)) { - // EmitClass took care of constructor already. + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + // [stack] CTOR? OBJ + + // emitClass took care of constructor already. if (type == ClassBody && key->as().atom() == cx->names().constructor && !propdef->as().isStatic()) { continue; } - } else { - MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); - if (!emitComputedPropertyName(&key->as())) { + + if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? return false; } - isIndex = true; + 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().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)); + } + } + + RootedAtom keyAtom(cx, key->as().atom()); + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitProp(keyAtom, isPropertyAnonFunctionOrClass, + anonFunction)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitGetter(keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitSetter(keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); + } + + continue; } - /* Emit code for the property initializer. */ - ParseNode* propVal = propdef->as().right(); - if (!emitTree(propVal)) { + 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().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; } - 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().funbox()->needsHomeObject()) { - FunctionBox* funbox = propVal->as().funbox(); - MOZ_ASSERT(funbox->function()->allowSuperProperty()); - bool isAsync = funbox->isAsync(); - if (isAsync) { - if (!emit1(JSOP_SWAP)) { + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitComputedProp(isPropertyAnonFunctionOrClass)) { + // [stack] CTOR? OBJ return false; } - } - if (!emitDupAt(1 + isIndex + isAsync)) { - return false; - } - if (!emit1(JSOP_INITHOMEOBJECT)) { - return false; - } - if (isAsync) { - if (!emit1(JSOP_POP)) { + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(isPropertyAnonFunctionOrClass); + if (!pe.emitInitComputedGetter()) { + // [stack] CTOR? OBJ return false; } - } - } - - // Class methods are not enumerable. - if (type == ClassBody) { - switch (op) { - case JSOP_INITPROP: - op = JSOP_INITHIDDENPROP; - break; - case JSOP_INITPROP_GETTER: - op = JSOP_INITHIDDENPROP_GETTER; - break; - case JSOP_INITPROP_SETTER: - op = JSOP_INITHIDDENPROP_SETTER; - 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)) { + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(isPropertyAnonFunctionOrClass); + if (!pe.emitInitComputedSetter()) { + // [stack] CTOR? OBJ return false; } - if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) { - 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().atom(), &index)) { - return false; - } - - if (objp) { - MOZ_ASSERT(type == ObjectLiteral); - MOZ_ASSERT(!IsHiddenInitOp(op)); - MOZ_ASSERT(!objp->inDictionaryMode()); - Rooted id(cx, AtomToId(key->as().atom())); - if (!NativeDefineDataProperty(cx, objp, id, UndefinedHandleValue, - JSPROP_ENUMERATE)) { - return false; - } - if (objp->inDictionaryMode()) { - objp.set(nullptr); - } - } - - if (propVal->isDirectRHSAnonFunction()) { - MOZ_ASSERT(prefixKind == FunctionPrefixKind::None); - - RootedAtom keyName(cx, key->as().atom()); - if (!setOrEmitSetFunName(propVal, keyName)) { - return false; - } - } - if (!emitIndex32(op, index)) { - return false; - } - } - - if (extraPop) { - if (!emit1(JSOP_POP)) { - return false; - } + break; + default: + MOZ_CRASH("Invalid op"); } } return true; @@ -7795,43 +7812,22 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ListNode* objNode) { 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()) { + // [stack] + + ObjectEmitter oe(this); + if (!oe.emitObject(objNode->count())) { + // [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(objNode->count()); - RootedPlainObject obj( - cx, NewBuiltinClassInstance(cx, kind, TenuredObject)); - if (!obj) { + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ return false; } - - if (!emitPropertyList(objNode, &obj, ObjectLiteral)) { + if (!oe.emitEnd()) { + // [stack] OBJ return false; } - if (obj) { - // The object survived and has a predictable shape: update the original - // bytecode. - if (!replaceNewInitWithNewObject(obj, offset)) { - return false; - } - } - return true; } @@ -8407,7 +8403,11 @@ bool BytecodeEmitter::emitFunctionBody(ParseNode* funBody) { } 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; } @@ -8424,272 +8424,108 @@ bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) { 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()) { // TODO(khyperia): Implement private field access. - return false; + continue; } + ClassMethod& method = mn->as(); ParseNode& methodName = method.name(); if (!method.isStatic() && (methodName.isKind(ParseNodeKind::ObjectPropertyName) || methodName.isKind(ParseNodeKind::StringExpr)) && methodName.as().atom() == cx->names().constructor) { - constructor = &method.method(); - break; + return &method.method(); } } - bool savedStrictness = sc->setLocalStrictMode(true); + return nullptr; +} - Maybe tdzCache; - Maybe emitterScope; +// 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())) { + 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; } } - // Pseudocode for class declarations: - // - // class extends BaseExpression { - // constructor() { ... } - // ... - // } - // - // - // if defined { - // 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 { - // if defined { - // cons = DefineMethod(, proto=homeObject, - // funProto=funProto); - // } else { - // cons = DefineMethod(, proto=homeObject); - // } - // } else { - // if defined { - // cons = DefaultDerivedConstructor(proto=homeObject, - // funProto=funProto); - // } else { - // cons = DefaultConstructor(proto=homeObject); - // } - // } - // - // cons.prototype = homeObject; - // homeObject.constructor = cons; - // - // EmitPropertyList(...) - // 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 + // [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 + 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 + bool needsHomeObject = constructor->funbox()->needsHomeObject(); + // HERITAGE is consumed inside emitFunction. + if (!emitFunction(constructor, isDerived)) { + // [stack] HOMEOBJ CTOR return false; } - if (constructor->funbox()->needsHomeObject()) { - if (!emitDupAt(1)) { - // [stack] ... HOMEOBJ CONSTRUCTOR HOMEOBJ - return false; - } - if (!emit1(JSOP_INITHOMEOBJECT)) { - // [stack] ... HOMEOBJ CONSTRUCTOR - 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)) { + if (!ce.emitInitDefaultConstructor(Some(classNode->pn_pos.begin), + Some(classNode->pn_pos.end))) { + // [stack] CTOR HOMEOBJ return false; } - - JSAtom* name = names ? names->innerBinding()->as().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 + if (!emitPropertyList(classMembers, ce, ClassBody)) { + // [stack] CTOR HOMEOBJ return false; } - - if (!emit1(JSOP_DUP2)) { - // [stack] ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR HOMEOBJ + if (!ce.emitEnd(kind)) { + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR 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)); return true; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 16656b67b259..d996b3da62c1 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -112,6 +112,7 @@ class CallOrNewEmitter; class ElemOpEmitter; class EmitterScope; class NestableControl; +class PropertyEmitter; class TDZCheckCache; struct MOZ_STACK_CLASS BytecodeEmitter { @@ -604,8 +605,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter { 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 @@ -687,7 +687,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter { 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( @@ -783,6 +783,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter { 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); @@ -851,6 +853,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter { 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. // diff --git a/js/src/frontend/LabelEmitter.cpp b/js/src/frontend/LabelEmitter.cpp new file mode 100644 index 000000000000..d39b99633603 --- /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; +} diff --git a/js/src/frontend/LabelEmitter.h b/js/src/frontend/LabelEmitter.h new file mode 100644 index 000000000000..d644d6b05e39 --- /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 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 */ diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp new file mode 100644 index 000000000000..55f9c42281c4 --- /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& 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& 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& 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& 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& 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& 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 key, bool isPropertyAnonFunctionOrClass /* = false */, + JS::Handle anonFunction /* = nullptr */) { + return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key, + isPropertyAnonFunctionOrClass, anonFunction); +} + +bool PropertyEmitter::emitInitGetter(JS::Handle key) { + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER, + key, false, nullptr); +} + +bool PropertyEmitter::emitInitSetter(JS::Handle 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 key, + bool isPropertyAnonFunctionOrClass, + JS::Handle 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 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(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 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 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 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 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& classStart, + const Maybe& 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; +} diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h new file mode 100644 index 000000000000..73d3c67ab8cc --- /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 // size_t, ptrdiff_t +#include // 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 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& keyPos); + MOZ_MUST_USE bool emitMutateProto(); + + // { ...obj } + // ^ + // | + // spreadPos + MOZ_MUST_USE bool prepareForSpreadOperand( + const mozilla::Maybe& spreadPos); + MOZ_MUST_USE bool emitSpread(); + + // { key: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForPropValue(const mozilla::Maybe& keyPos, + Kind kind = Kind::Prototype); + + // { 1: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForIndexPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForIndexPropValue(); + + // { [ key ]: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForComputedPropKey( + const mozilla::Maybe& 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 key, bool isPropertyAnonFunctionOrClass = false, + JS::Handle anonFunction = nullptr); + MOZ_MUST_USE bool emitInitGetter(JS::Handle key); + MOZ_MUST_USE bool emitInitSetter(JS::Handle 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& 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 key, + bool isPropertyAnonFunctionOrClass, + JS::Handle 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 { + // 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 { + // if defined { + // cons = DefineMethod(, proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefineMethod(, proto=homeObject); + // } + // } else { + // if defined { + // cons = DefaultDerivedConstructor(proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefaultConstructor(proto=homeObject); + // } + // } + // + // cons.prototype = homeObject; + // homeObject.constructor = cons; + // + // EmitPropertyList(...) + + bool isDerived_ = false; + + mozilla::Maybe tdzCacheForInnerName_; + mozilla::Maybe 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 name_; + + public: + explicit ClassEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitScopeForNamedClass( + JS::Handle scopeBindings); + + // @param name + // Name of the class (nullptr if this is anonymous class) + MOZ_MUST_USE bool emitClass(JS::Handle name); + MOZ_MUST_USE bool emitDerivedClass(JS::Handle 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& classStart, + const mozilla::Maybe& classEnd); + + MOZ_MUST_USE bool emitEnd(Kind kind); + + private: + void setName(JS::Handle name); + MOZ_MUST_USE bool initProtoAndCtor(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ObjectEmitter_h */ diff --git a/js/src/frontend/moz.build b/js/src/frontend/moz.build index c55a021d4371..779c5b3e9577 100644 --- a/js/src/frontend/moz.build +++ b/js/src/frontend/moz.build @@ -41,8 +41,10 @@ UNIFIED_SOURCES += [ 'ForOfLoopControl.cpp', 'IfEmitter.cpp', 'JumpList.cpp', + 'LabelEmitter.cpp', 'NameFunctions.cpp', 'NameOpEmitter.cpp', + 'ObjectEmitter.cpp', 'ParseContext.cpp', 'ParseNode.cpp', 'PropOpEmitter.cpp', diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index bec15c42fbfd..0d846da72921 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -349,7 +349,7 @@ bool GCRuntime::gcIfNeededAtAllocation(JSContext* cx) { // 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); } @@ -567,11 +567,11 @@ Arena* GCRuntime::allocateArena(Chunk* chunk, Zone* zone, AllocKind thingKind, // 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) { diff --git a/js/src/gc/GC.cpp b/js/src/gc/GC.cpp index 442478a82bfc..cdfbd6ce4447 100644 --- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -892,7 +892,7 @@ void Chunk::updateChunkListAfterFree(JSRuntime* rt, const AutoLockGC& lock) { } void GCRuntime::releaseArena(Arena* arena, const AutoLockGC& lock) { - arena->zone->usage.removeGCArena(); + arena->zone->zoneSize.removeGCArena(); arena->chunk()->releaseArena(rt, arena, lock); } @@ -902,7 +902,7 @@ GCRuntime::GCRuntime(JSRuntime* rt) atomsZone(nullptr), stats_(rt), marker(rt), - usage(nullptr), + heapSize(nullptr), rootsHash(256), nextCellUniqueId_(LargestTaggedNullCellPointer + 1), // Ensure disjoint from null tagged pointers. @@ -1379,7 +1379,7 @@ bool GCRuntime::setParameter(JSGCParamKey key, uint32_t value, 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); } } @@ -1607,7 +1607,7 @@ void GCRuntime::resetParameter(JSGCParamKey key, AutoLockGC& lock) { 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); } } @@ -1685,7 +1685,7 @@ uint32_t GCRuntime::getParameter(JSGCParamKey key, const AutoLockGC& lock) { 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: @@ -3280,7 +3280,7 @@ void GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, const AutoLockGC& lock) { 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) { @@ -3378,7 +3378,7 @@ void GCRuntime::maybeGC(Zone* zone) { 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); @@ -5782,7 +5782,7 @@ IncrementalProgress GCRuntime::endSweepingSweepGroup(FreeOp* fop, 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(); @@ -7258,7 +7258,7 @@ GCRuntime::IncrementalResult GCRuntime::budgetIncrementalGC( 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); @@ -7314,7 +7314,7 @@ class AutoScheduleZonesForGC { // 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(); } @@ -8136,7 +8136,7 @@ void GCRuntime::mergeRealms(Realm* source, Realm* target) { 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()); @@ -8592,9 +8592,10 @@ JS::dbg::GarbageCollectionEvent::Ptr JS::GCDescription::toGCEvent( 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(nchars + 1)); @@ -8623,14 +8624,15 @@ 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) { @@ -8721,7 +8723,7 @@ 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; } @@ -8770,7 +8772,7 @@ static bool MinorGCCountGetter(JSContext* cx, unsigned argc, Value* vp) { 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; } diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index adf61fdccdf4..8f96df8c871f 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -714,8 +714,8 @@ class GCRuntime { Vector unmarkGrayStack; - /* Track heap usage for this runtime. */ - HeapUsage usage; + /* Track heap size for this runtime. */ + HeapSize heapSize; /* GC scheduling state and parameters. */ GCSchedulingTunables tunables; diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 43ce7748da23..f641634888a0 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -842,12 +842,12 @@ static_assert( * 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 @@ -861,7 +861,7 @@ class HeapUsage { gcBytes_; public: - explicit HeapUsage(HeapUsage* parent) : parent_(parent), gcBytes_(0) {} + explicit HeapSize(HeapSize* parent) : parent_(parent), gcBytes_(0) {} size_t gcBytes() const { return gcBytes_; } @@ -880,7 +880,7 @@ class HeapUsage { } /* Pair to adoptArenas. Adopts the attendant usage statistics. */ - void adopt(HeapUsage& other) { + void adopt(HeapSize& other) { gcBytes_ += other.gcBytes_; other.gcBytes_ = 0; } diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index adb217fc9b61..3c020ebd1234 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -828,7 +828,7 @@ void js::Nursery::collect(JS::gcreason::Reason reason) { // 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 diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 7be1f1480010..6338bb90f314 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -327,7 +327,7 @@ UniqueChars Statistics::formatCompactSummaryMessage() const { 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))) { @@ -449,7 +449,7 @@ UniqueChars Statistics::formatDetailedDescription() const { 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, @@ -585,8 +585,7 @@ void Statistics::writeLogMessage(const char* fmt, ...) { } #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 @@ -607,9 +606,9 @@ UniqueChars Statistics::renderJsonMessage(uint64_t timestamp, 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); @@ -627,10 +626,11 @@ UniqueChars Statistics::renderJsonMessage(uint64_t timestamp, } 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: @@ -676,7 +676,11 @@ void Statistics::formatJsonDescription(uint64_t timestamp, 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 @@ -694,7 +698,7 @@ 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: @@ -742,7 +746,8 @@ Statistics::Statistics(JSRuntime* rt) gcDebugFile(nullptr), nonincrementalReason_(gc::AbortReason::None), allocsSinceMinorGC({0, 0}), - preBytes(0), + preHeapSize(0), + postHeapSize(0), thresholdTriggered(false), triggerAmount(0.0), triggerThreshold(0.0), @@ -971,7 +976,7 @@ void Statistics::beginGC(JSGCInvocationKind kind) { gckind = kind; nonincrementalReason_ = gc::AbortReason::None; - preBytes = runtime->gc.usage.gcBytes(); + preHeapSize = runtime->gc.heapSize.gcBytes(); startingMajorGCNumber = runtime->gc.majorGCCount(); startingSliceNumber = runtime->gc.gcNumber(); } @@ -979,6 +984,7 @@ void Statistics::beginGC(JSGCInvocationKind kind) { void Statistics::endGC() { TimeDuration sccTotal, sccLongest; sccDurations(&sccTotal, &sccLongest); + postHeapSize = runtime->gc.heapSize.gcBytes(); runtime->addTelemetry(JS_TELEMETRY_GC_IS_ZONE_GC, !zoneStats.isFullCollection()); diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 10cc68e0e0cb..4f48ac26caac 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -286,10 +286,14 @@ struct Statistics { // 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; @@ -356,8 +360,9 @@ struct Statistics { 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. */ @@ -439,7 +444,7 @@ struct Statistics { 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, diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index d5cf1f3595d2..9994aec574a2 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -52,7 +52,7 @@ JS::Zone::Zone(JSRuntime* rt) functionToStringCache_(this), keepAtomsCount(this, 0), purgeAtomsDeferred(this, 0), - usage(&rt->gc.usage), + zoneSize(&rt->gc.heapSize), threshold(), gcDelayBytes(0), tenuredStrings(this, 0), diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index eeaaaa4dfbb2..1a22ae99c9ec 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -544,8 +544,8 @@ class Zone : public JS::shadow::Zone, 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; diff --git a/js/src/jsapi-tests/testGCHooks.cpp b/js/src/jsapi-tests/testGCHooks.cpp index 9648e74bfdcb..93160f4d5561 100644 --- a/js/src/jsapi-tests/testGCHooks.cpp +++ b/js/src/jsapi-tests/testGCHooks.cpp @@ -24,7 +24,7 @@ static void NonIncrementalGCSliceCallback(JSContext* cx, if (progress == GC_CYCLE_END) { mozilla::UniquePtr summary(desc.formatSummaryMessage(cx)); mozilla::UniquePtr message(desc.formatSliceMessage(cx)); - mozilla::UniquePtr json(desc.formatJSON(cx, 0)); + mozilla::UniquePtr json(desc.formatJSONTelemetry(cx, 0)); } } diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 8aac0f009ddc..00c63c5667e6 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -1415,5 +1415,5 @@ JS_FRIEND_API JS::Value js::MaybeGetScriptPrivate(JSObject* object) { } JS_FRIEND_API uint64_t js::GetGCHeapUsageForObjectZone(JSObject* obj) { - return obj->zone()->usage.gcBytes(); + return obj->zone()->zoneSize.gcBytes(); } diff --git a/layout/generic/crashtests/1520798-2.html b/layout/generic/crashtests/1520798-2.html new file mode 100644 index 000000000000..83a27ab4ca7e --- /dev/null +++ b/layout/generic/crashtests/1520798-2.html @@ -0,0 +1,13 @@ + + + + + +
+
Hi
+
+ diff --git a/layout/generic/crashtests/crashtests.list b/layout/generic/crashtests/crashtests.list index 5c7879dd575e..b2964c16917f 100644 --- a/layout/generic/crashtests/crashtests.list +++ b/layout/generic/crashtests/crashtests.list @@ -724,3 +724,4 @@ 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 diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 8a9dc66c9454..2919b629cbfe 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -1592,6 +1592,94 @@ var gCSSProperties = { ], 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, diff --git a/layout/style/test/test_transitions_per_property.html b/layout/style/test/test_transitions_per_property.html index c6130fde880c..91d866b8a67e 100644 --- a/layout/style/test/test_transitions_per_property.html +++ b/layout/style/test/test_transitions_per_property.html @@ -64,6 +64,10 @@ var supported_properties = { "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 ], diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt index 71e4282a00dd..e2a748916274 100644 --- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -621,6 +621,7 @@ package org.mozilla.geckoview { 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; diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java index 950cfa0fc15b..0dc134ebd86b 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -3816,7 +3816,7 @@ public class GeckoSession implements Parcelable { @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; @@ -3844,6 +3844,10 @@ public class GeckoSession implements Parcelable { * 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. diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java index 9ab5bb4f1caa..527b44b5de89 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java @@ -14,6 +14,13 @@ import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate; 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(); @@ -36,6 +43,11 @@ import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate; 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); } @@ -57,6 +69,12 @@ import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate; 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; } } diff --git 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 index bbd5e20f8fce..007614408144 100644 --- 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 @@ -26,6 +26,8 @@ description: GeckoView API Changelog. - 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] @@ -113,4 +115,4 @@ description: GeckoView API Changelog. [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String- [65.25]: ../GeckoResult.html -[api-version]: e7a6a3ed65c75f7cb278b693adfa09cae5238ca2 +[api-version]: 45d1d8774e913a3077d7c489274184fd301f14fc diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 0daed929a730..d986fc07b5c7 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -5571,7 +5571,7 @@ 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); @@ -5640,7 +5640,7 @@ pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozil // 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. diff --git a/mozglue/misc/interceptor/PatcherDetour.h b/mozglue/misc/interceptor/PatcherDetour.h index 413b8a846691..4c3d5fe4eaf1 100644 --- a/mozglue/misc/interceptor/PatcherDetour.h +++ b/mozglue/misc/interceptor/PatcherDetour.h @@ -12,6 +12,7 @@ #include "mozilla/ScopeExit.h" #include "mozilla/TypedEnumBits.h" +#include "mozilla/Unused.h" #define COPY_CODES(NBYTES) \ do { \ @@ -137,6 +138,7 @@ class WindowsDllDetourPatcher final : public WindowsDllPatcherBase { continue; } #elif defined(_M_ARM64) + Unused << opcode1; MOZ_RELEASE_ASSERT(false, "Shouldn't get here"); #else #error "Unknown processor type" diff --git a/servo/components/style/logical_geometry.rs b/servo/components/style/logical_geometry.rs index b6cc9ef5bc67..63aa5f138a46 100644 --- a/servo/components/style/logical_geometry.rs +++ b/servo/components/style/logical_geometry.rs @@ -171,6 +171,49 @@ impl WritingMode { } } + #[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()) { @@ -1314,3 +1357,11 @@ pub enum PhysicalSide { Bottom, Left, } + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PhysicalCorner { + TopLeft, + TopRight, + BottomRight, + BottomLeft, +} diff --git a/servo/components/style/properties/data.py b/servo/components/style/properties/data.py index fa4f55bc9e57..39b5743f4a91 100644 --- a/servo/components/style/properties/data.py +++ b/servo/components/style/properties/data.py @@ -8,10 +8,13 @@ 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 @@ -239,12 +242,14 @@ class Longhand(object): 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] diff --git a/servo/components/style/properties/helpers.mako.rs b/servo/components/style/properties/helpers.mako.rs index b37842ddc574..2139ff2be712 100644 --- a/servo/components/style/properties/helpers.mako.rs +++ b/servo/components/style/properties/helpers.mako.rs @@ -4,7 +4,7 @@ <%! 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', @@ -842,12 +842,16 @@ <% 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-", "")) %> @@ -860,6 +864,15 @@ } % 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) diff --git a/servo/components/style/properties/longhands/border.mako.rs b/servo/components/style/properties/longhands/border.mako.rs index 79df20d78123..b9d3425f84a8 100644 --- a/servo/components/style/properties/longhands/border.mako.rs +++ b/servo/components/style/properties/longhands/border.mako.rs @@ -3,7 +3,7 @@ * 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", @@ -70,17 +70,27 @@ ${helpers.gecko_keyword_conversion( )} // FIXME(#4126): when gfx supports painting it, make this Size2D -% 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 diff --git a/testing/web-platform/tests/css/css-logical/logical-box-border-radius.html b/testing/web-platform/tests/css/css-logical/logical-box-border-radius.html new file mode 100644 index 000000000000..81b8fa0fece7 --- /dev/null +++ b/testing/web-platform/tests/css/css-logical/logical-box-border-radius.html @@ -0,0 +1,18 @@ + + +CSS Logical Properties: flow-relative border-radius + + + + + + +
+ + diff --git 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 index 1f17ff296ff2..ef1854f97de4 100644 --- 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 @@ -66,6 +66,41 @@ export function createBoxPropertyGroup(property, descriptor) { 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. * @@ -101,6 +136,7 @@ export function runTests(group) { 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 = []; @@ -141,7 +177,22 @@ export function runTests(group) { 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; } diff --git a/toolkit/components/extensions/parent/ext-downloads.js b/toolkit/components/extensions/parent/ext-downloads.js index 212fa5dffa92..f18ad9a948e7 100644 --- a/toolkit/components/extensions/parent/ext-downloads.js +++ b/toolkit/components/extensions/parent/ext-downloads.js @@ -266,8 +266,8 @@ const downloadQuery = query => { // 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. @@ -323,7 +323,7 @@ const downloadQuery = query => { // 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) { diff --git a/toolkit/components/extensions/schemas/downloads.json b/toolkit/components/extensions/schemas/downloads.json index 712f7051b8c1..88119638b812 100644 --- a/toolkit/components/extensions/schemas/downloads.json +++ b/toolkit/components/extensions/schemas/downloads.json @@ -112,7 +112,8 @@ }, "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.", @@ -257,8 +258,7 @@ "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.", diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js index 995044a94207..5c0914624708 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js @@ -2,6 +2,8 @@ /* 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(); @@ -424,3 +426,49 @@ add_task(async function test_search() { 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(); +}); diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp index 901930444eda..83dfde8f2afe 100644 --- a/xpcom/base/CycleCollectedJSRuntime.cpp +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -800,12 +800,12 @@ void CycleCollectedJSRuntime::TraverseNativeRoots( profiler_add_marker( "GCMajor", MakeUnique( aDesc.startTime(aContext), aDesc.endTime(aContext), - aDesc.summaryToJSON(aContext))); + aDesc.formatJSONProfiler(aContext))); } else if (aProgress == JS::GC_SLICE_END) { profiler_add_marker("GCSlice", MakeUnique( aDesc.lastSliceStart(aContext), aDesc.lastSliceEnd(aContext), - aDesc.sliceToJSON(aContext))); + aDesc.sliceToJSONProfiler(aContext))); } } #endif