michael@0: michael@0: michael@0: michael@0: michael@0: Adding a new style property - layout cookbook michael@0: michael@0: michael@0:

Adding a new style property

michael@0:
michael@0:

Document history:

michael@0: michael@0:

michael@0: NOTE: This document is still missing a few pieces. I need to michael@0: add information on adding to nsComputedDOMStyle. michael@0:

michael@0:
michael@0:

Overview

michael@0: When a new style property is needed there are many places in the code that michael@0: need to be updated. This document outlines the procedure used to add a new michael@0: property, in this case the property is a proprietary one called '-moz-force-broken-image-icons' michael@0: and is used as a way for a stylesheet to force broken image icons to be displayed. michael@0: This is all being done in the context of michael@0: bug 58646. michael@0: michael@0:

Analysis

michael@0:

Up front you have to decide some things about the new property:

michael@0: michael@0:

Questions:

michael@0:
    michael@0:
  1. Is the property proprietary or specified by the CSS standard?
  2. michael@0:
  3. Is the property inherited?
  4. michael@0:
  5. What types of values can the property have?
  6. michael@0:
  7. Does it logically fit with other existing properties?
  8. michael@0:
  9. What is the impact to the layout of a page if that property changes?
  10. michael@0:
  11. What do you want to name it?
  12. michael@0:
michael@0:

Answers:

michael@0:
    michael@0:
  1. In our specific case, we want a property that is used internally, michael@0: so it is a proprietary property.
  2. michael@0:
  3. The property is to be used for images, which are leaf elements, so michael@0: there is no need to inherit it.
  4. michael@0:
  5. The property is used simply to force a broken image to be represented michael@0: by an icon, so it only supports the values '0' and '1' as numerics. 
  6. michael@0:
  7. It is hard to see how this property fits logically in with other michael@0: properties, but if we stretch our imaginations we could say that it is a michael@0: sort of UI property.
  8. michael@0:
  9. If this property changes, the image frame has to be recreated. This michael@0: is because the decision about whether to display the icon or not will impact michael@0: the decision to replace the image frame with an inline text frame for the michael@0: ALT text, so if the ALT text inline is already made, there is no image frame michael@0: left around to reflow or otherwise modify.
  10. michael@0:
  11. Finally, the name will be '-moz-force-broken-image-icons' - that michael@0: should be pretty self-describing (by convention we start proprietary property michael@0: names with '-moz-').
  12. michael@0:
michael@0:

Implementation

michael@0: michael@0:

There are several places that need to be educated about a new style property. michael@0: They are: michael@0:

michael@0: michael@0:

CSS Property Name / Constants / Hints

michael@0: michael@0:

michael@0: First, add the new name to the property list in michael@0: nsCSSPropList.h michael@0:  Insert the property in the list alphabetically, using the existing michael@0: property names as a template. The format of the entry you will create is: michael@0:

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

The first value is the formal property name, in other words the property michael@0: name as it is seen by the CSS parser.
michael@0: The second value is the name of the property as it will appear internally.
michael@0: The third value is the name of the DOM property used to access your style.
michael@0: The last value indicates what must change when the value of the property michael@0: changes. It should be an michael@0: nsChangeHint.

michael@0: michael@0:

If you need to introduce new constants for the values of the property, they michael@0: must be added to michael@0: nsStyleConsts.h michael@0: and to the appropriate keyword tables in michael@0: nsCSSProps.cpp michael@0: (note: this cookbook does not do this since the new property does not require michael@0: any new keywords for the property values). michael@0:

CSS Declaration

