Of:de implementatie van de SpiderMonkey Debugger (en de opschoning ervan)
We hebben de afgelopen twee jaar grote verbeteringen aangebracht in JavaScript-foutopsporing in Firefox DevTools. Feedback van ontwikkelaars heeft ons werk op het gebied van prestaties, bronkaarten, betrouwbaarheid van stappen, mooie afdrukken en meer soorten breekpunten geïnformeerd en gevalideerd. Dank je. Als je Firefox al een tijdje niet hebt geprobeerd voor het debuggen van modern JavaScript, is dit het juiste moment.
Veel van de bovengenoemde inspanningen waren gericht op de Debugger-frontend (geschreven in React en Redux). We konden gestaag vooruitgang boeken. Door de integratie met SpiderMonkey, de JavaScript-engine van Firefox, ging het werk langzamer. Om grotere functies aan te pakken, zoals de juiste asynchrone call-stacks (nu beschikbaar in DevEdition), moesten we een grote schoonmaak uitvoeren. Hier is hoe we dat deden.
Achtergrond:een korte geschiedenis van de JS Debugger
De JavaScript-foutopsporing in Firefox is gebaseerd op de Debugger
. van de SpiderMonkey-engine API. Deze API is in 2011 toegevoegd. Sindsdien heeft het de toevoeging van vier JIT-compilers, de pensionering van twee ervan en de toevoeging van een WebAssembly-compiler overleefd. Dat alles, zonder dat er substantiële wijzigingen moeten worden aangebracht aan de gebruikers van de API. Debugger
legt slechts tijdelijk een prestatiestraf op, terwijl de ontwikkelaar de uitvoering van de debuggee nauwlettend in de gaten houdt. Zodra de ontwikkelaar wegkijkt, kan het programma terugkeren naar zijn geoptimaliseerde paden.
Een paar belangrijke beslissingen (sommige van ons, andere opgelegd door de situatie) waren van invloed op de Debugger
's implementatie:
- Voor beter of slechter, het is een centraal principe van de architectuur van Firefox dat JavaScript-code met verschillende privilegeniveaus een enkele hoop kan delen. Objectranden en functieaanroepen overschrijden indien nodig privilegegrenzen. SpiderMonkey's compartimenten zorg ervoor dat de nodige veiligheidscontroles worden uitgevoerd in deze vrij draaiende omgeving. De API moet naadloos over compartimentgrenzen heen werken.
Debugger
is een intra-thread debugging API:gebeurtenissen in de debuggee worden afgehandeld op dezelfde thread die ze heeft geactiveerd. Dit houdt de implementatie vrij van threading-problemen, maar roept andere soorten complicaties op.Debugger
s moeten een natuurlijke interactie hebben met het ophalen van afval. Als een object niet wordt gemist, moet het mogelijk zijn voor de vuilnisman om het te recyclen, of het nu eenDebugger
is , een debuggee of anderszins.- Een
Debugger
mag alleen activiteit observeren die plaatsvindt binnen het bereik van een bepaalde set globale JavaScript-objecten (bijvoorbeeld een venster of een sandbox). Het zou geen effect moeten hebben op activiteit elders in de browser. Maar het moet ook mogelijk zijn voor meerdereDebugger
s om hetzelfde globaal te observeren, zonder al te veel interferentie.
Vuilnisophaling
Mensen verklaren vuilnismannen meestal door te zeggen dat ze objecten recyclen die "onbereikbaar" zijn, maar dit is niet helemaal correct. Stel dat we bijvoorbeeld schrijven:
fetch("https://www.example.com/")
.then(res => {
res.body.getReader().closed.then(() => console.log("stream closed!"))
});
Als we klaar zijn met het uitvoeren van deze instructie, is geen van de objecten die het heeft geconstrueerd bereikbaar voor de rest van het programma. Desalniettemin verbiedt de WHATWG-specificatie de browser om alles te verzamelen en de fetch
te beëindigen . Als dit het geval zou zijn, zou het bericht niet op de console worden vastgelegd en zou de gebruiker weten dat de garbagecollection had plaatsgevonden.
Vuilnisverzamelaars houden zich aan een interessant principe:een object mag alleen worden gerecycled als het nooit zou worden gemist. Dat wil zeggen, het geheugen van een object mag alleen worden hergebruikt als dit geen waarneembaar effect heeft op de toekomstige uitvoering van het programma, behalve natuurlijk dat er meer geheugen beschikbaar komt voor verder gebruik.
Het principe in actie
Overweeg de volgende code:
// Create a new JavaScript global object, in its own compartment.
var global = newGlobal({ newCompartment: true });
// Create a new Debugger, and use its `onEnterFrame` hook to report function
// calls in `global`.
new Debugger(global).onEnterFrame = (frame) => {
if (frame.callee) {
console.log(`called function ${frame.callee.name}`);
}
};
global.eval(`
function f() { }
function g() { f(); }
g();
`);
Wanneer uitgevoerd in de JavaScript-shell van SpiderMonkey (waarin Debugger
constructor en de newGlobal
functie zijn onmiddellijk beschikbaar), dit drukt:
called function g
called function f
Net als in de fetch
voorbeeld, de nieuwe Debugger
wordt onbereikbaar door het programma zodra we klaar zijn met het instellen van de onEnterFrame
haak. Aangezien echter alle toekomstige functieaanroepen binnen het bereik van global
console-uitvoer zal produceren, zou het onjuist zijn voor de vuilnisman om de Debugger
. te verwijderen . De afwezigheid ervan zou waarneembaar zijn zodra global
een functie-aanroep gedaan.
Een soortgelijke redenering geldt voor veel andere Debugger
faciliteiten. De onNewScript
hook rapporteert de introductie van nieuwe code in het bereik van een debuggee global, hetzij door eval
aan te roepen , het laden van een <script>
element, het instellen van een onclick
behandelaar of iets dergelijks. Of, door een breekpunt in te stellen, wordt de handlerfunctie ervan aangeroepen telkens wanneer de besturing het aangewezen punt in de code bereikt. In al deze gevallen roept debuggee-activiteit functies aan die zijn geregistreerd met een Debugger
, die alles kan doen wat de ontwikkelaar wil, en dus waarneembare effecten heeft.
Dit geval is echter anders:
var global = newGlobal({ newCompartment: true });
new Debugger(global);
global.eval(`
function f() { }
function g() { f(); }
g();
`);
Hier, de nieuwe Debugger
is gemaakt, maar wordt verwijderd zonder dat er haken worden ingesteld. Als deze Debugger
werden weggegooid, zou niemand er ooit wijzer van worden. Het moet in aanmerking komen voor recycling door de vuilnisman. Verder gaan, in de onEnterFrame
voorbeeld hierboven, als global
onnodig wordt, zonder timers of gebeurtenishandlers of wachtende ophaalacties om er ooit weer code in uit te voeren, dan global
, het is Debugger
, en zijn handlerfunctie moeten allemaal in aanmerking komen voor verzameling.
Het principe is dat Debugger
objecten zijn niets bijzonders voor de GC. Het zijn gewoon objecten waarmee we de uitvoering van een JavaScript-programma kunnen observeren en verder dezelfde regels volgen als alle anderen. JavaScript-ontwikkelaars stellen het op prijs te weten dat, als ze simpelweg onnodige verstrikkingen vermijden, het systeem het geheugen voor hen zal opruimen zodra dit veilig is. En dit gemak strekt zich uit tot code met behulp van de Debugger
API.
De implementatie
Als we de bovenstaande beschrijving doornemen, lijkt het duidelijk dat wanneer een Debugger
heeft een onEnterFrame
haak, een onNewScript
hook, of iets dergelijks, zijn debuggee-globalen bevatten een eigen verwijzing ernaar. Zolang die globals in leven zijn, is de Debugger
moet ook behouden blijven. Het wissen van al die hooks zou die eigendomsreferentie moeten verwijderen. De levendigheid van het globale garandeert dus niet langer dat de Debugger
zal overleven. (Referenties van elders in het systeem kunnen natuurlijk.)
En dat is ongeveer hoe het is gedaan. Op C++-niveau heeft elke JavaScript-global een bijbehorende JS::Realm
object, dat een tabel van DebuggerLink
. bezit objecten, één voor elke Debugger
waarvan het een debuggee is. Elke DebuggerLink
object bevat een optioneel sterke verwijzing naar zijn Debugger
. Dit wordt ingesteld wanneer de Debugger
heeft interessante haken, en anders gewist. Daarom, wanneer de Debugger
heeft haken ingesteld, er is een sterk pad, via de DebuggerLink
tussenpersoon, van zijn debuggee-globalen tot de Debugger
. Als de haken daarentegen vrij zijn, is er geen pad.
Een onderbrekingspunt dat in een script is ingesteld, gedraagt zich op dezelfde manier. Het fungeert als een eigendomsreferentie van dat script naar de handlerfunctie van het breekpunt en de Debugger
waartoe het behoort. Zolang het script live is, zullen de handler en Debugger
moet ook in leven blijven. Of, als het script wordt hergebruikt, zal dat breekpunt zeker nooit meer worden geraakt, dus de handler kan net zo goed ook gaan. En als alle de Debugger
's breakpoints'-scripts worden gerecycled, waarna de scripts de Debugger
niet langer beschermen uit collectie.
De zaken waren echter niet altijd zo eenvoudig.
Wat is er veranderd
Oorspronkelijk Debugger
objecten hadden een enabled
vlag, die, indien ingesteld op false
, onmiddellijk alle Debugger
uitgeschakeld 's haken en breekpunten. De bedoeling was om één centraal controlepunt te bieden. Op deze manier zou de Firefox Developer Tools-server een Debugger
. kunnen neutraliseren (bijvoorbeeld wanneer de gereedschapskist gesloten is), zodat het geen verdere impact op het systeem heeft. Natuurlijk, gewoon het wissen van de Debugger
De set debuggee-globalen - een mogelijkheid die we sowieso voor andere doeleinden nodig hadden - heeft bijna precies hetzelfde effect. Dit betekende dus de enabled
vlag was overbodig. Maar, zo redeneerden we, hoeveel problemen kan een simpele booleaanse vlag eigenlijk veroorzaken?
Wat we niet hadden verwacht, was dat de aanwezigheid van de enabled
flag maakte de hierboven beschreven eenvoudige implementatie onpraktisch. Moet enabled
instellen tot false
echt alle breekpunten in de scripts van de debuggee opruimen? En zou het terug moeten zetten op true
ga je ze allemaal terugzetten? Dat leek belachelijk.
Dus, in plaats van globals en scripts te behandelen alsof ze referenties hadden naar hun geïnteresseerde Debugger
s, hebben we een nieuwe fase toegevoegd aan het afvalinzamelingsproces. Zodra de verzamelaar zoveel mogelijk objecten had gevonden om te behouden, zouden we alle Debugger
. doorlopen s in het systeem. We zouden iedereen willen vragen:Zijn een van uw debuggees er zeker van dat ze behouden blijven? Heb je haken of breekpunten ingesteld? En bent u ingeschakeld? Zo ja, dan hebben we de Debugger
. gemarkeerd zelf voor retentie.
Natuurlijk hebben we ooit besloten om een Debugger
, moesten we ook alle objecten behouden die het of zijn handlerfuncties mogelijk zouden kunnen gebruiken. We zouden dus het proces voor het verzamelen van afval opnieuw starten, het een tweede keer laten uitputten en de scan van alle Debuggers
herhalen. .
Vuilnisophaling opruimen
In het najaar van 2019 hebben Logan Smyth, Jason Laster en ik een reeks debugger-opruimingen uitgevoerd. Deze code, genaamd Debugger::markIteratively
, was een van onze doelen. We hebben de enabled
. verwijderd vlag, introduceerde (onder andere) de hierboven beschreven eigendomsranden en verkleinde Debugger::markIteratively
tot het punt dat het veilig kan worden verwijderd. Dit werk is gearchiveerd als bug 1592158:"Verwijder Debugger::hasAnyLiveFrames
en zijn gemene handlangers". (In feite heeft Logan het bij een stiekeme aanval verwijderd als onderdeel van een patch voor een blocker, bug 1592116.)
De SpiderMonkey-teamleden die verantwoordelijk zijn voor de vuilnisophaler, waardeerden ook onze opruiming. Het verwijderde een harig speciaal geval van de vuilnisman. De vervanging is code die er veel meer uitziet en zich gedraagt zoals al het andere in SpiderMonkey. Het idee dat “dit daarop wijst; dus als we dit houden, kunnen we dat maar beter ook houden' is het standaardpad voor een vuilnisophaler. En zo werd dit werk Debugger
van hoofdpijn in (bijna) gewoon een ander soort object.
Compartimenten
De Debugger
API leverde de beheerders van de vuilnismannen ook andere problemen op, in de interactie met SpiderMonkey-compartimenten en -zones.
In Firefox bevat de JavaScript-heap over het algemeen een mix van objecten van verschillende privilegeniveaus en oorsprong. Chrome-objecten kunnen verwijzen naar inhoudsobjecten en vice versa. Uiteraard moet Firefox bepaalde regels afdwingen over hoe deze objecten op elkaar inwerken. Het is bijvoorbeeld mogelijk dat inhoudscode alleen bepaalde methoden voor een Chrome-object mag aanroepen. Of Chrome-code wil misschien alleen de originele, door de webstandaard gespecificeerde methoden van een object zien, ongeacht hoe de inhoud met het prototype heeft gespeeld of de eigenschappen ervan opnieuw heeft geconfigureerd.
(Merk op dat Firefox' lopende 'Fission'-project webinhoud van verschillende oorsprongen zal scheiden in verschillende processen, dus randen tussen oorsprongen zullen veel minder gebruikelijk worden. Maar zelfs na Fission zal er nog steeds interactie zijn tussen chrome en inhoud JavaScript-code.)
Runtimes, zones en rijken
Om deze controles uit te voeren, afvalinzameling te ondersteunen en het web te ondersteunen zoals gespecificeerd, verdeelt Firefox de JavaScript-wereld als volgt:
- Een complete wereld van JavaScript-objecten die met elkaar kunnen interageren, wordt een runtime genoemd .
- De objecten van een runtime zijn verdeeld in zones , dat zijn de eenheden van garbagecollection. Elke garbagecollection verwerkt een bepaalde set zones. Meestal is er één zone per browsertabblad.
- Elke zone is verdeeld in compartimenten , die eenheden van oorsprong of privilege zijn. Alle objecten in een bepaald compartiment hebben dezelfde oorsprong en hetzelfde privilegeniveau.
- Een compartiment is verdeeld in rijken , overeenkomend met JavaScript-vensterobjecten of andere soorten globale objecten zoals sandboxen of JSM's.
Elk script is toegewezen aan een bepaald rijk, afhankelijk van hoe het is geladen. En aan elk object wordt een rijk toegewezen, afhankelijk van het script waarmee het wordt gemaakt.
Scripts en objecten mogen alleen direct verwijzen naar objecten in hun eigen compartiment. Voor referenties tussen compartimenten houdt elk compartiment een verzameling gespecialiseerde proxy's bij, genaamd omslagen tussen compartimenten . Elk van deze wikkels vertegenwoordigt een specifiek object in een ander compartiment. Deze wrappers onderscheppen alle toegangen tot eigendommen en functieaanroepen en voeren veiligheidscontroles uit. Dit wordt gedaan om te beslissen of ze moeten doorgaan, op basis van de relatieve privilegeniveaus en oorsprong van het compartiment van de verpakking en het compartiment van de referent. In plaats van een object van het ene compartiment naar het andere door te geven of terug te brengen, zoekt SpiderMonkey de verpakking van dat object op in het bestemmingscompartiment (maakt het als er geen bestaat). Dan overhandigt het de verpakking in plaats van het object.
Compartimenten inpakken
Een uitgebreid systeem van beweringen, in de vuilnisophaler maar ook in de rest van SpiderMonkey, verifieert dat er nooit directe randen tussen compartimenten worden gecreëerd. Bovendien mogen scripts alleen objecten in hun eigen compartimenten rechtstreeks raken.
Maar aangezien elke referentie tussen compartimenten door een wikkel moet worden onderschept, vormen de wikkeltabellen van de compartimenten een handig register van alle inter-zone ook referenties. Dit is precies de informatie die de vuilnisman nodig heeft om één set zones apart van de rest te verzamelen. Als een object geen wikkels heeft die het representeren in compartimenten buiten zijn eigen zone, dan weet de verzamelaar het. En dat allemaal zonder de hele looptijd te hoeven bekijken. Geen enkele andere zone zou dat object missen als het zou worden gerecycled.
Foutopsporing tussen compartimenten
De Debugger
API's Debugger.Object
objecten gooien een sleutel in deze nette machine. Aangezien de debugger-server geprivilegieerde Chrome-code is en de debuggee meestal inhoudscode is, vallen deze in afzonderlijke compartimenten. Dit betekent dat een Debugger.Object
's verwijzing naar zijn referentie is een referentie tussen compartimenten.
Maar de Debugger.Objects
kunnen geen compartimentoverschrijdende wikkels zijn. Een compartiment kan veel Debugger
. hebben objecten, die elk hun eigen kudde van Debugger.Objects
. hebben , dus er kunnen veel Debugger.Objects
. zijn verwijzend naar hetzelfde debuggee-object in een enkel compartiment. (Hetzelfde geldt voor Debugger.Script
en andere API-objecten. We concentreren ons op Debugger.Object
hier voor de eenvoud.)
Voorheen loste SpiderMonkey dit op door te eisen dat elke Debugger.Object
worden gecombineerd met een speciale toegang tot de wikkeltafel van het compartiment. De opzoeksleutel van de tabel was niet alleen een vreemd object, maar een (Debugger
, vreemd voorwerp) paar. Dit behield de invariant dat de wikkeltabellen van de compartimenten een record hadden van alle referenties tussen compartimenten.
Helaas vereisten deze inzendingen een speciale behandeling. Een gewone wikkel met meerdere compartimenten kan worden verwijderd als de voorwerpen van het compartiment daar niet meer naar wijzen, aangezien een gelijkwaardige wikkel op aanvraag kan worden geconstrueerd. Maar een Debugger.Object
moet worden bewaard zolang de Debugger
en referent leven. Een gebruiker kan een aangepaste eigenschap op een Debugger.Object
. plaatsen of gebruik het als een sleutel in een zwakke kaart. Die gebruiker zou de eigenschap of zwakke kaartinvoer kunnen verwachten wanneer hij het corresponderende debuggee-object opnieuw tegenkomt. Er is ook speciale zorg vereist om ervoor te zorgen dat de wrapper-tabelitems op betrouwbare wijze worden gemaakt en verwijderd synchroon met Debugger.Object
maken, zelfs als er fouten in het geheugen of andere onderbrekingen optreden.
Compartimenten opruimen
Als onderdeel van onze code-opschoning in de herfst van 2019 hebben we de speciale wikkeltabel-items verwijderd. Door simpelweg de Debugger
. te raadplegen API's eigen tabellen van Debugger.Objects
, hebben we de verwijzingen naar cross-compartimentverwijzingen voor de vuilnisophaler gewijzigd. Dit is Debugger
-specifieke code, die we natuurlijk liever vermijden, maar de afspraak was ook Debugger
-specifiek. De huidige aanpak is directer. Het lijkt meer op een gewone traceercode voor vuilnismannen. Hierdoor is een zorgvuldige synchronisatie tussen twee tabellen niet meer nodig.
Gedwongen retouren en uitzonderingen
Wanneer SpiderMonkey een Debugger
aanroept API-hook om een soort activiteit in de debuggee te rapporteren, de meeste hooks kunnen een hervattingswaarde retourneren om te zeggen hoe de debuggee moet doorgaan met uitvoeren:
undefined
betekent dat de debuggee normaal zou moeten doorgaan, alsof er niets was gebeurd.- Een object retourneren van de vorm
{ throw: EXN }
betekent dat de debuggee moet doorgaan alsof de waardeEXN
werden als uitzondering gegooid. - Een object retourneren van de vorm
{ return: RETVAL }
betekent dat de debuggee onmiddellijk moet terugkeren van de functie die nu wordt uitgevoerd, metRETVAL
als de retourwaarde. null
betekent dat de debuggee moet worden beëindigd, als door de trage scriptdialoog.
In de C++-code van SpiderMonkey was er een opgesomd type met de naam ResumeMode
, die waarden had Continue
, Throw
, Return
, en Terminate
, die elk van deze mogelijkheden vertegenwoordigen. Elke site in SpiderMonkey die een gebeurtenis moest melden aan Debugger
en respecteer dan een hervattingswaarde die nodig is om een switch
. te hebben verklaring voor elk van deze gevallen. De code in de bytecode-interpreter voor het invoeren van een functieaanroep zag er bijvoorbeeld als volgt uit:
switch (DebugAPI::onEnterFrame(cx, activation.entryFrame())) {
case ResumeMode::Continue:
break;
case ResumeMode::Return:
if (!ForcedReturn(cx, REGS)) {
goto error;
}
goto successful_return_continuation;
case ResumeMode::Throw:
case ResumeMode::Terminate:
goto error;
default:
MOZ_CRASH("bad DebugAPI::onEnterFrame resume mode");
}
Relevante SpiderMonkey-conventies ontdekken
Logan Smyth merkte echter dat, behalve voor ResumeMode::Return
, vielen al deze gevallen al onder SpiderMonkey's conventie voor 'feilbare operaties'. Volgens deze conventie moet een C++-functie die zou kunnen mislukken een JSContext*
. accepteren argument, en retourneer een bool
waarde. Als de bewerking slaagt, moet deze true
. teruggeven; anders zou het false
moeten retourneren en stel de status in van de gegeven JSContext
om een gegenereerde uitzondering of een beëindiging aan te geven.
Aangezien JavaScript-objecten bijvoorbeeld proxy's kunnen zijn of getter-eigenschappen kunnen hebben, is het ophalen van een eigenschap van een object een feilbare bewerking. Dus SpiderMonkey's js::GetProperty
functie heeft de handtekening:
bool js::GetProperty(JSContext* cx,
HandleValue v, HandlePropertyName name,
MutableHandleValue vp);
De waarde v
is het object, en name
is de naam van de eigenschap die we ervan willen ophalen. Bij succes, GetProperty
slaat de waarde op in vp
en retourneert true
. Bij mislukking vertelt het cx
wat er mis ging, en retourneert false
. Code die deze functie aanroept, kan er als volgt uitzien:
if (!GetProperty(cx, obj, id, &value)) {
return false; // propagate failure to our caller
}
Allerlei functies in SpiderMonkey volgen deze conventie. Ze kunnen zo complex zijn als het evalueren van een script, of zo eenvoudig als het toewijzen van een object. (Sommige functies retourneren een nullptr
in plaats van een bool
, maar het principe is hetzelfde.)
Deze conventie omvat drie van de vier ResumeMode
waarden:
ResumeMode::Continue
is gelijk aan het retourneren vantrue
.ResumeMode::Throw
is gelijk aan het retourneren vanfalse
en een uitzondering instellen op deJSContext
.ResumeMode::Terminate
is gelijk aan het retourneren vanfalse
maar geen uitzondering instellen op deJSContext
.
Het enige geval dat dit niet ondersteunt is ResumeMode::Return
.
Voortbouwen op SpiderMonkey-conventies
Vervolgens merkte Logan op dat SpiderMonkey al verantwoordelijk is voor het rapporteren van alle stackframe-pops aan de DebugAPI::onLeaveFrame
functie, zodat Debugger
kan frame onPop
oproepen handlers en het uitvoeren van andere boekhouding. Dus om een onmiddellijke terugkeer af te dwingen, kunnen we in principe:
- de gewenste retourwaarde ergens opbergen;
- retour
false
zonder een uitzondering te maken om beëindiging te forceren; - wacht tot de beëindiging zich verspreidt via de huidige functieaanroep, waarna SpiderMonkey
DebugAPI::onLeaveFrame
aanroept; - herstel onze opgeslagen retourwaarde en bewaar deze op de juiste plaats in het stapelframe; en tot slot
- retour
true
alsof er niets was gebeurd, een gewone terugkeer nabootsen.
Met deze aanpak zou de ResumeMode
. niet nodig zijn opsomming of speciale afhandeling op DebugAPI
bel sites. De gewone regels van SpiderMonkey voor het verhogen en verspreiden van uitzonderingen zijn al heel bekend bij elke SpiderMonkey-ontwikkelaar. Die regels doen al het werk voor ons.
Het blijkt dat de machinerie voor het opbergen van de retourwaarde en het herkennen van de noodzaak van interventie in DebugAPI::onLeaveFrame
bestond al in SpiderMonkey. Shu-Yu Guo had het jaren geleden geïmplementeerd om een zeldzaam geval van trage scripttime-outs en single-stepping af te handelen.
Met deze verzameling inzichten was Logan in staat om de oproepsites waarop SpiderMonkey activiteit rapporteert om te zetten in Debugger
in oproepsites net als die van elke andere feilbare functie. De oproep naar DebugAPI::onEnterFrame
hierboven weergegeven luidt nu eenvoudig:
if (!DebugAPI::onEnterFrame(cx, activation.entryFrame())) {
goto error;
}
Andere opruimacties
We hebben een aantal andere kleine schoonmaakacties uitgevoerd als onderdeel van onze inspanningen in het najaar van 2019:
- We splitsen het bestand
js/src/vm/Debugger.cpp
, oorspronkelijk 14k regels lang en bevat de volledigeDebugger
implementatie, in acht afzonderlijke bronbestanden, en verplaatste ze naar de mapjs/src/debugger
. Phabricator weigert niet langer om het bestand in te kleuren vanwege de lengte. - Elke
Debugger
API-objecttype,Debugger.Object
,Debugger.Frame
,Debugger.Environment
,Debugger.Script
, enDebugger.Source
, wordt nu vertegenwoordigd door zijn eigen C++-subklasse vanjs::NativeObject
. Hierdoor kunnen we de organisatorische hulpmiddelen gebruiken die C++ biedt om hun implementatiecode te structureren en te reikwijdte. We kunnen ook dynamische typecontroles in de C++-code vervangen door typen. De compiler kan deze tijdens het compileren controleren. - De code waarmee
Debugger.Script
enDebugger.Source
verwijzen naar zowel JavaScript als WebAssembly-code werd vereenvoudigd zodatDebugger::wrapVariantReferent
, in plaats van vijf sjabloonparameters te vereisen, vereist slechts één - en één die kan worden afgeleid door de C++-compiler, om op te starten.
Ik geloof dat dit werk heeft geresulteerd in een substantiële verbetering van de kwaliteit van leven van ingenieurs die te maken hebben met Debugger
’s uitvoering. Ik hoop dat het Firefox de komende jaren effectief kan blijven bedienen.