Firefox: aggiungere una nuova proprietà CSS a Gecko

Firefox: aggiungere una nuova proprietà CSS a Gecko

Quella che segue è la traduzione di un documento fondamentale della vecchia documentazione che accompagna il codice sorgente di Firefox, ossia come aggiungere una nuova proprietà CSS a Gecko. Spero lo troviate interessante.

Storia del documento:

  • 21/3/2002: Marc Attinasi (attinasi@netscape.com) ha creato il documento implementando -moz-force-broken-image-icon per il bug 58646
  • 18/6/2002: David Baron (dbaron@dbaron.org) ha corretto il supporto per 'inherit' e '-moz-initial' (ma non si è testato il codice), e ha spiegato la funzione del booleano finale in CheckPropertyData.
  • 09/11/2002: Christopher Aillon (caillon@returnzero.com) ha aggiornato la descrizione delle macro nsCSSPropList.h e ha aggiunto informazioni su nsIDOMCSS2Properties.idl.

NOTA: Questo documento manca di alcune parti. Ho bisogno di aggiungere informazioni sull'inserimento di nsComputedDOMStyle.

Sguardo d'insieme

Quando si deve inserire una nuova proprietà di stile vi sono molte parti del codice che devono essere aggiornate. Questo documento illustra la procedura usata per aggiungere una nuova proprietà, in questo caso la proprietà proprietaria '-moz-force-broken-image-icons', che viene usata come modo per forzare la visualizzazione delle icone delle immagini mancanti. Tutto questo viene fatto nel contesto del bug 58646.

Analisi

Dovete subito decidere alcune cose sulla nuova proprietà:

Domande:

  1. La proprietà è proprietaria o specificata dallo standard CSS?
  2. La proprietà è ereditaria?
  3. Quali tipi di valori può avere la proprietà?
  4. Si adatta logicamente alle altre proprietà esistenti?
  5. Qual'è l'impatto sul layout della pagina se la proprietà cambia?
  6. Come volete chiamarla?

Risposte:

  1. Nel nostro caso specifico, vogliamo una proprietà che sia usata internamente, ossia una proprietà proprietaria.
  2. La proprietà deve essere usata per le immagini, che sono elementi foglia, così non c'è bisogno che sia ereditaria.
  3. La proprietà viene usata solo per rappresentare con un'icona le immagini mancanti, quindi supporta solo i valori numerici '0' e '1'.
  4. È difficile prevedere come questa proprietà si adatti con le altre, ma se facciamo uno sforzo di immaginazione, possiamo dire che è un tipo di proprietà della UI.
  5. Se questa proprietà cambia, il frame dell'immagine deve essere ricreato. Questo accade perché la decisione di visualizzare o meno l'icona ha un'impatto sulla decisione di sostituire il frame dell'immagine con un frame di testo inline per il testo dell'attributo ALT. Quindi se tale testo inline è già stato creato, non vi è alcun frame immagine intorno per il reflow o una successiva modifica.
  6. Infine, il nome sarà '-moz-force-broken-image-icons' – che dovrebbe essere alquanto autodescrittivo (per convenzione facciamo cominciare i nomi delle proprietà proprietarie con '-moz-').

Implementazione

Vi sono diverse parti del codice che devono essere edotte circa una nuova proprietà di stile. Esse sono:

  • CSS Property Names e Hint Tables: la nuova proprietà deve essere formalmente nota al sistema.
  • CSS Declaration: la dichiarazione deve essere in grado di gestire la proprietà e i suoi(o) valore(i).
  • CSS Parser: il parser deve essere in grado di analizzare il nome della proprietà, validare i valori e fornire una dichiarazione per la proprietà e il valore.
  • Style Context: lo StyleContext deve essere in grado di gestire il valore della proprietà, e fornire un mezzo per ottenere tale valore. Inoltre lo StyleContext deve sapere che tipo di impatto causa una modifica a questa proprietà.
  • Rule Nodes: RuleNodes deve sapere come viene ereditata la proprietà e come viene condivisa da altri elementi.
  • DOM: Lo stile dovrebbe essere accessibile dal DOM in modo che gli utenti possano impostare/ottenere il valore della proprietà.
  • Layout: Layout deve sapere cosa fare con la proprietà, ossia il significato della proprietà.

CSS Property Name / Costanti / Hints

Per prima cosa aggiungiamo il nome della proprietà all'elenco di proprietà in nsCSSPropList.h. Inseriamo la proprietà in ordine alfabetico, usando i nomi delle proprietà esistenti come modello. L'entry inserita avrà la forma di:


CSS_PROP(-moz-force-broken-image-icons, force_broken_image_icons, MozForceBrokenImageIcons, NS_STYLE_HINT_FRAMECHANGE) 
// bug 58646

Il primo valore è il nome formale della proprietà, ossia il nome che verrà letto dal parser CSS. Il secondo valore è il nome della proprietà come apparirà internamente. Il terzo valore è il nome della proprietà del DOM usata per accedere allo stile. L'ultimo valore indica cosa deve cambiare quando cambia il valore della proprietà. Deve essere un nsChangeHint.

Se avete bisogno di introdurre nuove costanti per i valori della proprietà queste devono essere aggiunte a nsStyleConsts.h e alle tabelle appropriate di parola chiave in nsCSSProps.cpp (nota: questo manuale non lo fa, dato che la nuova proprietà non richiede nessuna nuova parola chiave per i valori della proprietà).

CSS Declaration

Devono essere apportati dei cambiamenti alle strutture e alle classi definite in nsCSSDeclaration.h e nsCSSDeclaration.cpp.

Per prima cosa, trovate la dichiarazione della struttura che conserverà il valore della nuova proprietà (nel file header). In questo esempio è la struttura nsCSSUserInterface. Modificate la dichiarazione della struttura per includere un nuovo membro dati per la nuova proprietà, del tipo CSSValue. Quindi aprite il file dell'implementazione (il .cpp) e modificate i costruttori della struttura.

Quindi il metodo AppendValue deve essere aggiornato per supportare la nuova proprietà. Il CSSParser lo chiamerà per costruire una dichiarazione. Trovate la parte del metodo che ha a che fare con le altre proprietà nella struttura a cui state aggiungendo la proprietà (o create una nuova sezione se state creando una nuova struttura di stile). In questo esempio troveremo la sezione UserInterface e aggiungeremo la nostra nuova proprietà in quel punto.


// nsCSSUserInterface
case eCSSProperty_user_input:
case eCSSProperty_user_modify:
case eCSSProperty_user_select:
case eCSSProperty_key_equivalent:
case eCSSProperty_user_focus:
case eCSSProperty_resizer:
case eCSSProperty_cursor:
case eCSSProperty_force_broken_image_icons: {
CSS_ENSURE(UserInterface) {
switch (aProperty) {
case eCSSProperty_user_input: theUserInterface->mUserInput = aValue; break;
case eCSSProperty_user_modify: theUserInterface->mUserModify = aValue; break;
case eCSSProperty_user_select: theUserInterface->mUserSelect = aValue; break;
case eCSSProperty_key_equivalent:
CSS_ENSURE_DATA(theUserInterface->mKeyEquivalent, nsCSSValueList) {
theUserInterface->mKeyEquivalent->mValue = aValue;
CSS_IF_DELETE(theUserInterface->mKeyEquivalent->mNext);
}
break;
case eCSSProperty_user_focus: theUserInterface->mUserFocus = aValue; break;
case eCSSProperty_resizer: theUserInterface->mResizer = aValue; break;
case eCSSProperty_cursor:
CSS_ENSURE_DATA(theUserInterface->mCursor, nsCSSValueList) {
theUserInterface->mCursor->mValue = aValue;
CSS_IF_DELETE(theUserInterface->mCursor->mNext);
}
break;
case eCSSProperty_force_broken_image_icons: theUserInterface->mForceBrokenImageIcon = aValue; break;
CSS_BOGUS_DEFAULT; // fai felice il compilatore
}
}
break;
}

Il metodo GetValue deve a sua volta essere modificato.


// nsCSSUserInterface
case eCSSProperty_user_input:
case eCSSProperty_user_modify:
case eCSSProperty_user_select:
case eCSSProperty_key_equivalent:
case eCSSProperty_user_focus:
case eCSSProperty_resizer:
case eCSSProperty_cursor:
case eCSSProperty_force_broken_image_icons: {
CSS_VARONSTACK_GET(UserInterface);
if (nsnull != theUserInterface) {
switch (aProperty) {
case eCSSProperty_user_input: aValue = theUserInterface->mUserInput; break;
case eCSSProperty_user_modify: aValue = theUserInterface->mUserModify; break;
case eCSSProperty_user_select: aValue = theUserInterface->mUserSelect; break;
case eCSSProperty_key_equivalent:
if (nsnull != theUserInterface->mKeyEquivalent) {
aValue = theUserInterface->mKeyEquivalent->mValue;
}
break;
case eCSSProperty_user_focus: aValue = theUserInterface->mUserFocus; break;
case eCSSProperty_resizer: aValue = theUserInterface->mResizer; break;
case eCSSProperty_cursor:
if (nsnull != theUserInterface->mCursor) {
aValue = theUserInterface->mCursor->mValue;
}
break;
case eCSSProperty_force_broken_image_icons: aValue = theUserInterface->mForceBrokenImageIcons; break;
CSS_BOGUS_DEFAULT; // fai felice il compilatore
}
}
else {
aValue.Reset();
}
break;
}