michael@0: Changes will need to be made to the structs and classes defined in michael@0: nsCSSDeclaration.h michael@0: and michael@0: nsCSSDeclaration.cpp michael@0:
michael@0:
michael@0: First, find the declaration of the struct that will hold the new property michael@0: value (in the header file). For this example it is the struct nsCSSUserInterface michael@0: . Modify the struct declaration to include a new data member for the new michael@0: property, of the type CSSValue. Next, open the implementation file (the cpp) michael@0: and modify the struct's constructors.
michael@0:
michael@0: Next, the michael@0: AppendValue michael@0: method must be updated to support your new property. The CSSParser will michael@0: call this to build up a declaration. Find the portion of that method that michael@0: deals with the other properties in the struct that you are adding your property michael@0: to (or create a new section if you are creating a new style struct). For michael@0: this example we will find the 'UserInterface' section and add our new property michael@0: there michael@0: .
michael@0:
    // nsCSSUserInterface
michael@0:     case eCSSProperty_user_input:
michael@0:     case eCSSProperty_user_modify:
michael@0:     case eCSSProperty_user_select:
michael@0:     case eCSSProperty_key_equivalent:
michael@0:     case eCSSProperty_user_focus:
michael@0:     case eCSSProperty_resizer:
michael@0:     case eCSSProperty_cursor:
michael@0:     case eCSSProperty_force_broken_image_icons: {
michael@0:       CSS_ENSURE(UserInterface) {
michael@0:         switch (aProperty) {
michael@0:           case eCSSProperty_user_input:       theUserInterface->mUserInput = aValue;      break;
michael@0:           case eCSSProperty_user_modify:      theUserInterface->mUserModify = aValue;     break;
michael@0:           case eCSSProperty_user_select:      theUserInterface->mUserSelect = aValue;     break;
michael@0:           case eCSSProperty_key_equivalent: 
michael@0:             CSS_ENSURE_DATA(theUserInterface->mKeyEquivalent, nsCSSValueList) {
michael@0:               theUserInterface->mKeyEquivalent->mValue = aValue;
michael@0:               CSS_IF_DELETE(theUserInterface->mKeyEquivalent->mNext);
michael@0:             }
michael@0:             break;
michael@0:           case eCSSProperty_user_focus:       theUserInterface->mUserFocus = aValue;      break;
michael@0:           case eCSSProperty_resizer:          theUserInterface->mResizer = aValue;        break;
michael@0:           case eCSSProperty_cursor:
michael@0:             CSS_ENSURE_DATA(theUserInterface->mCursor, nsCSSValueList) {
michael@0:               theUserInterface->mCursor->mValue = aValue;
michael@0:               CSS_IF_DELETE(theUserInterface->mCursor->mNext);
michael@0:             }
michael@0:             break;
michael@0:           case eCSSProperty_force_broken_image_icons: theUserInterface->mForceBrokenImageIcon = aValue; break;
michael@0: 
michael@0:           CSS_BOGUS_DEFAULT; // make compiler happy
michael@0:         }
michael@0:       }
michael@0:       break;
michael@0:     }
michael@0: 
michael@0: 
michael@0: The GetValue method must be similarly modified michael@0: :
michael@0:
    // nsCSSUserInterface
