/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ #include "xp.h" #include "jsapi.h" #include "libstyle.h" #include "xp_mcom.h" #include "jsspriv.h" #include "jssrules.h" /* #include "jsscope.h" */ #include "jsatom.h" #include "plhash.h" extern void LO_SetStyleObjectRefs(MWContext *context, void *tags, void *classes, void *ids); /**** Declaration of JavaScript classes ****/ extern JSClass Tags_class; extern JSClass Classes_class; extern JSClass Tag_class; static JSBool is_valid_jsss_prop(char *prop) { if(!prop) return FALSE; switch(XP_TO_UPPER(*prop)) { case 'A': if(!strcasecomp(prop, "absolute")) return TRUE; else if(!strcasecomp(prop, "activeColor")) return TRUE; else if(!strcasecomp(prop, "align")) return TRUE; break; case 'B': if(!strcasecomp(prop, "borderWidth")) return TRUE; else if(!strcasecomp(prop, "borderStyle")) return TRUE; else if(!strcasecomp(prop, "borderColor")) return TRUE; else if(!strcasecomp(prop, "borderRightWidth")) return TRUE; else if(!strcasecomp(prop, "borderLeftWidth")) return TRUE; else if(!strcasecomp(prop, "borderTopWidth")) return TRUE; else if(!strcasecomp(prop, "borderBottomWidth")) return TRUE; else if(!strcasecomp(prop, "backgroundColor")) return TRUE; else if(!strcasecomp(prop, "backgroundImage")) return TRUE; else if(!strcasecomp(prop, "backgroundRepeat")) return TRUE; break; case 'C': if(!strcasecomp(prop, "clear")) return TRUE; else if(!strcasecomp(prop, "color")) return TRUE; else if(!strcasecomp(prop, "clip")) return TRUE; break; case 'D': if(!strcasecomp(prop, "display")) return TRUE; break; case 'F': if(!strcasecomp(prop, "fontSize")) return TRUE; else if(!strcasecomp(prop, "fontFamily")) return TRUE; else if(!strcasecomp(prop, "fontWeight")) return TRUE; else if(!strcasecomp(prop, "fontStyle")) return TRUE; break; case 'H': if(!strcasecomp(prop, "height")) return TRUE; break; case 'I': if(!strcasecomp(prop, "includeSource")) return TRUE; break; case 'L': if(!strcasecomp(prop, "lineHeight")) return TRUE; else if(!strcasecomp(prop, "listStyleType")) return TRUE; else if(!strcasecomp(prop, "layerBackgroundColor")) return TRUE; else if(!strcasecomp(prop, "layerBackgroundImage")) return TRUE; else if(!strcasecomp(prop, "linkColor")) return TRUE; else if(!strcasecomp(prop, "linkBorder")) return TRUE; else if(!strcasecomp(prop, "_layer_width")) return TRUE; else if(!strcasecomp(prop, "left")) return TRUE; break; case 'M': if(!strcasecomp(prop, "marginLeft")) return TRUE; else if(!strcasecomp(prop, "marginRight")) return TRUE; else if(!strcasecomp(prop, "marginTop")) return TRUE; else if(!strcasecomp(prop, "marginBottom")) return TRUE; break; case 'O': if(!strcasecomp(prop, "overflow")) return TRUE; break; case 'P': if(!strcasecomp(prop, "padding")) return TRUE; else if(!strcasecomp(prop, "paddingLeft")) return TRUE; else if(!strcasecomp(prop, "paddingRight")) return TRUE; else if(!strcasecomp(prop, "paddingTop")) return TRUE; else if(!strcasecomp(prop, "paddingBottom")) return TRUE; else if(!strcasecomp(prop, "position")) return TRUE; else if(!strcasecomp(prop, "pageBreakBefore")) return TRUE; else if(!strcasecomp(prop, "pageBreakAfter")) return TRUE; break; case 'R': if(!strcasecomp(prop, "relative")) return TRUE; break; case 'T': if(!strcasecomp(prop, "top")) return TRUE; else if(!strcasecomp(prop, "textTransform")) return TRUE; else if(!strcasecomp(prop, "textAlign")) return TRUE; else if(!strcasecomp(prop, "textIndent")) return TRUE; else if(!strcasecomp(prop, "textDecoration")) return TRUE; break; case 'V': if(!strcasecomp(prop, "visitedColor")) return TRUE; else if(!strcasecomp(prop, "verticalAlign")) return TRUE; else if(!strcasecomp(prop, "visibility")) return TRUE; break; case 'W': if(!strcasecomp(prop, "width")) return TRUE; if(!strcasecomp(prop, "whiteSpace")) return TRUE; break; case 'Z': if(!strcasecomp(prop, "zIndex")) return TRUE; default: XP_ASSERT(0); break; } return FALSE; }; int PR_CALLBACK jss_CompareStringsNoCase(const void *str1, const void *str2) { return XP_STRCASECMP(str1, str2) == 0; } PRHashNumber PR_CALLBACK jss_HashStringNoCase(const void *key) { PRHashNumber h; const unsigned char *s; h = 0; for (s = key; *s; s++) h = (h >> 28) ^ (h << 4) ^ toupper(*s); return h; } /**** Finalizer for JSSTags and JSSClasses ****/ int PR_CALLBACK jss_DestroyTags(PRHashEntry *he, int i, void *arg) { XP_ASSERT(he->value); if (he->value) jss_DestroyTag((StyleTag *)he->value); return HT_ENUMERATE_NEXT; /* keep enumerating */ } void PR_CALLBACK jss_FinalizeStyleObject(JSContext *mc, JSObject *obj) { StyleObject *tags; XP_ASSERT(JS_InstanceOf(mc, obj, &Tags_class, 0) || JS_InstanceOf(mc, obj, &Classes_class, 0)); /* Note: the prototype objects won't have any private data */ tags = JS_GetPrivate(mc, obj); if (tags) { XP_ASSERT(tags->type >= JSSTags && tags->type <= JSSClass); if (tags->table) { if (tags->type != JSSClasses) PR_HashTableEnumerateEntries(tags->table, jss_DestroyTags, 0); PR_HashTableDestroy(tags->table); } if (tags->name) XP_FREE(tags->name); XP_FREE(tags); } } /**** Implementation of JavaScript JSSTags classes ****/ /* Constructor for JSSTags class */ JSBool PR_CALLBACK TagsConstructor (JSContext *mc, JSObject *obj, unsigned argc, jsval *argv, jsval *rval) { /* Check arguments first */ XP_ASSERT(JS_InstanceOf(mc, obj, &Tags_class, 0)); /* We don't expect any arguments */ XP_ASSERT(argc == 0); return JS_TRUE; } /* Helper routine to determine the specificity of a tag */ static uint32 jss_TagSpecificity(StyleObject *obj) { /* Compute the specificity for a tag of this type */ switch (obj->type) { case JSSTags: return JSS_SPECIFICITY(1, 0, 0); case JSSIds: return JSS_SPECIFICITY(0, 0, 1); case JSSClasses: XP_ASSERT(FALSE); break; case JSSClass: XP_ASSERT(obj->name); if (obj->name && XP_STRCMP(obj->name, "all") == 0) return JSS_SPECIFICITY(0, 1, 0); else return JSS_SPECIFICITY(1, 1, 0); break; } return 0; } /* Called by JS to resolve names */ JSBool PR_CALLBACK Tags_ResolveName(JSContext *mc, JSObject *obj, jsval id) { if (JSVAL_IS_STRING(id)) { char *name = JS_GetStringBytes(JSVAL_TO_STRING(id)); JSObject *tag_obj; StyleObject *tags; StyleTag *tag; XP_ASSERT(JS_InstanceOf(mc, obj, &Tags_class, 0)); /* Get the pointer to the hash table */ tags = JS_GetPrivate(mc, obj); if (!tags) return JS_TRUE; /* we must be getting called before we've been initialized */ /* * See if there is an existing StyleTag object with this name (all names * are case insensitive like in CSS) */ if (tags->table) { PRHashEntry *he, **hep; hep = PR_HashTableRawLookup(tags->table, tags->table->keyHash(name), name); if ((he = *hep) != 0) { /* Alias this name with the original name */ JS_AliasProperty(mc, obj, (const char *)he->key, name); return JS_TRUE; } } /* Lazily create tag objects when needed */ tag_obj = JS_NewObject(mc, &Tag_class, 0, obj); if (!tag_obj) return JS_FALSE; /* Create a new StyleTag structure */ tag = jss_NewTag(name); if (!tag) { JS_ReportOutOfMemory(mc); return JS_FALSE; } JS_SetPrivate(mc, tag_obj, tag); /* We don't create the hash table until it's actually needed */ if (!tags->table) { /* All lookups must be case insensitive */ tags->table = PR_NewHashTable(8, (PRHashFunction)jss_HashStringNoCase, (PRHashComparator)jss_CompareStringsNoCase, PR_CompareValues, 0, 0); if (!tags->table) { jss_DestroyTag(tag); JS_ReportOutOfMemory(mc); return JS_FALSE; } } /* Add this tag to the hash table */ PR_HashTableAdd(tags->table, (const void *)tag->name, tag); tag->specificity = jss_TagSpecificity(tags); /* Give the object a name */ return JS_DefineProperty(mc, obj, name, OBJECT_TO_JSVAL(tag_obj), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); } return JS_TRUE; } /**** Implementation of JavaScript JSSClasses class ****/ /* Constructor for JSSClasses class */ JSBool PR_CALLBACK ClassesConstructor (JSContext *mc, JSObject *obj, unsigned argc, jsval *argv, jsval *rval) { /* Check arguments first */ XP_ASSERT(JS_InstanceOf(mc, obj, &Classes_class, 0)); /* We don't expect any arguments */ XP_ASSERT(argc == 0); return JS_TRUE; } /* Called by JS to resolve names */ JSBool PR_CALLBACK Classes_ResolveName(JSContext *mc, JSObject *obj, jsval id) { if (JSVAL_IS_STRING(id)) { char* name = JS_GetStringBytes(JSVAL_TO_STRING(id)); StyleObject *classes; JSObject* tags_obj; StyleObject *tags; /* Get the pointer to the hash table */ classes = JS_GetPrivate(mc, obj); if (!classes) return JS_TRUE; /* we must be getting called before we've been initialized */ /* * See if there is an existing StyleObject object with this name (all names * are case insensitive like in CSS) */ if (classes->table) { PRHashEntry *he, **hep; hep = PR_HashTableRawLookup(classes->table, classes->table->keyHash(name), name); if ((he = *hep) != 0) { /* Alias this name with the original name */ JS_AliasProperty(mc, obj, (const char *)he->key, name); return JS_TRUE; } } /* Create an instance of the "Tags" class for the specified class name */ tags_obj = JS_NewObject(mc, &Tags_class, 0, obj); if (!tags_obj) { JS_ReportOutOfMemory(mc); return JS_FALSE; } /* Create a StyleObject data structure */ tags = (StyleObject *)XP_CALLOC(1, sizeof(StyleObject)); if (!tags) { JS_ReportOutOfMemory(mc); return JS_FALSE; } /* Make a copy of the name */ tags->name = XP_STRDUP(name); if (!tags->name) { XP_FREE(tags); JS_ReportOutOfMemory(mc); return JS_FALSE; } /* We don't create the hash table until it's actually needed */ if (!classes->table) { /* All lookups must be case insensitive */ classes->table = PR_NewHashTable(8, (PRHashFunction)jss_HashStringNoCase, (PRHashComparator)jss_CompareStringsNoCase, PR_CompareValues, 0, 0); if (!classes->table) { if (tags->name) XP_FREE(tags->name); XP_FREE(tags); JS_ReportOutOfMemory(mc); return JS_FALSE; } } /* Add it to the hash table */ PR_HashTableAdd(classes->table, (const void *)tags->name, tags); tags->type = JSSClass; JS_SetPrivate(mc, tags_obj, tags); return JS_DefineProperty(mc, obj, name, OBJECT_TO_JSVAL(tags_obj), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); } return JS_TRUE; } /**** Implementation of JavaScript JSSTag class ****/ /* Constructor for JSSTag class */ JSBool PR_CALLBACK TagConstructor (JSContext *mc, JSObject *obj, unsigned argc, jsval *argv, jsval *rval) { /* Check arguments first */ XP_ASSERT(JS_InstanceOf(mc, obj, &Tag_class, 0)); /* We don't expect any arguments */ XP_ASSERT(argc == 0); return JS_TRUE; } /* Destroys a list of properties */ void jss_DestroyProperties(StyleProperty *p) { StyleProperty *next; while (p) { next = p->next; /* Free the name */ if (p->name) XP_FREE(p->name); /* If the value is a string then free it, too */ if (p->tag == JSVAL_STRING) { XP_ASSERT(p->u.strVal); if (p->u.strVal) XP_FREE(p->u.strVal); } XP_FREE(p); p = next; } } static JSBool jss_PropertySetValue(JSContext *mc, StyleProperty *prop, jsval *vp) { JSString *str; if (JSVAL_IS_BOOLEAN(*vp)) { prop->u.bVal = JSVAL_TO_BOOLEAN(*vp); } else if (JSVAL_IS_INT(*vp)) { prop->u.nVal = JSVAL_TO_INT(*vp); /* XXX - JSVAL_TAG doesn't do the right thing for ints */ prop->tag = JSVAL_INT; return JS_TRUE; } else if (JSVAL_IS_DOUBLE(*vp)) { prop->u.dVal = *JSVAL_TO_DOUBLE(*vp); } else { XP_ASSERT(JSVAL_IS_STRING(*vp)); str = JSVAL_TO_STRING(*vp); prop->u.strVal = XP_STRDUP(JS_GetStringBytes(str)); } prop->tag = JSVAL_TAG(*vp); return JS_TRUE; } static JSBool jss_PropertyGetValue(JSContext *mc, StyleProperty *prop, jsval *vp) { switch (prop->tag) { case JSVAL_BOOLEAN: *vp = BOOLEAN_TO_JSVAL(prop->u.bVal); break; case JSVAL_STRING: *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(mc, prop->u.strVal)); break; case JSVAL_INT: *vp = INT_TO_JSVAL(prop->u.nVal); break; case JSVAL_DOUBLE: JS_NewDoubleValue(mc, prop->u.dVal, vp); break; default: XP_ASSERT(FALSE); break; } return JS_TRUE; } static StyleProperty * jss_TagGetProperty(JSContext *mc, StyleTag *tag, char *propname) { StyleProperty *prop; /* Look for the property in our list of properties */ for (prop = tag->properties; prop; prop = prop->next) { if (XP_STRCASECMP(prop->name, propname) == 0) return prop; } return 0; } static JSBool jss_TagAddProperty(JSContext *mc, StyleTag *tag, char *name, jsval *vp) { StyleProperty *prop; /* Create a new property */ prop = (StyleProperty *)XP_CALLOC(1, sizeof(StyleProperty)); if (!prop) { JS_ReportOutOfMemory(mc); return JS_FALSE; } /* Set the name and property value */ prop->name = XP_STRDUP(name); if (!prop->name) { XP_FREE(prop); JS_ReportOutOfMemory(mc); return JS_FALSE; } jss_PropertySetValue(mc, prop, vp); /* Add it to the list */ prop->next = tag->properties; tag->properties = prop; return JS_TRUE; } /* Creates a new property if necessary */ static JSBool jss_TagSetProperty(JSContext *mc, StyleTag *tag, char *name, jsval *vp) { StyleProperty *prop; /* See if we already have the property defined */ prop = jss_TagGetProperty(mc, tag, name); if (prop) jss_PropertySetValue(mc, prop, vp); else jss_TagAddProperty(mc, tag, name, vp); return JS_TRUE; } /* Called by JS so we can handle the get operation */ JSBool PR_CALLBACK Tag_GetProperty(JSContext *mc, JSObject *obj, jsval id, jsval *vp) { if (JSVAL_IS_STRING(id)) { char *name = JS_GetStringBytes(JSVAL_TO_STRING(id)); StyleTag *tag = JS_GetPrivate(mc, obj); StyleProperty *prop; if(!tag) return JS_TRUE; LO_LockLayout(); prop = jss_TagGetProperty(mc, tag, name); if (prop) jss_PropertyGetValue(mc, prop, vp); LO_UnlockLayout(); } return JS_TRUE; } /* Called by JS so we can handle the set operation */ JSBool PR_CALLBACK Tag_SetProperty(JSContext *mc, JSObject *obj, jsval id, jsval *vp) { /* We only support number, strings, and booleans */ if (!JSVAL_IS_NUMBER(*vp) && !JSVAL_IS_BOOLEAN(*vp) && !JSVAL_IS_STRING(*vp)) return JS_TRUE; if (JSVAL_IS_STRING(id)) { char *name = JS_GetStringBytes(JSVAL_TO_STRING(id)); StyleTag *tag = JS_GetPrivate(mc, obj); if (tag) { LO_LockLayout(); jss_TagSetProperty(mc, tag, name, vp); LO_UnlockLayout(); } } return JS_TRUE; } /* Called by JS to resolve names */ JSBool PR_CALLBACK Tag_ResolveName(JSContext *mc, JSObject *obj, jsval id) { if (JSVAL_IS_STRING(id)) { char *name = JS_GetStringBytes(JSVAL_TO_STRING(id)); /* * We need to have a resolve function for the case where there's a "with" clause, e.g. * "with (tags.H1) {color = 'red'}". In this case we need to resolve the property or * our setter function won't get called */ if(is_valid_jsss_prop(name)) return JS_DefineProperty(mc, obj, name, JSVAL_VOID, 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT); } return JS_TRUE; } /* Grouping syntax method for setting all the margins at once */ JSBool PR_CALLBACK Tag_Margin(JSContext *mc, JSObject *obj, unsigned argc, jsval *argv, jsval *rval) { StyleTag *tag = JS_GetPrivate(mc, obj); /* * We expect between 1 and 4 arguments. We'll fail if there isn't at least * one and silently ignore anything more than 4 */ if (argc == 0) { JS_ReportError(mc, "Function margin() requires at least one argument."); return JS_FALSE; } /* * The arguments apply to top, right, bottom, and left in that order. If there is * only one argument it applies to all sides, if there are two or three, the missing * values are taken from the opposite side */ if (tag) { jsval *vp; LO_LockLayout(); jss_TagSetProperty(mc, tag, "marginTop", argv); jss_TagSetProperty(mc, tag, "marginRight", argc >= 2 ? &argv[1] : argv); jss_TagSetProperty(mc, tag, "marginBottom", argc >= 3 ? &argv[2] : argv); if (argc == 4) vp = &argv[3]; else if (argc >= 2) vp = &argv[1]; else vp = argv; jss_TagSetProperty(mc, tag, "marginLeft", vp); LO_UnlockLayout(); } return JS_TRUE; } /* Grouping syntax method for setting all the paddings at once */ JSBool PR_CALLBACK Tag_Padding(JSContext *mc, JSObject *obj, unsigned argc, jsval *argv, jsval *rval) { StyleTag *tag = JS_GetPrivate(mc, obj); /* * We expect between 1 and 4 arguments. We'll fail if there isn't at least * one and silently ignore anything more than 4 */ if (argc == 0) { JS_ReportError(mc, "Function padding() requires at least one argument."); return JS_FALSE; } /* * The arguments apply to top, right, bottom, and left in that order. If there is * only one argument it applies to all sides, if there are two or three, the missing * values are taken from the opposite side */ if (tag) { jsval *vp; LO_LockLayout(); jss_TagSetProperty(mc, tag, "paddingTop", argv); jss_TagSetProperty(mc, tag, "paddingRight", argc >= 2 ? &argv[1] : argv); jss_TagSetProperty(mc, tag, "paddingBottom", argc >= 3 ? &argv[2] : argv); if (argc == 4) vp = &argv[3]; else if (argc >= 2) vp = &argv[1]; else vp = argv; jss_TagSetProperty(mc, tag, "paddingLeft", vp); LO_UnlockLayout(); } return JS_TRUE; } /* Grouping syntax method for setting all the border widths at once */ JSBool PR_CALLBACK Tag_BorderWidth(JSContext *mc, JSObject *obj, unsigned argc, jsval *argv, jsval *rval) { StyleTag *tag = JS_GetPrivate(mc, obj); /* * We expect between 1 and 4 arguments. We'll fail if there isn't at least * one and silently ignore anything more than 4 */ if (argc == 0) { JS_ReportError(mc, "Function borderWidth() requires at least one argument."); return JS_FALSE; } XP_ASSERT(tag); /* * The arguments apply to top, right, bottom, and left in that order. If there is * only one argument it applies to all sides, if there are two or three, the missing * values are taken from the opposite side */ if (tag) { jsval *vp; LO_LockLayout(); jss_TagSetProperty(mc, tag, "borderTopWidth", argv); jss_TagSetProperty(mc, tag, "borderRightWidth", argc >= 2 ? &argv[1] : argv); jss_TagSetProperty(mc, tag, "borderBottomWidth", argc >= 3 ? &argv[2] : argv); if (argc == 4) vp = &argv[3]; else if (argc >= 2) vp = &argv[1]; else vp = argv; jss_TagSetProperty(mc, tag, "borderLeftWidth", vp); LO_UnlockLayout(); } return JS_TRUE; } /* Creates a new StyleTag structure */ StyleTag * jss_NewTag(char *name) { StyleTag *tag = (StyleTag *)XP_CALLOC(1, sizeof(StyleTag)); if (!tag) return 0; /* Make a copy of the name */ if (name) { tag->name = XP_STRDUP(name); if (!tag->name) { XP_FREE(tag); return 0; } } return tag; } /* Destroys a StyleTag structure */ void jss_DestroyTag(StyleTag *tag) { if (tag->name) XP_FREE(tag->name); jss_DestroyProperties(tag->properties); jss_DestroyRules(tag->rules); XP_FREE(tag); } /* JS function for creating contextual selectors */ JSBool PR_CALLBACK jss_Contextual(JSContext *mc, JSObject *obj, unsigned argc, jsval *argv, jsval *rval) { /* We expect at least 1 argument */ if (argc == 0) { JS_ReportError(mc, "Function contextual() requires at least one argument."); return JS_FALSE; } /* If there's just one argument, then this is really just a simple selector */ if (argc == 1) { *rval = argv[0]; /* copy the argument to the result */ } else { StyleTag *tag; JSObject *tag_obj; StyleRule *rule; unsigned i; /* * Each StyleTag has a "rules" member where we store all contextual selectors that * have that instance as the last simple selector in the list, i.e. the contextual * selector "contextual(tags.UL, tags.OL)" would be stored in the "tags.OL" object * * Validate each of the arguments, and make sure it's a JavaScript object of * type JSSTag */ for (i = 0; i < argc; i++) { /* We expect each simple selector to be a JavaScript object */ if (!JSVAL_IS_OBJECT(argv[i]) || !JS_InstanceOf(mc, (JSObject *)argv[i], &Tag_class, 0)) { /* XXX - don't report error! */ JS_ReportError(mc, "Invalid argument number '%u' in call to contextual().", i); return JS_FALSE; } } tag = JS_GetPrivate(mc, (JSObject *)argv[argc - 1]); if(!tag) return JS_TRUE; LO_LockLayout(); /* Look and see if there's already a rule for this selector */ rule = jss_LookupRule(mc, tag->rules, argc, argv); if (!rule) { /* Create a new rule */ rule = jss_NewRule(mc, argc, argv); if (!rule) { LO_UnlockLayout(); return JS_FALSE; } /* Add it to the list of existing rules */ rule->next = tag->rules; tag->rules = rule; } LO_UnlockLayout(); /* Create a new JavaScript object */ tag_obj = JS_NewObject(mc, &Tag_class, 0, 0); if (!tag_obj) { JS_ReportOutOfMemory(mc); return JS_FALSE; } JS_SetPrivate(mc, tag_obj, rule); *rval = OBJECT_TO_JSVAL(tag_obj); } return JS_TRUE; } static void jss_TagAddStyleProperties(StyleTag *tag, StyleStruct *style) { StyleProperty *prop; for (prop = tag->properties; prop; prop = prop->next) { SS_Number *num; switch (prop->tag) { case JSVAL_BOOLEAN: STYLESTRUCT_SetString(style, prop->name, prop->u.bVal ? "true" : "false", (int32)tag->specificity); break; case JSVAL_STRING: STYLESTRUCT_SetString(style, prop->name, prop->u.strVal, (int32)tag->specificity); break; case JSVAL_INT: num = STYLESTRUCT_NewSSNumber(style, (double)prop->u.nVal, ""); STYLESTRUCT_SetNumber(style, prop->name, num, (int32)tag->specificity); STYLESTRUCT_FreeSSNumber(style, num); break; case JSVAL_DOUBLE: num = STYLESTRUCT_NewSSNumber(style, prop->u.dVal, ""); STYLESTRUCT_SetNumber(style, prop->name, num, (int32)tag->specificity); STYLESTRUCT_FreeSSNumber(style, num); break; default: XP_ASSERT(FALSE); break; } } } /* * This routine will find all the selectors that apply and add their * properties to the style struct */ static void jss_AddMatchingSelectors(JSSContext *jc, StyleTag *tag, StyleAndTagStack *styleStack) { /* * Iterate over each of the rules and for each one that applies add * its properties */ if (tag->rules) { jss_EnumApplicableRules(jc, tag->rules, styleStack, (RULECALLBACK)jss_TagAddStyleProperties, STYLESTACK_GetStyleByIndex(styleStack, 0)); } } /* * This routine is called to retrieve the list of style properties for the current * tag (tag at the top of the tag stack) */ XP_Bool jss_GetStyleForTopTag(StyleAndTagStack *styleStack) { TagStruct *tag; StyleStruct *style; JSSContext jc; tag = STYLESTACK_GetTagByIndex(styleStack, 0); style = STYLESTACK_GetStyleByIndex(styleStack, 0); XP_ASSERT(tag && style); /* Get the top-level hash tables */ XP_MEMSET(&jc, 0, sizeof(JSSContext)); sml_GetJSSContext(styleStack, &jc); /* * Find all the rules that apply and add their declarations. We pass * the specificity along and the style stack decides whether to use the * declaration. This way we avoid having to sort the declarations * * Start with document.ids */ if (tag->id && jc.ids && jc.ids->table) { StyleTag *jsstag = (StyleTag *)PR_HashTableLookup(jc.ids->table, tag->id); if (jsstag) { jss_TagAddStyleProperties(jsstag, style); jss_AddMatchingSelectors(&jc, jsstag, styleStack); } } /* Now do document.classes */ if (tag->class_name && jc.classes && jc.classes->table) { StyleObject *jsscls = (StyleObject *)PR_HashTableLookup(jc.classes->table, tag->class_name); if (jsscls && jsscls->table) { StyleTag *jsstag; /* First we check all elements of the class, e.g. classes.punk.all */ jsstag = (StyleTag *)PR_HashTableLookup(jsscls->table, "all"); if (jsstag) { jss_TagAddStyleProperties(jsstag, style); jss_AddMatchingSelectors(&jc, jsstag, styleStack); } /* Now check for the specified tag */ jsstag = (StyleTag *)PR_HashTableLookup(jsscls->table, tag->name); if (jsstag) { jss_TagAddStyleProperties(jsstag, style); jss_AddMatchingSelectors(&jc, jsstag, styleStack); } } } /* Last we do document.tags */ if (tag->name && jc.tags && jc.tags->table) { StyleTag *jsstag = (StyleTag *)PR_HashTableLookup(jc.tags->table, tag->name); if (jsstag) { jss_TagAddStyleProperties(jsstag, style); jss_AddMatchingSelectors(&jc, jsstag, styleStack); } } return JS_TRUE; } /* Grouping syntax methods */ static JSFunctionSpec tag_groupingMethods[] = { {"margins", Tag_Margin, 1}, {"paddings", Tag_Padding, 1}, {"borderWidths", Tag_BorderWidth, 1}, {0} }; /* * This routine is called to resolve names for the document object */ JSBool JSS_ResolveDocName(JSContext *mc, MWContext *context, JSObject *obj, jsval id) { if (JSVAL_IS_STRING(id)) { char *name = JS_GetStringBytes(JSVAL_TO_STRING(id)); if (XP_STRCMP(name, "tags") == 0 || XP_STRCMP(name, "classes") == 0 || XP_STRCMP(name, "ids") == 0 || XP_STRCMP(name, "contextual") == 0) { JSObject *winobj = JS_GetParent(mc, obj); JSObject *tags_obj, *classes_obj, *ids_obj; StyleObject *tags, *classes, *ids; /* Define the JS "JSSTags" class which is a container for "JSSTag" objects */ if (!JS_InitClass(mc, winobj, NULL, &Tags_class, TagsConstructor, 2, 0, 0, 0, 0)) goto out_of_memory; /* Define an instances "tags" and bind it to the document */ tags_obj = JS_DefineObject(mc, obj, "tags", &Tags_class, 0, JSPROP_READONLY | JSPROP_PERMANENT); if (!tags_obj) goto out_of_memory; /* Define an instances "ids" and bind it to the document */ ids_obj = JS_DefineObject(mc, obj, "ids", &Tags_class, 0, JSPROP_READONLY | JSPROP_PERMANENT); if (!ids_obj) goto out_of_memory; /* Define the JS "JSSClasses" class which is a container for "JSSTags" objects */ if (!JS_InitClass(mc, winobj, NULL, &Classes_class, ClassesConstructor, 2, 0, 0, 0, 0)) goto out_of_memory; /* Define an instances "classes" and bind it to the document */ classes_obj = JS_DefineObject(mc, obj, "classes", &Classes_class, 0, JSPROP_READONLY | JSPROP_PERMANENT); if (!classes_obj) goto out_of_memory; /* Define our C data structures */ tags = (StyleObject *)XP_CALLOC(1, sizeof(StyleObject)); if (!tags) goto out_of_memory; tags->type = JSSTags; classes = (StyleObject *)XP_CALLOC(1, sizeof(StyleObject)); if (!classes) goto out_of_memory; classes->type = JSSClasses; ids = (StyleObject *)XP_CALLOC(1, sizeof(StyleObject)); if (!ids) goto out_of_memory; ids->type = JSSIds; /* Add them to the style stack so we can get at them later */ LO_SetStyleObjectRefs(context, tags, classes, ids); /* We need to be able to get to the C data structures from the JavaScript objects */ JS_SetPrivate(mc, tags_obj, tags); JS_SetPrivate(mc, classes_obj, classes); JS_SetPrivate(mc, ids_obj, ids); /* Define the JS "JSSTag" class and some grouping syntax methods */ if (!JS_InitClass(mc, winobj, NULL, &Tag_class, TagConstructor, 2, 0, tag_groupingMethods, 0, 0)) goto out_of_memory; /* Define the function for creating contextual selectors */ JS_DefineFunction(mc, obj, "contextual", jss_Contextual, 1, 0); } } return JS_TRUE; out_of_memory: JS_ReportOutOfMemory(mc); return JS_FALSE; } /**** Definition of JavaScript classes ****/ /* * This is the JS "JSSTags" class. This class is a container for "JSSTag" * objects (see below). There are two main instance of this class: "document.tags" * and "document.ids" */ JSClass Tags_class = { "JSSTags", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, Tags_ResolveName, JS_ConvertStub, jss_FinalizeStyleObject }; /* * This is the JS "JSSClasses" class. This class is a container for "JSSTags" * objects which in turn contain the actual tags. There is one instance of this * class: "document.classes" */ JSClass Classes_class = { "JSSClasses", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, Classes_ResolveName, JS_ConvertStub, jss_FinalizeStyleObject }; /* * This is the JS "JSSTag" class. There can be as many instances of this class * as there are HTML tags. JSS declarations are represented as properties */ JSClass Tag_class = { "JSSTag", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, Tag_GetProperty, Tag_SetProperty, JS_EnumerateStub, Tag_ResolveName, JS_ConvertStub, JS_FinalizeStub };