Infine, modifichiamo il metodo List per l'output del valore della proprietà.


void nsCSSUserInterface::List(FILE* out, PRInt32 aIndent) const
{
for (PRInt32 index = aIndent; --index >= 0; ) fputs(" ", out);
nsAutoString buffer;
mUserInput.AppendToString(buffer, eCSSProperty_user_input);
mUserModify.AppendToString(buffer, eCSSProperty_user_modify);
mUserSelect.AppendToString(buffer, eCSSProperty_user_select);
nsCSSValueList* keyEquiv = mKeyEquivalent;
while (nsnull != keyEquiv) {
keyEquiv->mValue.AppendToString(buffer, eCSSProperty_key_equivalent);
keyEquiv= keyEquiv->mNext;
}
mUserFocus.AppendToString(buffer, eCSSProperty_user_focus);
mResizer.AppendToString(buffer, eCSSProperty_resizer);
nsCSSValueList* cursor = mCursor;
while (nsnull != cursor) {
cursor->mValue.AppendToString(buffer, eCSSProperty_cursor);
cursor = cursor->mNext;
}
mForceBrokenImageIcon.AppendToString(buffer,eCSSProperty_force_broken_image_icons);
fputs(NS_LossyConvertUTF16toASCII(buffer).get(), out);
}

CSS Parser

Ora il CSSParser deve essere edotto circa questa nuova proprietà, in modo da essere in grado di leggere le dichiarazioni formali e costruire le dichiarazioni interne che saranno usate per creare le regole. Se state aggiungendo una semplice proprietà che usa un solo valore, aggiungete la nuova proprietà al metodo ParseSingleProperty. Se viene richiesto un parsing più complesso, dovrete scrivere un nuovo metodo per gestirlo, modellandolo sulla base di uno dei metodi helper di parsing esistenti (vedi ParseBackground per un esempio). Qui stiamo solo aggiungendo una proprietà dal valore singolo.

Aprite nsCSSParser.cpp e cercate il metodo ParseSingleProperty. Questo metodo chiama la routine helper per analizzare il valore(i). Trovate una proprietà esistente simile alla proprietà che state aggiungendo. Nel nostro esempio stiamo aggiungendo una proprietà che assume un valore numerico, così la modelleremo sulla base della proprietà height e chiameremo ParsePositiveVariant. Aggiungete un nuovo caso per la nuova proprietà e chiamate il parser-helper appropriato. Eseguite una chiamata a ParseVariant passandogli il flag variante che ha un senso per la vostra proprietà. Nel nostro caso


case eCSSProperty_force_broken_image_icons:
return ParsePositiveVariant(aErrorCode, aValue, VARIANT_INTEGER, nsnull);

Questo farà analizzare il valore come un valore positivo intero, che è quello che vogliamo.

Style Context

Dopo aver implementato il supporto per la nuova proprietà nelle classi CSS Parser e CSS Declaration nel modulo del contenuto, è tempo di fornire supporto alla nuova proprietà nel layout. Allo Style Context deve essere dato un nuovo membro dati corrispondente al nuovo membro dati della dichiarazione, in modo che il valore calcolato possa essere conservato per gli oggetti del layout da usare.

Per prima cosa guardate in nsStyleStruct.h per vedere le strutture di stile esistenti. Trovatene una in cui volete memorizzare i dati. In questo esempio vogliamo metterli nella struttura nsStyleUserInterface, ma vi è una classe nsStyleUIReset che conserva i valori non ereditari e quindi useremo questa (ricordate che la nostra proprietà non è ereditaria). Aggiungete un membro dati per conservare il valore:


struct nsStyleUIReset {
nsStyleUIReset(void);
nsStyleUIReset(const nsStyleUIReset& aOther);
~nsStyleUIReset(void);
NS_DEFINE_STATIC_STYLESTRUCTID_ACCESSOR(eStyleStruct_UIReset)
void* operator new(size_t sz, nsPresContext* aContext) {
return aContext->AllocateFromShell(sz);
}
void Destroy(nsPresContext* aContext) {
this->~nsStyleUIReset();
aContext->FreeToShell(sizeof(nsStyleUIReset), this);
};
PRInt32 CalcDifference(const nsStyleUIReset& aOther) const;
PRUint8 mUserSelect; // [reset] (selezione-stile)
PRUnichar mKeyEquivalent; // [reset] XXX di che tipo deve essere?
PRUint8 mResizer; // [reset]
PRUint8 mForceBrokenImageIcon; // [reset] (0 se non viene forzato, altrimenti forzato)
};