michael@0:     case eCSSProperty_user_input:
michael@0:     case eCSSProperty_user_modify:
michael@0:     case eCSSProperty_user_select:
michael@0:     case eCSSProperty_key_equivalent:
michael@0:     case eCSSProperty_user_focus:
michael@0:     case eCSSProperty_resizer:
michael@0:     case eCSSProperty_cursor:
michael@0:     case eCSSProperty_force_broken_image_icons: {
michael@0:       CSS_VARONSTACK_GET(UserInterface);
michael@0:       if (nullptr != theUserInterface) {
michael@0:         switch (aProperty) {
michael@0:           case eCSSProperty_user_input:       aValue = theUserInterface->mUserInput;       break;
michael@0:           case eCSSProperty_user_modify:      aValue = theUserInterface->mUserModify;      break;
michael@0:           case eCSSProperty_user_select:      aValue = theUserInterface->mUserSelect;      break;
michael@0:           case eCSSProperty_key_equivalent:
michael@0:             if (nullptr != theUserInterface->mKeyEquivalent) {
michael@0:               aValue = theUserInterface->mKeyEquivalent->mValue;
michael@0:             }
michael@0:             break;
michael@0:           case eCSSProperty_user_focus:       aValue = theUserInterface->mUserFocus;       break;
michael@0:           case eCSSProperty_resizer:          aValue = theUserInterface->mResizer;         break;
michael@0:           case eCSSProperty_cursor:
michael@0:             if (nullptr != theUserInterface->mCursor) {
michael@0:               aValue = theUserInterface->mCursor->mValue;
michael@0:             }
michael@0:             break;
michael@0:           case eCSSProperty_force_broken_image_icons: aValue = theUserInterface->mForceBrokenImageIcons; break;
michael@0: 
michael@0:           CSS_BOGUS_DEFAULT; // make compiler happy
michael@0:         }
michael@0:       }
michael@0:       else {
michael@0:         aValue.Reset();
michael@0:       }
michael@0:       break;
michael@0:     }
michael@0: 
michael@0: 
michael@0: Finally modify michael@0: the 'List' method to output the property value.
michael@0:
void nsCSSUserInterface::List(FILE* out, int32_t aIndent) const
michael@0: {
michael@0:   for (int32_t index = aIndent; --index >= 0; ) fputs("  ", out);
michael@0: 
michael@0:   nsAutoString buffer;
michael@0: 
michael@0:   mUserInput.AppendToString(buffer, eCSSProperty_user_input);
michael@0:   mUserModify.AppendToString(buffer, eCSSProperty_user_modify);
michael@0:   mUserSelect.AppendToString(buffer, eCSSProperty_user_select);
michael@0:   nsCSSValueList*  keyEquiv = mKeyEquivalent;
michael@0:   while (nullptr != keyEquiv) {
michael@0:     keyEquiv->mValue.AppendToString(buffer, eCSSProperty_key_equivalent);
michael@0:     keyEquiv= keyEquiv->mNext;
michael@0:   }
michael@0:   mUserFocus.AppendToString(buffer, eCSSProperty_user_focus);
michael@0:   mResizer.AppendToString(buffer, eCSSProperty_resizer);
michael@0:   
michael@0:   nsCSSValueList*  cursor = mCursor;
michael@0:   while (nullptr != cursor) {
michael@0:     cursor->mValue.AppendToString(buffer, eCSSProperty_cursor);
michael@0:     cursor = cursor->mNext;
michael@0:   }
michael@0: 
michael@0:   mForceBrokenImageIcon.AppendToString(buffer,eCSSProperty_force_broken_image_icons);
michael@0: 
michael@0:   fputs(NS_LossyConvertUTF16toASCII(buffer).get(), out);
michael@0: }
michael@0: 
michael@0: 
michael@0: michael@0:

CSS Parser

michael@0: Next, the CSSParser must be educated about this new property so that it can michael@0: read in the formal declarations and build up the internal declarations that michael@0: will be used to build the rules. If you are adding a simple property that michael@0: takes a single value, you simply add your new property to the ParseSingleProperty michael@0: method. If a more complex parsing is required you will have to write a new michael@0: method to handle it, modeling it off of one of the existing parsing helper michael@0: methods (see michael@0: ParseBackground michael@0: , for and example). We are just adding a simple single-value property here.
michael@0:
michael@0: Open nsCSSParser.cpp and look for the method michael@0: ParseSingleProperty michael@0: . This method is responsible for calling the relevant helper routine to parse michael@0: the value(s). Find an existing property that is similar to the property you michael@0: are adding. For our example we are adding a property that takes a numeric michael@0: value so we will model it after the 'height' property and call ParsePositiveVariant. michael@0: Add a new case for the new property and call the appropriate parser-helper michael@0: and make a call to ParseVariant passing the michael@0: variant flag michael@0: that makes sense for your property. In our case
michael@0:
michael@0:
  case eCSSProperty_force_broken_image_icons:
michael@0:
    return ParsePositiveVariant(aErrorCode, aValue, VARIANT_INTEGER, nullptr);
