/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* * DOM Style information, using JSSS. */ #include "domstyle.h" #include "dom_priv.h" #ifdef DEBUG_shaver /* #define DEBUG_shaver_style_primitives 1 */ #endif #define STYLE_DB_FROM_CX(db, cx) \ PR_BEGIN_MACRO \ (db) = DOM_StyleDatabaseFromContext(cx); \ if (!db) \ return JS_FALSE; \ PR_END_MACRO /* if either is present, they must both be and match */ #define PSEUDO_MATCHES(p1, p2) ((p1 || p2) ? \ (p1 && p2 && !XP_STRCMP(p1, p2)) : \ JS_TRUE) static int CompareSelectors(const void *v1, const void *v2) { DOM_StyleSelector *s1 = (DOM_StyleSelector *)v1, *s2 = (DOM_StyleSelector *)v2; return s1->type == s2->type && !XP_STRCMP(s1->selector, s2->selector) && /* if both have a psuedo, they have to match, else both must be NULL */ (s1->pseudo && s2->pseudo ? XP_STRCMP(s1->pseudo, s2->pseudo) : !s1->pseudo && !s2->pseudo); } DOM_StyleDatabase * DOM_NewStyleDatabase(JSContext *cx) { DOM_StyleDatabase *db = XP_NEW_ZAP(DOM_StyleDatabase); if (!db) return NULL; db->ht = PL_NewHashTable(32, PL_HashString, PL_CompareStrings, CompareSelectors, NULL, NULL); if (!db->ht) { XP_FREE(db); return NULL; } return db; } void DOM_DestroyStyleDatabase(JSContext *cx, DOM_StyleDatabase *db) { PL_HashTableDestroy(db->ht); XP_FREE(db); } #ifdef MOZILLA_CLIENT #include "lo_ele.h" #include "structs.h" #include "layout.h" #include "laystyle.h" #include "proto.h" #define IMAGE_DEF_ANCHOR_BORDER 2 #define IMAGE_DEF_VERTICAL_SPACE 0 DOM_StyleDatabase * DOMMOZ_NewStyleDatabase(JSContext *cx, lo_DocState *state) { DOM_StyleDatabase *db; LO_Color visitCol, linkCol; DOM_StyleSelector *sel, *imgsel; DOM_AttributeEntry *entry; lo_TopState *top = state->top_state; /* * Install default rules. * In an ideal world (perhaps 5.0?), we would parse .netscape/ua.css * at startup and keep the JSSS style buffer around for execution * right here. That would be very cool in many ways, including the * fact that people could have ua.css at all. We might want to * make the weighting stuff work correctly at the same time, too, * but I don't think it's vital. */ db = DOM_NewStyleDatabase(cx); if (!db) return NULL; linkCol.red = STATE_UNVISITED_ANCHOR_RED(state); linkCol.green = STATE_UNVISITED_ANCHOR_GREEN(state); linkCol.blue = STATE_UNVISITED_ANCHOR_BLUE(state); visitCol.red = STATE_VISITED_ANCHOR_RED(state); visitCol.green = STATE_VISITED_ANCHOR_GREEN(state); visitCol.blue = STATE_VISITED_ANCHOR_BLUE(state); top->style_db = db; sel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_TAG, "A", NULL, "link"); if (!sel) goto error; #define SET_DEFAULT_VALUE(name, value) \ entry = DOM_StyleAddRule(cx, db, sel, name, "default"); \ if (!entry) \ goto error; \ entry->dirty = JS_FALSE; \ entry->data = value; /* A:link { color:prefLinkColor } */ SET_DEFAULT_VALUE(COLOR_STYLE, *(uint32*)&linkCol); /* A:link { text-decoration:underline } */ if (lo_underline_anchors() && !DOM_StyleAddRule(cx, db, sel, TEXTDECORATION_STYLE, "underline")) goto error; sel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_TAG, "A", NULL, "visited"); if (!sel) goto error; /* A:visited { color:prefVisitedLinkColor } */ SET_DEFAULT_VALUE(COLOR_STYLE, *(uint32*)&visitCol); /* A:visited { text-decoration:underline } */ if (lo_underline_anchors() && !DOM_StyleAddRule(cx, db, sel, TEXTDECORATION_STYLE, "underline")) goto error; /* set styles for IMG within A:link and A:visited */ /* XXX should set (and teach layout about) borderTop/Bottom, etc. */ imgsel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_TAG, "IMG", NULL, NULL); if (!imgsel) goto error; /* set border styles for ``A:link IMG'' */ sel = DOM_StyleFindSelectorFull(cx, db, imgsel, SELECTOR_TAG, "A", NULL, "link"); if (!sel) goto error; SET_DEFAULT_VALUE(BORDERWIDTH_STYLE, IMAGE_DEF_ANCHOR_BORDER); SET_DEFAULT_VALUE(PADDING_STYLE, IMAGE_DEF_VERTICAL_SPACE); /* set border styles for ``A:visited IMG'' */ sel = DOM_StyleFindSelectorFull(cx, db, imgsel, SELECTOR_TAG, "A", NULL, "visited"); if (!sel) goto error; SET_DEFAULT_VALUE(BORDERWIDTH_STYLE, IMAGE_DEF_ANCHOR_BORDER); SET_DEFAULT_VALUE(PADDING_STYLE, IMAGE_DEF_VERTICAL_SPACE); #ifdef DEBUG_shaver fprintf(stderr, "successfully added all default rules\n"); #endif return db; error: if (db) DOM_DestroyStyleDatabase(cx, db); return NULL; } DOM_StyleDatabase * DOM_StyleDatabaseFromContext(JSContext *cx) { MochaDecoder *decoder; lo_TopState *top; lo_DocState *state; DOM_StyleDatabase *db = NULL; if (!cx) return NULL; decoder = JS_GetPrivate(cx, JS_GetGlobalObject(cx)); if (!decoder) return NULL; LO_LockLayout(); top = lo_FetchTopState(decoder->window_context->doc_id); if (!top) goto out; if (top->style_db) { LO_UnlockLayout(); return (DOM_StyleDatabase *)top->style_db; } state = top->doc_state; if (!state) goto out; db = DOMMOZ_NewStyleDatabase(cx, state); top->style_db = db; out: LO_UnlockLayout(); return db; } #endif /* MOZILLA_CLIENT */ static JSBool InsertBaseSelector(JSContext *cx, DOM_StyleDatabase *db, DOM_StyleSelector *sel) { DOM_StyleSelector *base, *iter; base = PL_HashTableLookup(db->ht, sel->selector); if (!base) { return PL_HashTableAdd(db->ht, sel->selector, sel) != NULL; } /* * We need to keep these properly ordered, so that we always find the * best match first. This means that "selector"/"extra" must always * appear before "selector"/NULL. */ if (sel->extra) { /* these go at the beginning, so we can stick it right here */ sel->sibling = base; return PL_HashTableRemove(db->ht, sel->selector) && (PL_HashTableAdd(db->ht, sel->selector, sel) != NULL); } /* * This is O(n), which is a bit sucky. We could cache the tail * somewhere if it becomes an issue. */ for (iter = base; iter->sibling; iter = iter->sibling) /* nothing */ ; iter->sibling = sel; return JS_TRUE; } /* * Does the selector match the tokens (the parts that matter for this class)? * strict means that document.classes.foo.all does NOT match * document.classes.foo.H4, for example. */ static JSBool SelectorMatchesToken(JSContext *cx, DOM_StyleSelector *sel, uint8 type, DOM_StyleToken token, DOM_StyleToken extra, DOM_StyleToken pseudo, JSBool strict) { if (sel->type != type || XP_STRCMP(sel->selector, token)) return JS_FALSE; switch (type) { case SELECTOR_CLASS: if (strict) { if (!PSEUDO_MATCHES(sel->extra, extra)) return JS_FALSE; } else { if (sel->extra && (!extra || XP_STRCMP(extra, sel->extra))) return JS_FALSE; } /* FALLTHROUGH */ case SELECTOR_TAG: if (strict) { if (!PSEUDO_MATCHES(sel->pseudo, pseudo)) return JS_FALSE; } else { if (sel->pseudo && (!pseudo || XP_STRCMP(pseudo, sel->pseudo))) return JS_FALSE; } case SELECTOR_ID: return JS_TRUE; default: XP_ASSERT(0 && "unknown selector type in base selector"); return JS_FALSE; } } static DOM_StyleSelector * GetBaseSelector(JSContext *cx, DOM_StyleDatabase *db, uint8 type, DOM_StyleToken token, DOM_StyleToken extra, DOM_StyleToken pseudo, JSBool strict) { DOM_StyleSelector *sel; if (!cx) return NULL; if (!db) STYLE_DB_FROM_CX(db, cx); if (!db) return NULL; sel = PL_HashTableLookup(db->ht, token); for (; sel; sel = sel->sibling) { if (SelectorMatchesToken(cx, sel, type, token, extra, pseudo, strict)) return sel; } return NULL; } static void DestroySelector(JSContext *cx, DOM_StyleSelector *sel) { XP_FREE((char *)sel->selector); if (sel->pseudo) XP_FREE((char *)sel->pseudo); if (sel->extra) XP_FREE((char *)sel->extra); if (sel->rules) { DOM_StyleRule *iter, *next; iter = sel->rules; do { next = iter->next; XP_FREE((char *)iter->entry.name); XP_FREE((char *)iter->entry.value); XP_FREE(iter); iter = next; } while (iter); } XP_FREE(sel); } static DOM_StyleSelector * NewSelector(JSContext *cx, uint8 type, DOM_StyleToken selector, DOM_StyleToken extra, DOM_StyleToken pseudo) { DOM_StyleSelector *sel; sel = XP_NEW_ZAP(DOM_StyleSelector); if (!sel) return NULL; sel->type = type; sel->selector = XP_STRDUP(selector); if (!sel->selector) { DestroySelector(cx, sel); return NULL; } if (pseudo) { sel->pseudo = XP_STRDUP(pseudo); if (!sel->pseudo) { DestroySelector(cx, sel); return NULL; } } if (extra) { sel->extra = XP_STRDUP(extra); if (!sel->extra) { DestroySelector(cx, sel); return NULL; } } return sel; } /* the pseudoclass, if any, is stored in a magic attribute */ DOM_StyleToken DOM_GetElementPseudo(JSContext *cx, DOM_Element *element) { DOM_AttributeEntry *entry; if (!DOM_GetElementAttribute(cx, element, "dom:pseudoclass", &entry)) /* report error? */ return NULL; return entry ? entry->value : NULL; } JSBool DOM_SetElementPseudo(JSContext *cx, DOM_Element *element, DOM_StyleToken pseudo) { /* don't run the callbacks for attribute setting */ return dom_SetElementAttribute(cx, element, "dom:pseudoclass", pseudo, JS_FALSE); } #ifdef DEBUG_shaver_SME #define MATCH() \ fprintf(stderr, "[selector %s:%s matches element %s:%s]", \ sel->selector, sel->pseudo ? sel->pseudo : "", \ element->tagName, elementPseudo); #else #define MATCH() #endif #ifdef DEBUG_shaver_SME #define NO_MATCH() \ fprintf(stderr, "[selector %s:%s doesn't match element %s:%s]", \ sel->selector, sel->pseudo ? sel->pseudo : "", \ element->tagName, elementPseudo); #else #define NO_MATCH() #endif static JSBool SelectorMatchesElement(JSContext *cx, DOM_Element *element, DOM_StyleSelector *sel) { DOM_StyleToken elementPseudo = DOM_GetElementPseudo(cx, element); #ifdef DEBUG_shaver_SME fprintf(stderr, "[checking selector %d:%s/%s:%s against element %s/%s/%s:%s]", sel->type, sel->selector, sel->extra, sel->pseudo, element->styleID, element->styleClass, element->tagName, elementPseudo); #endif switch(sel->type) { case SELECTOR_TAG: if (XP_STRCMP(sel->selector, element->tagName)) return JS_FALSE; /* check pseudo, if any */ if (sel->pseudo) { if (PSEUDO_MATCHES(sel->pseudo, elementPseudo)) { MATCH(); return JS_TRUE; } NO_MATCH(); return JS_FALSE; } else { MATCH(); return JS_TRUE; } case SELECTOR_ID: return element->styleID && !XP_STRCMP(sel->selector, element->styleID); case SELECTOR_CLASS: if (XP_STRCMP(sel->selector, element->styleClass) || (sel->extra && XP_STRCMP(sel->extra, element->tagName))) return JS_FALSE; return JS_TRUE; } return JS_FALSE; } static DOM_AttributeEntry * RuleValueFor(JSContext *cx, DOM_StyleRule *rule, DOM_StyleToken property) { DOM_StyleRule *iter = rule; do { if (!XP_STRCMP(iter->entry.name, property)) { return &iter->entry; } iter = iter->next; } while (iter); return NULL; } static DOM_StyleRule * AddRule(JSContext *cx, DOM_StyleSelector *sel, const char *name, const char *value, int16 weight) { DOM_StyleRule *rule; #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "adding rule %s = %s to %d:%s/%s:%s\n", name, value, sel->type, sel->selector, sel->extra, sel->pseudo); #endif rule = XP_NEW_ZAP(DOM_StyleRule); if (!rule) return NULL; rule->entry.name = XP_STRDUP(name); rule->entry.value = XP_STRDUP(value); rule->entry.dirty = JS_TRUE; rule->weight = weight; rule->next = sel->rules; sel->rules = rule; return rule; } /* Find the parent element of appropriate type for given element */ static DOM_Element * AncestorOfType(JSContext *cx, DOM_Element *element, DOM_StyleSelector *sel) { do { /* check type */ if (SelectorMatchesElement(cx, element, sel)) { return element; } element = (DOM_Element *)element->node.parent; } while (element && element->node.type == NODE_TYPE_ELEMENT); return NULL; } /* * From libstyle/jssrules.h: * "Specificity is implemented as three 8-bit components: the number * of tags in the selector (the least significant component), the * number of classes in the selector, and the number of ids in the * selector (the most significant component)." * * We use the lower 8 bits to count the ``enclosure depth'' of a rule * in SELECTOR_SCORE below, so that "H1 EM {color:blue}" can override * "EM {color:red}" without interfering with the other specificity * scores. Because we use the lower 8 bits for this, the depth will * only matter for selectors that otherwise had the same score, unless * we get a depth of more than 255 elements. In that case, I'll try * to care. */ #define CSS_SPECIFICITY(ids, classes, tags) \ ((uint32)(ids << 24) | (uint32)(classes << 16) | (uint32)(tags << 8)) static uint32 ScoreSelector(DOM_StyleSelector *sel) { switch(sel->type) { case SELECTOR_ID: return CSS_SPECIFICITY(1, 0, 0); case SELECTOR_CLASS: if (sel->extra) /* sel->extra is NULL for .all */ return CSS_SPECIFICITY(0, 1, 1); else return CSS_SPECIFICITY(0, 1, 0); case SELECTOR_TAG: return CSS_SPECIFICITY(0, 0, 1); default: XP_ASSERT(0 && "bogus selector type in ScoreSelector"); return 0; } } #define SELECTOR_SCORE(sel, specificity) (ScoreSelector(sel) + specificity) /* * Determine if the given selector is a best-yet match for element, and * recurse/iterate appropriately over enclosing and sibling elements. */ static JSBool CheckSelector(JSContext *cx, DOM_Element *element, DOM_StyleSelector *sel, DOM_StyleToken property, DOM_AttributeEntry **entryp, uintN *best, uintN specificity) { DOM_AttributeEntry *entry; DOM_Element *next; DOM_StyleSelector *iter; /* check self */ if (SelectorMatchesElement(cx, element, sel)) { /* if we have rules, maybe get an entry from them */ if (sel->rules) { uintN score = SELECTOR_SCORE(sel, specificity); if (score > *best) { /* are we the best so far? */ entry = RuleValueFor(cx, sel->rules, property); if (entry) { /* do we have a value for this property? */ #ifdef DEBUG_shaver fprintf(stderr, "[+SCORE %d, VALUE %s]", score, entry->value); #endif *best = score; *entryp = entry; } } else { #ifdef DEBUG_shaver entry = RuleValueFor(cx, sel->rules, property); if (entry) { /* do we have a value for this property? */ fprintf(stderr, "[-score %d, value %s]", score, entry->value); } #endif } } /* now, check our enclosing selector */ iter = sel->enclosing; while(iter) { next = AncestorOfType(cx, element, iter); if (next) { if (!CheckSelector(cx, next, iter, property, entryp, best, specificity + 1)) return JS_FALSE; return JS_TRUE; } iter = iter->sibling; } } /* check our sibling */ if (sel->sibling) if (!CheckSelector(cx, element, sel->sibling, property, entryp, best, specificity)) return JS_FALSE; return JS_TRUE; } JSBool DOM_StyleGetProperty(JSContext *cx, DOM_StyleDatabase *db, DOM_Node *node, DOM_StyleToken property, DOM_AttributeEntry **entryp) { DOM_StyleSelector *sel; DOM_Element *element, *iter; JSBool ok; uintN best = 0; uint8 type; *entryp = NULL; if (node->type != NODE_TYPE_ELEMENT) { #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "(node is type %d, using parent) ", node->type); #endif element = (DOM_Element *)node->parent; if (!element || element->node.type != NODE_TYPE_ELEMENT) return JS_TRUE; } else { element = (DOM_Element *)node; } if (!db) STYLE_DB_FROM_CX(db, cx); #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "\nGetProperty: looking for %s/%s/%s:%s.%s: ", element->styleID, element->styleClass, element->tagName, DOM_GetElementPseudo(cx, element), property); #endif /* * We have to handle TAGs, .classes and #IDs. Basically, we run through * this loop 3 times, finding the best match for IDs, then classes, then * tags. */ iter = element; do { const char *token, *extra, *pseudo; pseudo = DOM_GetElementPseudo(cx, iter); for (type = SELECTOR_ID; type <= SELECTOR_TAG; type++) { switch(type) { case SELECTOR_ID: token = iter->styleID; extra = NULL; break; case SELECTOR_CLASS: token = iter->styleClass; extra = iter->tagName; break; case SELECTOR_TAG: token = iter->tagName; extra = NULL; break; } if (!token) continue; sel = GetBaseSelector(cx, db, type, token, extra, pseudo, JS_FALSE); if (sel) { #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "[BASE selector for %d:%s/%s:%s -- checking]", type, token, extra, pseudo); #endif ok = CheckSelector(cx, iter, sel, property, entryp, &best, 0); if (ok) { /* * the first one we find that gives this property is * correct, because we check the innermost nodes first. */ if (*entryp) { #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "found %s ", (*entryp)->value); #endif goto found_it; /* oh, for a labelled break */ } } else { #ifdef DEBUG_shaver fprintf(stderr, "CheckSelector failed\n"); #endif return JS_FALSE; } } } iter = (DOM_Element *)iter->node.parent; } while (iter && iter->node.type == NODE_TYPE_ELEMENT); found_it: #ifdef DEBUG_shaver_verbose if (!sel) fprintf(stderr, "no base selector found"); else #endif #ifdef DEBUG_shaver_style_primitives if (!*entryp) fprintf(stderr, "no match found"); fputs("\n", stderr); #endif return JS_TRUE; } DOM_AttributeEntry * DOM_StyleAddRule(JSContext *cx, DOM_StyleDatabase *db, DOM_StyleSelector *sel, DOM_StyleToken name, const char *value) { DOM_StyleRule *rule; DOM_AttributeEntry *entry; if (sel->rules) { entry = RuleValueFor(cx, sel->rules, name); if (entry) { if (!XP_STRCMP(entry->value, value)) return entry; #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "overwriting %s=%s with %s=%s\n", name, entry->value, name, value); #endif XP_FREE((char *)entry->value); entry->value = XP_STRDUP(value); if (!entry->value) return NULL; entry->dirty = JS_TRUE; return entry; } } /* * XXXshaver we should allow a weight to be specified as well, so that * user-agent/author/user sheets can compete correctly. Right now, we * always use a weight of 0. */ rule = AddRule(cx, sel, name, value, 0); if (!rule) return NULL; return &rule->entry; } static DOM_StyleSelector * FindSelector(JSContext *cx, DOM_StyleDatabase *db, DOM_StyleSelector *base, uint8 type, DOM_StyleToken enclosing, DOM_StyleToken extra, DOM_StyleToken pseudo) { DOM_StyleSelector *sel; /* looking for the base one */ if (!base) { sel = GetBaseSelector(cx, db, type, enclosing, extra, pseudo, JS_TRUE); if (!sel) { sel = NewSelector(cx, type, enclosing, extra, pseudo); if (!sel) return NULL; InsertBaseSelector(cx, db, sel); } return sel; } /* check existing enclosing selectors */ for(sel = base->enclosing; sel; sel = sel->sibling) { if (SelectorMatchesToken(cx, sel, type, enclosing, extra, pseudo, JS_TRUE)) return sel; } /* nothing found that matches, so create a new one */ sel = NewSelector(cx, type, enclosing, extra, pseudo); if (!sel) return NULL; sel->sibling = base->enclosing; base->enclosing = sel; return sel; } #define HANDLE_SELECTOR_TYPE(token, type) \ PR_BEGIN_MACRO \ if (*token == '.') { \ type = SELECTOR_CLASS; \ token++; \ } else if (*token == '#') { \ type = SELECTOR_ID; \ token++; \ } else { \ type = SELECTOR_TAG; \ } \ PR_END_MACRO DOM_StyleSelector * DOM_StyleFindSelector(JSContext *cx, DOM_StyleDatabase *db, DOM_StyleSelector *base, DOM_StyleToken enclosing, DOM_StyleToken pseudo) { uint8 type; HANDLE_SELECTOR_TYPE(enclosing, type); return FindSelector(cx, db, base, type, enclosing, NULL, pseudo); } DOM_StyleSelector * DOM_StyleFindSelectorFull(JSContext *cx, DOM_StyleDatabase *db, DOM_StyleSelector *base, uint8 type, DOM_StyleToken enclosing, DOM_StyleToken extra, DOM_StyleToken pseudo) { if (type == SELECTOR_ID) extra = NULL; return FindSelector(cx, db, base, type, enclosing, extra, pseudo); } /* * JS classes */ /* * StyleSelector class: document.tags.H1 or contextual(tags.H1, tags.EM) or * document.ids.mine or document.classes.punk.all or whatever. */ static JSBool StyleSelector_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { DOM_StyleSelector *sel; DOM_StyleDatabase *db; JSString *rule, *value; if (!JSVAL_IS_STRING(id)) return JS_TRUE; sel = JS_GetPrivate(cx, obj); if (!sel) return JS_TRUE; rule = JSVAL_TO_STRING(id); value = JS_ValueToString(cx, *vp); if (!value) return JS_FALSE; STYLE_DB_FROM_CX(db, cx); #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "%s.", sel->selector); switch (sel->type) { case SELECTOR_CLASS: fprintf(stderr, "%s.", sel->extra ? sel->extra : "all"); break; default: break; } fprintf(stderr, "%s = %s\n", JS_GetStringBytes(rule), JS_GetStringBytes(value)); #endif return DOM_StyleAddRule(cx, db, sel, JS_GetStringBytes(rule), JS_GetStringBytes(value)) != NULL; } static JSBool StyleSelector_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { if (!JSVAL_IS_STRING(id)) return JS_TRUE; return JS_TRUE; } static JSBool StyleSelector_convert(JSContext *cx, JSObject *obj, JSType hint, jsval *vp) { JSString *str; DOM_StyleSelector *sel; if (hint == JSTYPE_STRING) { /* XXXshaver handle A.class:visited and A#id, etc. */ char * selstring; uintN len; sel = JS_GetPrivate(cx, obj); if (!sel || !sel->selector) return JS_TRUE; if (sel->pseudo) { /* "selector:pseudo" */ selstring = JS_malloc(cx, (len = XP_STRLEN(sel->selector)) + XP_STRLEN(sel->pseudo) + 2 /* : and \0 */); if (!selstring) return JS_FALSE; XP_STRCPY(selstring, sel->selector); selstring[len] = ':'; selstring[len + 1] = '\0'; XP_STRCAT(selstring, sel->pseudo); } else { selstring = (char *)sel->selector; } str = JS_NewStringCopyZ(cx, selstring); if (sel->pseudo) JS_free(cx, selstring); if (!str) return JS_FALSE; *vp = STRING_TO_JSVAL(str); } return JS_TRUE; } static JSBool StyleSelector_resolve(JSContext *cx, JSObject *obj, jsval id) { if (!JSVAL_IS_STRING(id)) return JS_TRUE; /* XXX we should check that it's a valid JSSS property name first */ return JS_DefineProperty(cx, obj, JS_GetStringBytes(JSVAL_TO_STRING(id)), JSVAL_VOID, 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT); } static JSClass StyleSelectorClass = { "StyleSelector", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, StyleSelector_getProperty, StyleSelector_setProperty, JS_EnumerateStub, StyleSelector_resolve, StyleSelector_convert, JS_FinalizeStub }; static JSBool StyleSelector(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { return JS_TRUE; } JSObject * dom_StyleSelectorInit(JSContext *cx, JSObject *scope) { JSObject *proto; proto = JS_InitClass(cx, scope, NULL, &StyleSelectorClass, StyleSelector, 0, 0, 0, 0, 0); return proto; } JSObject * DOM_NewStyleSelectorObject(JSContext *cx, DOM_StyleSelector *sel) { JSObject *obj; obj = JS_ConstructObject(cx, &StyleSelectorClass, NULL, NULL); if (!obj) return NULL; if (!JS_SetPrivate(cx, obj, sel)) return NULL; sel->mocha_object = obj; return obj; } JSObject * DOM_ObjectForStyleSelector(JSContext *cx, DOM_StyleSelector *sel) { if (!sel) return NULL; if (sel->mocha_object) return sel->mocha_object; return DOM_NewStyleSelectorObject(cx, sel); } /* * Tags object: document.tags */ static JSBool Tags_resolve(JSContext *cx, JSObject *obj, jsval id) { JSString *tag; DOM_StyleDatabase *db; char *tagString, *pseudo, *selName; DOM_StyleSelector *sel; JSObject *tagObj; if (!JSVAL_IS_STRING(id)) return JS_TRUE; STYLE_DB_FROM_CX(db, cx); tag = JSVAL_TO_STRING(id); /* XXXshaver use GetStringChars and DefinePropertyUC? */ tagString = JS_GetStringBytes(tag); if (!tagString) return JS_FALSE; #ifdef DEBUG_shaver fprintf(stderr, "tags.%s\n", tagString); #endif pseudo = strchr(tagString, ':'); if (pseudo) { ptrdiff_t pseudoOff = pseudo - tagString; selName = XP_STRDUP(tagString); if (!selName) return JS_FALSE; selName[pseudoOff] = 0; pseudo = selName + pseudoOff + 1; #ifdef DEBUG_shaver fprintf(stderr, "found pseudo: \"%s\":\"%s\"\n", selName, pseudo); #endif } else { selName = tagString; } sel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_TAG, selName, NULL, pseudo); if (pseudo) XP_FREE(selName); tagObj = DOM_ObjectForStyleSelector(cx, sel); return JS_DefineProperty(cx, obj, tagString, OBJECT_TO_JSVAL(tagObj), 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT); } static JSClass TagsClass = { "Tags", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, Tags_resolve, JS_ConvertStub, JS_FinalizeStub }; static JSObject * TagsObjectInit(JSContext *cx, JSObject *scope) { JSObject *obj = JS_NewObject(cx, &TagsClass, NULL, scope); return obj; } /* * Classes object: document.classes and document.classes.punk */ static void ClassHolder_finalize(JSContext *cx, JSObject *obj) { char *cls = JS_GetPrivate(cx, obj); if (cls) XP_FREE(cls); } static JSBool ClassHolder_resolve(JSContext *cx, JSObject *obj, jsval id) { JSString *tag; DOM_StyleDatabase *db; char *clsString, *tagString, *extra; DOM_StyleSelector *sel; JSObject *clsObj; if (!JSVAL_IS_STRING(id)) return JS_TRUE; clsString = JS_GetPrivate(cx, obj); if (!clsString) return JS_TRUE; STYLE_DB_FROM_CX(db, cx); tag = JSVAL_TO_STRING(id); /* XXXshaver use GetStringChars and DefinePropertyUC? */ tagString = JS_GetStringBytes(tag); if (!tagString) return JS_FALSE; #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "classes.%s.%s\n", clsString, tagString); #endif /* XXX handle pseudo */ if (XP_STRCMP(tagString, "all")) extra = tagString; else extra = NULL; sel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_CLASS, clsString, extra, NULL); clsObj = DOM_ObjectForStyleSelector(cx, sel); return JS_DefineProperty(cx, obj, tagString, OBJECT_TO_JSVAL(clsObj), 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT); } static JSClass ClassHolderClass = { "ClassHolder", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, ClassHolder_resolve, JS_ConvertStub, ClassHolder_finalize }; static JSObject * ClassHolder(JSContext *cx, const char *cls) { char *clsDup; JSObject *obj = JS_NewObject(cx, &ClassHolderClass, NULL, JS_GetGlobalObject(cx)); if (!obj) return NULL; clsDup = XP_STRDUP(cls); if (!clsDup) return NULL; if (!JS_SetPrivate(cx, obj, clsDup)) return NULL; return obj; } static JSBool Classes_resolve(JSContext *cx, JSObject *obj, jsval id) { JSString *cls; char *clsString; JSObject *clsObj; if (!JSVAL_IS_STRING(id)) return JS_TRUE; cls = JSVAL_TO_STRING(id); /* XXXshaver use GetStringChars and DefinePropertyUC? */ clsString = JS_GetStringBytes(cls); if (!clsString) return JS_FALSE; #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "classes.%s\n", clsString); #endif clsObj = ClassHolder(cx, clsString); if (!clsObj) return JS_FALSE; return JS_DefineProperty(cx, obj, clsString, OBJECT_TO_JSVAL(clsObj), 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT); } static JSClass ClassesClass = { "Classes", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, Classes_resolve, JS_ConvertStub, JS_FinalizeStub }; static JSObject * ClassesObjectInit(JSContext *cx, JSObject *scope) { JSObject *obj = JS_NewObject(cx, &ClassesClass, NULL, scope); return obj; } /* * Ids object: document.ids */ static JSBool Ids_resolve(JSContext *cx, JSObject *obj, jsval id) { JSString *str; DOM_StyleDatabase *db; char *idString; DOM_StyleSelector *sel; JSObject *idObj; if (!JSVAL_IS_STRING(id)) return JS_TRUE; STYLE_DB_FROM_CX(db, cx); str = JSVAL_TO_STRING(id); /* XXXshaver use GetStringChars and DefinePropertyUC? */ idString = JS_GetStringBytes(str); if (!idString) return JS_FALSE; #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "ids.%s\n", idString); #endif /* XXX handle pseudo and tag */ sel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_ID, idString, NULL, NULL); idObj = DOM_ObjectForStyleSelector(cx, sel); return JS_DefineProperty(cx, obj, idString, OBJECT_TO_JSVAL(idObj), 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT); } static JSClass IdsClass = { "Ids", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, Ids_resolve, JS_ConvertStub, JS_FinalizeStub }; static JSObject * IdsObjectInit(JSContext *cx, JSObject *scope) { JSObject *obj = JS_NewObject(cx, &IdsClass, NULL, scope); return obj; } JSBool DOM_DocObjectResolveStyleProps(JSContext *cx, JSObject *obj, jsval id) { char *name; if (!JSVAL_IS_STRING(id)) return JS_TRUE; name = JS_GetStringBytes(JSVAL_TO_STRING(id)); #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "resolving document.%s\n", name); #endif if (!XP_STRCMP(name, "tags")) { JSObject *tags; tags = TagsObjectInit(cx, obj); if (!tags) return JS_FALSE; return JS_DefineProperty(cx, obj, "tags", OBJECT_TO_JSVAL(tags), 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT); } else if (!XP_STRCMP(name, "classes")) { JSObject *classes; classes = ClassesObjectInit(cx, obj); if (!classes) return JS_FALSE; return JS_DefineProperty(cx, obj, "classes", OBJECT_TO_JSVAL(classes), 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT); } else if (!XP_STRCMP(name, "ids")) { JSObject *ids; ids = IdsObjectInit(cx, obj); if (!ids) return JS_FALSE; return JS_DefineProperty(cx, obj, "ids", OBJECT_TO_JSVAL(ids), 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT); } else if (!XP_STRCMP(name, "contextual")) { return (JSBool)(JS_DefineFunction(cx, obj, "contextual", DOM_JSContextual, 1, 0) != NULL); } return JS_TRUE; } /* * Return a contextual selector: contextual("H1", tags.EM); */ JSBool DOM_JSContextual(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { DOM_StyleDatabase *db; intN i; DOM_StyleSelector *sel = NULL; JSObject *selObj; JSString *str; if (!argc) return JS_TRUE; STYLE_DB_FROM_CX(db, cx); for (i = argc - 1; i >= 0; i--) { JSObject *obj2; const char *selName, *selPseudo, *selExtra; int8 selType; if (JSVAL_IS_OBJECT(argv[i]) && (obj2 = JSVAL_TO_OBJECT(argv[i]), #ifdef JS_THREADSAFE JS_GetClass(cx, obj2) #else JS_GetClass(obj2) #endif == &StyleSelectorClass)) { /* tags.H1 or ids.mine or classes.punk.all */ DOM_StyleSelector *sel2 = JS_GetPrivate(cx, obj2); if (!sel2) /* have prototype, will travel (and ignore it) */ continue; selName = sel2->selector; selType = sel2->type; selExtra = sel2->extra; selPseudo = sel2->pseudo; } else { /* "H1" or ".myclass" or "#ID" or whatever */ str = JS_ValueToString(cx, argv[i]); if (!str) return JS_FALSE; selName = JS_GetStringBytes(str); if (!selName) return JS_FALSE; /* XXX parse and handle class/id and pseudo/extra! */ selPseudo = NULL; selExtra = NULL; selType = SELECTOR_TAG; } #ifdef DEBUG_shaver_style_primitives fprintf(stderr, "contextual: going from %d:%s/%s to %d:%s/%s\n", sel ? sel->type : -1, sel ? sel->selector : "", sel ? sel->extra : "", selType, selName, selExtra); #endif sel = DOM_StyleFindSelectorFull(cx, db, sel, selType, selName, selExtra, selPseudo); if (!sel) { JS_ReportError(cx, "Error getting selector %s\n", selName); return JS_FALSE; } } selObj = DOM_ObjectForStyleSelector(cx, sel); *rval = OBJECT_TO_JSVAL(selObj); return JS_TRUE; }