Nel file di implementazione nsStyleContext.cpp aggiungete il nuovo membro dati ai costruttori della struttura di stile e il metodo CalcDifference che deve restituire il corretto hint stile-modifica quando viene individuata una modifica alla vostra nuova proprietà. I cambiamenti nel costruttore sono ovvi, ma ecco la modifica a CalcDifference per il nostro esempio:


PRInt32 nsStyleUIReset::CalcDifference(const nsStyleUIReset& aOther) const
{
if (mForceBrokenImageIcon == aOther.mForceBrokenImageIcon) {
if (mResizer == aOther.mResizer) {
if (mUserSelect == aOther.mUserSelect) {
if (mKeyEquivalent == aOther.mKeyEquivalent) {
return NS_STYLE_HINT_NONE;
}
return NS_STYLE_HINT_CONTENT;
}
return NS_STYLE_HINT_VISUAL;
}
return NS_STYLE_HINT_VISUAL;
}
return NS_STYLE_HINT_FRAMECHANGE;
}

CSSStyleRule

nsCSSStyleRule deve essere aggiornato per gestire la correlazione tra la dichiarazione e la struttura di stile. Nel file nsCSSStyleRule.cpp, individuate la funzione che correla la dichiarazione con la struttura di stile che avete aggiunto alla vostra proprietà. Per esempio, aggiorniamo MapUIForDeclaration:


static nsresult
MapUIForDeclaration(nsCSSDeclaration* aDecl, const nsStyleStructID& aID, nsCSSUserInterface& aUI)
{
if (!aDecl)
return NS_OK; // La regola deve avere una dichiarazione.
nsCSSUserInterface* ourUI = (nsCSSUserInterface*)aDecl->GetData(kCSSUserInterfaceSID);
if (!ourUI)
return NS_OK; // Non abbiamo nessuna regola per UI.
if (aID == eStyleStruct_UserInterface) {
if (aUI.mUserFocus.GetUnit() == eCSSUnit_Null && ourUI->mUserFocus.GetUnit() != eCSSUnit_Null)
aUI.mUserFocus = ourUI->mUserFocus;
if (aUI.mUserInput.GetUnit() == eCSSUnit_Null && ourUI->mUserInput.GetUnit() != eCSSUnit_Null)
aUI.mUserInput = ourUI->mUserInput;
if (aUI.mUserModify.GetUnit() == eCSSUnit_Null && ourUI->mUserModify.GetUnit() != eCSSUnit_Null)
aUI.mUserModify = ourUI->mUserModify;
if (!aUI.mCursor && ourUI->mCursor)
aUI.mCursor = ourUI->mCursor;
}
else if (aID == eStyleStruct_UIReset) {
if (aUI.mUserSelect.GetUnit() == eCSSUnit_Null && ourUI->mUserSelect.GetUnit() != eCSSUnit_Null)
aUI.mUserSelect = ourUI->mUserSelect;
if (!aUI.mKeyEquivalent && ourUI->mKeyEquivalent)
aUI.mKeyEquivalent = ourUI->mKeyEquivalent;
if (aUI.mResizer.GetUnit() == eCSSUnit_Null && ourUI->mResizer.GetUnit() != eCSSUnit_Null)
aUI.mResizer = ourUI->mResizer;
if (aUI.mForceBrokenImageIcon.GetUnit() == eCSSUnit_Null && ourUI->mForceBrokenImageIcon.GetUnit() == eCSSUnit_Integer) aUI.mForceBrokenImageIcon = ourUI->mForceBrokenImageIcon;
}
return NS_OK;
}

Rule Node

Ora dobbiamo aggiornare il codice di RuleNode per avere informazioni sulla nuova proprietà. Per prima cosa, individuate l'array PropertyCheckData per i dati aggiunti alla nuova proprietà. Nel nostro esempio aggiungiamo i seguenti dati:


static const PropertyCheckData UIResetCheckProperties[] = {
CHECKDATA_PROP(nsCSSUserInterface, mUserSelect, CHECKDATA_VALUE, PR_FALSE),
CHECKDATA_PROP(nsCSSUserInterface, mResizer, CHECKDATA_VALUE, PR_FALSE),
CHECKDATA_PROP(nsCSSUserInterface, mKeyEquivalent, CHECKDATA_VALUELIST, PR_FALSE)
CHECKDATA_PROP(nsCSSUserInterface, mForceBrokenImageIcon, CHECKDATA_VALUE, PR_FALSE)
};