michael@0: This will parse the value as a positive integer value, which is what we want.
michael@0:
michael@0:

Style Context

michael@0: Having implemented support for the new property in the CSS Parser and CSS michael@0: Declaration classes in the content module, it is now time to provide support michael@0: for the new property in layout. The Style Context must be given a new data michael@0: member corresponding to the declaration's new data member, so the computed michael@0: value can be held for the layout objects to use.
michael@0:
michael@0: First look into michael@0: nsStyleStruct.h michael@0: to see the existing style strucs. Find the one that you want to store the michael@0: data on. In this example, we want to put it on the nsStyleUserInterface struct, michael@0: however there is also a class nsStyleUIReset that holds the non-inherited michael@0: values, so we will use that one (remember, our property is not inherited). michael@0: Add a data member michael@0: to hold the value: michael@0:
struct nsStyleUIReset {
michael@0:   nsStyleUIReset(void);
michael@0:   nsStyleUIReset(const nsStyleUIReset& aOther);
michael@0:   ~nsStyleUIReset(void);
michael@0: 
michael@0:   NS_DEFINE_STATIC_STYLESTRUCTID_ACCESSOR(eStyleStruct_UIReset)
michael@0: 
michael@0:   void* operator new(size_t sz, nsPresContext* aContext) {
michael@0:     return aContext->AllocateFromShell(sz);
michael@0:   }
michael@0:   void Destroy(nsPresContext* aContext) {
michael@0:     this->~nsStyleUIReset();
michael@0:     aContext->FreeToShell(sizeof(nsStyleUIReset), this);
michael@0:   };
michael@0: 
michael@0:   int32_t CalcDifference(const nsStyleUIReset& aOther) const;
michael@0: 
michael@0:   uint8_t   mUserSelect;      // [reset] (selection-style)
michael@0:   PRUnichar mKeyEquivalent;   // [reset] XXX what type should this be?
michael@0:   uint8_t   mResizer;         // [reset]
michael@0:   uint8_t   mForceBrokenImageIcon; // [reset]  (0 if not forcing, otherwise forcing)
michael@0: };
michael@0: 
michael@0: In the implementation file michael@0: nsStyleContext.cpp michael@0: add the new data member to the constructors of the style struct and the CalcDifference michael@0: method, which must return the correct style-change hint when a change to michael@0: your new property is detected. The constructor changes are obvious, but here michael@0: is the CalcDifference change for our example:
michael@0:
int32_t nsStyleUIReset::CalcDifference(const nsStyleUIReset& aOther) const
michael@0: {
michael@0:   if (mForceBrokenImageIcon == aOther.mForceBrokenImageIcon) {
michael@0:     if (mResizer == aOther.mResizer) {
michael@0:       if (mUserSelect == aOther.mUserSelect) {
michael@0:         if (mKeyEquivalent == aOther.mKeyEquivalent) {
michael@0:           return NS_STYLE_HINT_NONE;
michael@0:         }
michael@0:         return NS_STYLE_HINT_CONTENT;
michael@0:       }
michael@0:       return NS_STYLE_HINT_VISUAL;
michael@0:     }
michael@0:     return NS_STYLE_HINT_VISUAL;
michael@0:   }
michael@0:   return NS_STYLE_HINT_FRAMECHANGE;
michael@0: }
michael@0: 
michael@0:

CSSStyleRule

michael@0: The nsCSSStyleRule must be updated to manage mapping the declaration to the michael@0: style struct. In the file michael@0: nsCSSStyleRule.cpp michael@0: , locate the Declaration mapping function corresponding to the style struct michael@0: you have added your property to. For example, we michael@0: update michael@0: MapUIForDeclaration:
michael@0:
static nsresult
michael@0: MapUIForDeclaration(nsCSSDeclaration* aDecl, const nsStyleStructID& aID, nsCSSUserInterface& aUI)
michael@0: {
michael@0:   if (!aDecl)
michael@0:     return NS_OK; // The rule must have a declaration.
michael@0: 
michael@0:   nsCSSUserInterface* ourUI = (nsCSSUserInterface*)aDecl->GetData(kCSSUserInterfaceSID);
michael@0:   if (!ourUI)
michael@0:     return NS_OK; // We don't have any rules for UI.
michael@0: 
michael@0:   if (aID == eStyleStruct_UserInterface) {
michael@0:     if (aUI.mUserFocus.GetUnit() == eCSSUnit_Null && ourUI->mUserFocus.GetUnit() != eCSSUnit_Null)
michael@0:       aUI.mUserFocus = ourUI->mUserFocus;
michael@0:     
michael@0:     if (aUI.mUserInput.GetUnit() == eCSSUnit_Null && ourUI->mUserInput.GetUnit() != eCSSUnit_Null)
michael@0:       aUI.mUserInput = ourUI->mUserInput;
michael@0: 
michael@0:     if (aUI.mUserModify.GetUnit() == eCSSUnit_Null && ourUI->mUserModify.GetUnit() != eCSSUnit_Null)
michael@0:       aUI.mUserModify = ourUI->mUserModify;
michael@0: 
michael@0:     if (!aUI.mCursor && ourUI->mCursor)
michael@0:       aUI.mCursor = ourUI->mCursor;
michael@0: 
michael@0: 
michael@0:   }
michael@0:   else if (aID == eStyleStruct_UIReset) {
michael@0:     if (aUI.mUserSelect.GetUnit() == eCSSUnit_Null && ourUI->mUserSelect.GetUnit() != eCSSUnit_Null)
michael@0:       aUI.mUserSelect = ourUI->mUserSelect;
michael@0:    
michael@0:     if (!aUI.mKeyEquivalent && ourUI->mKeyEquivalent)
michael@0:       aUI.mKeyEquivalent = ourUI->mKeyEquivalent;
michael@0: 
michael@0:     if (aUI.mResizer.GetUnit() == eCSSUnit_Null && ourUI->mResizer.GetUnit() != eCSSUnit_Null)
michael@0:       aUI.mResizer = ourUI->mResizer;
michael@0:     
michael@0:     if (aUI.mForceBrokenImageIcon.GetUnit() == eCSSUnit_Null && ourUI->mForceBrokenImageIcon.GetUnit() == eCSSUnit_Integer)
michael@0:       aUI.mForceBrokenImageIcon = ourUI->mForceBrokenImageIcon;
michael@0:   }
michael@0: 
michael@0:   return NS_OK;
michael@0: 
michael@0: }
michael@0: 
michael@0:

Rule Node

michael@0: Now we have to update the RuleNode code to know about the new property. First, michael@0: locate the PropertyCheckData array for the data that you added the new property michael@0: to. For this example, we add the following:
michael@0:
static const PropertyCheckData UIResetCheckProperties[] = {
michael@0:   CHECKDATA_PROP(nsCSSUserInterface, mUserSelect, CHECKDATA_VALUE, PR_FALSE),
michael@0:   CHECKDATA_PROP(nsCSSUserInterface, mResizer, CHECKDATA_VALUE, PR_FALSE),
michael@0:   CHECKDATA_PROP(nsCSSUserInterface, mKeyEquivalent, CHECKDATA_VALUELIST, PR_FALSE)
michael@0:   CHECKDATA_PROP(nsCSSUserInterface, mForceBrokenImageIcon, CHECKDATA_VALUE, PR_FALSE)
michael@0: };
michael@0: 
michael@0: The first two arguments correspond to the structure and data member from michael@0: the CSSDeclaration, the third is the data type, the fourth indicates michael@0: whether it is a coord value that uses an explicit inherit value on the michael@0: style data struct that must be computed by layout.
michael@0:
michael@0: Next, we have to make sure the ComputeXXX method for the structure the property michael@0: was added to is updated to mange the new value. In this example we need to michael@0: modify the nsRuleNode::ComputeUIResetData method to handle the CSS Declaration michael@0: to the style struct:
michael@0:
  ...