I primi due argomenti corrispondono alla struttura e ai dati membro di CSSDeclaration, il terzo è il tipo di dati, il quarto indica se vi è un valore di coordinata che usa un valore ereditato esplicito sulla struttura di dati di stile che deve essere calcolata dal layout.

Quindi dobbiamo assicurarci che il metodo ComputeXXX della struttura a cui la proprietà è stata aggiunta sia aggiornato per gestire il nuovo valore. In questo esempio dobbiamo modificare il metodo nsRuleNode::ComputeUIResetData per gestire CSSDeclaration nella struttura di stile:


...
// ridimensiona: auto, none, enum, inherit
if (eCSSUnit_Enumerated == uiData.mResizer.GetUnit()) {
ui->mResizer = uiData.mResizer.GetIntValue();
}
else if (eCSSUnit_Auto == uiData.mResizer.GetUnit()) {
ui->mResizer = NS_STYLE_RESIZER_AUTO;
}
else if (eCSSUnit_None == uiData.mResizer.GetUnit()) {
ui->mResizer = NS_STYLE_RESIZER_NONE;
}
else if (eCSSUnit_Inherit == uiData.mResizer.GetUnit()) {
inherited = PR_TRUE;
ui->mResizer = parentUI->mResizer;
}
// force-broken-image-icons: integer, inherit, -moz-initial
if (eCSSUnit_Integer == uiData.mForceBrokenImageIcons.GetUnit()) {
ui->mForceBrokenImageIcons = uiData.mForceBrokenImageIcons.GetIntValue();
} else if (eCSSUnit_Inherit == uiData.mForceBrokenImageIcons.GetUnit()) {
inherited = PR_TRUE;
ui->mForceBrokenImageIcons = parentUI->mForceBrokenImageIcons;
} else if (eCSSUnit_Initial == uiData.mForceBrokenImageIcons.GetUnit()) {
ui->mForceBrokenImageIcons = 0;
}
if (inherited)
// È ereditaria, e quindi non si può memorizzare nel Rule Node. Dobbiamo metterla nello
// Style Context.
aContext->SetStyle(eStyleStruct_UIReset, *ui);
else {
// Pienamente specificata e quindi si può memorizzare nel Rule Node.
if (!aHighestNode->mStyleData.mResetData)
aHighestNode->mStyleData.mResetData = new (mPresContext) nsResetStyleData;
aHighestNode->mStyleData.mResetData->mUIData = ui;
// Propagare il bit.
PropagateDependentBit(NS_STYLE_INHERIT_UI_RESET, aHighestNode);
}
...

DOM

Gli utenti, sia negli script o al di fuori del modulo del layout o del contenuto, possono aver bisogno di accedere alla nuova proprietà. Questo viene fatto tramite il DOM CSS, nello specifico con nsIDOMCSSStyleDeclaration, nsIDOMCSS2Properties, e nsIDOMNSCSS2Properties. Grazie al preprocessore C++, le interfacce CSS2Properties verranno automaticamente implementate quando aggiungete la vostra proprietà a nsCSSPropList.h. Per questo motivo, se non riuscite ad aggiungere la proprietà all'interfaccia del DOM, vi verranno restituiti degli errori di compilazione. Tutto quello che dovete fare è modificare nsIDOMCSS2Properties.idl e aggiungere l'attributo appropriato all'interfaccia nsIDOMNSCSS2Properties (in fondo al file). Per esempio:


attribute DOMString MozForceBrokenImageIcon;
// solleva(DOMException) nell'impostazione

Layout

Alla fine lo Style System supporta la nuova proprietà. È tempo di farne un uso effettivo.

Nel modulo del layout, reperite la struttura di stile che ha la nuova proprietà dal contesto di stile del frame. Accedete alla nuova proprietà e ottenete il suo valore. È semplice. In questo esempio abbiamo nsImageFrame:


PRBool forceIcon = PR_FALSE;
if (GetStyleUIReset()->mForceBrokenImageIcon) {
forceIcon = PR_TRUE;
}

Create alcuni test con le regole di stile che usano la nuova proprietà. Assicuratevi che il parsing sia corretto. Testatela in un foglio di stile esterno e con uno stile inline. Testate che venga ereditata in modo corretto (o non ereditata). Aggiornate questo documento con ulteriori dettagli, o correggete gli errori.

Torna su