michael@0:   // resizer: auto, none, enum, inherit
michael@0:   if (eCSSUnit_Enumerated == uiData.mResizer.GetUnit()) {
michael@0:     ui->mResizer = uiData.mResizer.GetIntValue();
michael@0:   }
michael@0:   else if (eCSSUnit_Auto == uiData.mResizer.GetUnit()) {
michael@0:     ui->mResizer = NS_STYLE_RESIZER_AUTO;
michael@0:   }
michael@0:   else if (eCSSUnit_None == uiData.mResizer.GetUnit()) {
michael@0:     ui->mResizer = NS_STYLE_RESIZER_NONE;
michael@0:   }
michael@0:   else if (eCSSUnit_Inherit == uiData.mResizer.GetUnit()) {
michael@0:     inherited = PR_TRUE;
michael@0:     ui->mResizer = parentUI->mResizer;
michael@0:   }
michael@0: 
michael@0:   // force-broken-image-icons: integer, inherit, initial
michael@0:   if (eCSSUnit_Integer == uiData.mForceBrokenImageIcons.GetUnit()) {
michael@0:     ui->mForceBrokenImageIcons = uiData.mForceBrokenImageIcons.GetIntValue();
michael@0:   } else if (eCSSUnit_Inherit == uiData.mForceBrokenImageIcons.GetUnit()) {
michael@0:     inherited = PR_TRUE;
michael@0:     ui->mForceBrokenImageIcons = parentUI->mForceBrokenImageIcons;
michael@0:   } else if (eCSSUnit_Initial == uiData.mForceBrokenImageIcons.GetUnit()) {
michael@0:     ui->mForceBrokenImageIcons = 0;
michael@0:   }
michael@0:   
michael@0:   if (inherited)
michael@0:     // We inherited, and therefore can't be cached in the rule node.  We have to be put right on the
michael@0:     // style context.
michael@0:     aContext->SetStyle(eStyleStruct_UIReset, *ui);
michael@0:   else {
michael@0:     // We were fully specified and can therefore be cached right on the rule node.
michael@0:     if (!aHighestNode->mStyleData.mResetData)
michael@0:       aHighestNode->mStyleData.mResetData = new (mPresContext) nsResetStyleData;
michael@0:     aHighestNode->mStyleData.mResetData->mUIData = ui;
michael@0:     // Propagate the bit down.
michael@0:     PropagateDependentBit(NS_STYLE_INHERIT_UI_RESET, aHighestNode);
michael@0:   }
michael@0:   ...
michael@0: 
michael@0:

DOM

michael@0: Users in scripts, or anywhere outside of layout/ or content/ may need to access michael@0: the new property. This is done using the CSS OM, specifically michael@0: nsIDOMCSSStyleDeclaration and CSS2Properties. michael@0: By the magic of C++ pre-processing, the michael@0: CSS2Properties bits will be implemented automatically when you michael@0: add your property to michael@0: nsCSSPropList.h. michael@0:

Layout

michael@0: OK, finally the style system is supporting the new property. It is time to michael@0: actually make use of it now.
michael@0:
michael@0: In layout, retrieve the styleStruct that has the new property from the frame's michael@0: style context. Access the new property and get its value. It is that simple. michael@0: For this example, it looks like this, in nsImageFrame:
michael@0:
        PRBool forceIcon = PR_FALSE;
michael@0: 
michael@0:         if (StyleUIReset()->mForceBrokenImageIcon) {
michael@0:           forceIcon = PR_TRUE;
michael@0:         }
michael@0: 
michael@0: 
michael@0: Create some testcases with style rules that use the new property, make sure michael@0: it is being parsed correctly. Test it in an external stylesheet and in inline michael@0: style. Test that it is inherited correctly, or not inherited as appropriate michael@0: to your property. Update this document with any further details, or correcting michael@0: any errors.
michael@0: