diff --git a/lib/libdom/Makefile b/lib/libdom/Makefile index ba1b7a0915e..b1aa96985bf 100644 --- a/lib/libdom/Makefile +++ b/lib/libdom/Makefile @@ -20,9 +20,16 @@ DEPTH = ../.. MODULE = dom LIBRARY_NAME = dom -REQUIRES = js +REQUIRES = js dom -EXPORTS = dom.h +ifdef MOZILLA_CLIENT +REQUIRES += lay img layer util + +# hack until PERIGNON takes over the world +REQUIRES += style +endif + +EXPORTS = dom.h domstyle.h CSRCS = domattr.c \ domcore.c \ @@ -34,3 +41,5 @@ CSRCS = domattr.c \ $(NULL) include $(DEPTH)/config/rules.mk + +DEFINES += -DDOM diff --git a/lib/libdom/Makefile.in b/lib/libdom/Makefile.in index 752c1b01767..8a79897b522 100644 --- a/lib/libdom/Makefile.in +++ b/lib/libdom/Makefile.in @@ -26,8 +26,14 @@ MODULE = dom LIBRARY_NAME = dom REQUIRES = js +#ifdef MOZILLA_CLIENT +REQUIRES += lay img layer util -EXPORTS = $(srcdir)/dom.h +# hack until PERIGNON takes over the world +REQUIRES += style +#endif + +EXPORTS = $(srcdir)/dom.h $(srcdir)/domstyle.h CSRCS = domattr.c \ domcore.c \ @@ -39,3 +45,5 @@ CSRCS = domattr.c \ $(NULL) include $(topsrcdir)/config/rules.mk + +DEFINES += -DDOM diff --git a/lib/libdom/STYLE_NOTES b/lib/libdom/STYLE_NOTES new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/libdom/TODO b/lib/libdom/TODO new file mode 100644 index 00000000000..54bdb7ae76e --- /dev/null +++ b/lib/libdom/TODO @@ -0,0 +1,27 @@ +General: +- document.create* +- escape HTML entities (JS entities?) coming into the text functions + +Style: +- add a CSS-parsing input API, or extract CSS->JS conversion from +libstyle +- DOM_StyleIsDirty/DOM_LockStyle/DOM_UnlockStyle to allow global +caching of style data during document layout without racing with +alterations from mocha thread. + +Layout: +- make proper LO_Elements for table stuff (pollmann) +- stick the DOM_StyleGetElementProperty stuff in all the right places +(reflow?) +- make
honour inherited style (colour, etc.) + +libmocha: +- move destruction into LM_ReleaseDocument +- add destruction of style db +- look closely at the implicit pop stuff in DOM_HTMLPushNode +- wire up node reordering +- put single element child on #document at creation (and never + pop it off) + +XML: +- everything, really diff --git a/lib/libdom/dom.h b/lib/libdom/dom.h index c7a4f03c7ab..114274330bc 100644 --- a/lib/libdom/dom.h +++ b/lib/libdom/dom.h @@ -224,8 +224,9 @@ struct DOM_Element { DOM_ElementOps *ops; const char *tagName; uintN nattrs; - DOM_AttributeEntry *attrs; - void *style; /* later, later... */ + DOM_AttributeEntry *attrs; + char *styleClass; + char *styleID; }; /* @@ -235,7 +236,7 @@ struct DOM_Element { DOM_Element * DOM_NewElement(const char *tagName, DOM_ElementOps *eleops, char *name, - DOM_NodeOps *nodeops); + char *styleClass, char *styleID, DOM_NodeOps *nodeops); JSObject * DOM_NewElementObject(JSContext *cx, DOM_Element *element); @@ -251,6 +252,12 @@ JSBool DOM_SetElementAttribute(JSContext *cx, DOM_Element *element, const char *name, const char *value); +typedef JSBool(*DOM_DataParser)(const char *str, uint32 *data, void *closure); + +JSBool +DOM_GetCleanEntryData(JSContext *cx, DOM_AttributeEntry *entry, + DOM_DataParser parser, uint32 *data, void *closure); + /* * Set the attributes from a pair of synchronized lists. * (This is what PA_FetchAllNameValues provides, handily enough.) diff --git a/lib/libdom/dom_priv.h b/lib/libdom/dom_priv.h index afd36ae0ddb..e69de29bb2d 100644 --- a/lib/libdom/dom_priv.h +++ b/lib/libdom/dom_priv.h @@ -1,85 +0,0 @@ -/* -*- 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 Original Code is Mozilla Communicator client code. - * - * 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. - */ - -/* - * Module-private stuff for the DOM lib. - */ - -#ifndef DOM_PRIV_H -#define DOM_PRIV_H - -#include "dom.h" -#ifdef XP_PC -/* XXX this pulls in half the freaking client! but it's needed for some bogus - reason probably to do with compiled headers, to avoid unresolved refs - to XP_MEMCPY and XP_STRDUP */ -#include "xp.h" -#else -#include "xpassert.h" -#include "xp_mem.h" -#include "xp_str.h" -#endif - -JSObject * -dom_NodeInit(JSContext *cx, JSObject *obj); - -JSObject * -dom_ElementInit(JSContext *cx, JSObject *obj, JSObject *node_proto); - -JSObject * -dom_AttributeInit(JSContext *cx, JSObject *scope, JSObject *node_proto); - -JSObject * -dom_CharacterDataInit(JSContext *cx, JSObject *scope, JSObject *node_proto); - -JSObject * -dom_TextInit(JSContext *cx, JSObject *scope, JSObject *data_proto); - -JSObject * -dom_CommentInit(JSContext *cx, JSObject *scope, JSObject *data_proto); - -JSBool -dom_node_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp); - -JSBool -dom_node_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp); - -void -dom_node_finalize(JSContext *cx, JSObject *obj); - -/* if you adjust these enums, be sure to adjust the various setters */ -enum { - DOM_NODE_NODENAME = -1, - DOM_NODE_NODEVALUE = -2, - DOM_NODE_NODETYPE = -3, - DOM_NODE_PARENTNODE = -4, - DOM_NODE_CHILDNODES = -5, - DOM_NODE_FIRSTCHILD = -6, - DOM_NODE_LASTCHILD = -7, - DOM_NODE_PREVIOUSSIBLING = -8, - DOM_NODE_NEXTSIBLING = -9, - DOM_NODE_ATTRIBUTES = -10, - DOM_NODE_HASCHILDNODES = -11, - DOM_ELEMENT_TAGNAME = -12, -}; - -extern JSPropertySpec dom_node_props[]; - -#endif /* DOM_PRIV_H */ diff --git a/lib/libdom/domcore.c b/lib/libdom/domcore.c index 77131ab9e34..e69de29bb2d 100644 --- a/lib/libdom/domcore.c +++ b/lib/libdom/domcore.c @@ -1,193 +0,0 @@ -/* -*- 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. - */ - -/* - * Core DOM stuff -- initialization, stub functions, DOMImplementation, etc. - */ - -#include "dom_priv.h" - -JSBool -DOM_Init(JSContext *cx, JSObject *scope) { - JSObject *node, *cdata; - return (( node = dom_NodeInit(cx, scope)) && - dom_AttributeInit(cx, scope, node) && - dom_ElementInit(cx, scope, node) && - ( cdata = dom_CharacterDataInit(cx, scope, node)) && - dom_TextInit(cx, scope, cdata) && - dom_CommentInit(cx, scope, cdata)); -} - -static char *exception_names[] = { - "NO_ERR", - "INDEX_SIZE_ERR", - "WSTRING_SIZE_ERR", - "HIERARCHY_REQUEST_ERR", - "WRONG_DOCUMENT_ERR", - "INVALID_NAME_ERR", - "NO_DATA_ALLOWED_ERR", - "NO_MODIFICATION_ALLOWED_ERR", - "NOT_FOUND_ERR", - "NOT_SUPPORTED_ERR", - "INUSE_ATTRIBUTE_ERR", - "UNSUPPORTED_DOCUMENT_ERR" -}; - -JSBool -DOM_SignalException(JSContext *cx, DOM_ExceptionCode exception) -{ - JS_ReportError(cx, "DOM Exception: %s", exception_names[exception]); - return JS_TRUE; -} - -JSBool -DOM_InsertBeforeStub(JSContext *cx, DOM_Node *node, DOM_Node *child, - DOM_Node *ref, JSBool before) -{ - return JS_TRUE; -} - -JSBool -DOM_ReplaceChildStub(JSContext *cx, DOM_Node *node, DOM_Node *child, - DOM_Node *old, JSBool before) -{ - return JS_TRUE; -} - -JSBool -DOM_RemoveChildStub(JSContext *cx, DOM_Node *node, DOM_Node *old, - JSBool before) -{ - return JS_TRUE; -} - -JSBool -DOM_AppendChildStub(JSContext *cx, DOM_Node *node, DOM_Node *child, - JSBool before) -{ - return JS_TRUE; -} - -void -DOM_DestroyNodeStub(JSContext *cx, DOM_Node *node) -{ - if (node->data) - JS_free(cx, node->data); -} - -JSObject * -DOM_ReflectNodeStub(JSContext *cx, DOM_Node *node) -{ - return NULL; -} - -JSBool -DOM_SetAttributeStub(JSContext *cx, DOM_Element *element, const char *name, - const char *value) -{ - return JS_TRUE; -} - -const char * -DOM_GetAttributeStub(JSContext *cx, DOM_Element *element, const char *name, - JSBool *cacheable) -{ - *cacheable = JS_FALSE; - return "#none"; -} - -intN -DOM_GetNumAttrsStub(JSContext *cx, DOM_Element *element, JSBool *cacheable) -{ - *cacheable = JS_FALSE; - return -1; -} - -#ifdef DEBUG_shaver -int LM_Node_indent = 0; -#endif - -JSObject * -DOM_ObjectForNodeDowncast(JSContext *cx, DOM_Node *node) -{ - if (!node) - return NULL; - - if (!node->mocha_object) - node->mocha_object = node->ops->reflectNode(cx, node); - return node->mocha_object; -} - -void -DOM_DestroyTree(JSContext *cx, DOM_Node *top) -{ - DOM_Node *iter, *next; - for (iter = top->child; iter; iter = next) { - next = iter->sibling; - if (iter->mocha_object) { - iter->prev_sibling = iter->parent = iter->sibling = - iter->child = NULL; -#ifdef DEBUG_shaver - fprintf(stderr, "node %s type %d has mocha_object\n", - iter->name ? iter->name : "", iter->type); -#endif - } else { - DOM_DestroyTree(cx, iter); - } - } - DOM_DestroyNode(cx, top); -} - -/* - * DOMImplementation - */ - -static void -dom_finalize(JSContext *cx, JSObject *obj) -{ -} - -JSClass DOM_DOMClass = { - "DOM", JSCLASS_HAS_PRIVATE, - JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, - JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, dom_finalize -}; - -#if 0 -static JSBool -dom_hasFeature(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, - jsval *rval) -{ - char *feature, *version; - if (!JS_ConvertArguments(cx, argc, argv, "ss", &feature, &version)) - return JS_TRUE; - - if (!strcmp(feature, "HTML") && - !strcmp(version, "1")) - *rval = JSVAL_TRUE; - else - *rval = JSVAL_FALSE; - return JS_TRUE; -} - -static JSFunctionSpec dom_methods[] = { - {"hasFeature", dom_hasFeature, 2}, - {0} -}; -#endif - diff --git a/lib/libdom/domelement.c b/lib/libdom/domelement.c index 737762335a3..ba0f4c51dca 100644 --- a/lib/libdom/domelement.c +++ b/lib/libdom/domelement.c @@ -188,7 +188,7 @@ static JSFunctionSpec element_methods[] = { DOM_Element * DOM_NewElement(const char *tagName, DOM_ElementOps *eleops, char *name, - DOM_NodeOps *nodeops) + char *styleClass, char *styleID, DOM_NodeOps *nodeops) { DOM_Node *node; DOM_Element *element = XP_NEW_ZAP(DOM_Element); @@ -201,7 +201,10 @@ DOM_NewElement(const char *tagName, DOM_ElementOps *eleops, char *name, node->ops = nodeops; element->tagName = tagName; + element->styleClass = styleClass; + element->styleID = styleID; element->ops = eleops; + return element; } @@ -290,7 +293,22 @@ DOM_GetElementAttribute(JSContext *cx, DOM_Element *element, const char *name, return JS_TRUE; } -static JSBool +JSBool +DOM_GetCleanEntryData(JSContext *cx, DOM_AttributeEntry *entry, + DOM_DataParser parser, uint32 *data, void *closure) +{ + if (entry->dirty) { + uint32 newdata; + if (!parser(entry->value, &newdata, closure)) + return JS_FALSE; + entry->data = newdata; + entry->dirty = JS_FALSE; + } + *data = entry->data; + return JS_TRUE; +} + +static DOM_AttributeEntry * AddAttribute(JSContext *cx, DOM_Element *element, const char *name, const char *value) { @@ -299,29 +317,31 @@ AddAttribute(JSContext *cx, DOM_Element *element, const char *name, if (!element->attrs) { element->attrs = JS_malloc(cx, sizeof(DOM_AttributeEntry)); if (!element->attrs) - return JS_FALSE; + return NULL; element->nattrs = 1; } else { element->attrs = XP_REALLOC(element->attrs, (element->nattrs++) * sizeof(DOM_AttributeEntry)); if (!element->attrs) - return JS_FALSE; + return NULL; } entry = element->attrs + element->nattrs - 1; entry->name = name; entry->value = value; - return JS_TRUE; + + return entry; } JSBool -DOM_SetElementAttribute(JSContext *cx, DOM_Element *element, const char *name, - const char *value) +dom_SetElementAttribute(JSContext *cx, DOM_Element *element, const char *name, + const char *value, JSBool runCallback) { DOM_AttributeEntry *entry; if (!DOM_GetElementAttribute(cx, element, name, &entry)) return JS_FALSE; if (!entry) { - if (!AddAttribute(cx, element, name, value)) + entry = AddAttribute(cx, element, name, value); + if (!entry) return JS_FALSE; } else { if (entry->value) @@ -330,9 +350,18 @@ DOM_SetElementAttribute(JSContext *cx, DOM_Element *element, const char *name, } entry->dirty = JS_TRUE; + if (!runCallback) + return JS_TRUE; return element->ops->setAttribute(cx, element, name, value); } +JSBool +DOM_SetElementAttribute(JSContext *cx, DOM_Element *element, const char *name, + const char *value) +{ + return dom_SetElementAttribute(cx, element, name, value, JS_TRUE); +} + static JSBool Element(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *vp) { diff --git a/lib/libdom/domstyle.c b/lib/libdom/domstyle.c index 73af8512a10..3df80ec0240 100644 --- a/lib/libdom/domstyle.c +++ b/lib/libdom/domstyle.c @@ -16,9 +16,29 @@ * 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) { @@ -26,8 +46,6 @@ CompareSelectors(const void *v1, const void *v2) *s2 = (DOM_StyleSelector *)v2; return s1->type == s2->type && - - /* compare base selector */ !XP_STRCMP(s1->selector, s2->selector) && /* if both have a psuedo, they have to match, else both must be NULL */ @@ -60,20 +78,220 @@ DOM_DestroyStyleDatabase(JSContext *cx, DOM_StyleDatabase *db) XP_FREE(db); } +#ifdef MOZILLA_CLIENT +#include "lo_ele.h" +#include "structs.h" +#include "layout.h" +#include "laystyle.h" +#define IMAGE_DEF_ANCHOR_BORDER 2 +#define IMAGE_DEF_VERTICAL_SPACE 0 + +DOM_StyleDatabase * +DOM_StyleDatabaseFromContext(JSContext *cx) +{ + MochaDecoder *decoder; + lo_TopState *top; + + if (!cx) + return NULL; + + decoder = JS_GetPrivate(cx, JS_GetGlobalObject(cx)); + if (!decoder) + return NULL; + + top = lo_FetchTopState(decoder->window_context->doc_id); + if (!top) + return NULL; + + if (!top->style_db) { + LO_Color visitCol, linkCol; + lo_DocState *state = top->doc_state; + DOM_StyleSelector *sel, *imgsel; + DOM_AttributeEntry *entry; + DOM_StyleDatabase *db; + + if (!state) + return NULL; + + db = DOM_NewStyleDatabase(cx); + if (!db) + return NULL; + /* + * 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. + */ + + 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); + + sel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_TAG, + "A", NULL, "link"); + if (!sel) + return NULL; + +#define SET_DEFAULT_VALUE(name, value) \ + entry = DOM_StyleAddRule(cx, db, sel, name, "default"); \ + if (!entry) \ + return NULL; \ + 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")) + return NULL; + + sel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_TAG, + "A", NULL, "visited"); + if (!sel) + return NULL; + + /* 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")) + return NULL; + + /* 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) + return NULL; + + /* set border styles for ``A:link IMG'' */ + sel = DOM_StyleFindSelectorFull(cx, db, imgsel, SELECTOR_TAG, + "A", NULL, "link"); + if (!sel) + return NULL; + 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) + return NULL; + 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 + top->style_db = db; + } + + return (DOM_StyleDatabase *)top->style_db; +} +#endif /* MOZILLA_CLIENT */ + static JSBool InsertBaseSelector(JSContext *cx, DOM_StyleDatabase *db, DOM_StyleSelector *sel) { - return PL_HashTableAdd(db->ht, sel->selector, sel) != NULL; + 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, DOM_StyleToken type, - DOM_StyleToken pseudo) +GetBaseSelector(JSContext *cx, DOM_StyleDatabase *db, uint8 type, + DOM_StyleToken token, DOM_StyleToken extra, + DOM_StyleToken pseudo, JSBool strict) { - DOM_StyleSelector *sel = NULL; - /* sel = HASH_LOOKUP(db->hashtable, type); */ - return sel; + 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 @@ -82,6 +300,8 @@ 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; @@ -97,22 +317,14 @@ DestroySelector(JSContext *cx, DOM_StyleSelector *sel) } static DOM_StyleSelector * -NewSelector(JSContext *cx, DOM_StyleToken selector, DOM_StyleToken pseudo) +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; - if (selector[0] == '.') { - sel->type = SELECTOR_CLASS; - selector++; - } else if (selector[0] == '#') { - sel->type = SELECTOR_ID; - selector++; - } else { - sel->type = SELECTOR_TAG; - } - + sel->type = type; sel->selector = XP_STRDUP(selector); if (!sel->selector) { DestroySelector(cx, sel); @@ -126,102 +338,185 @@ NewSelector(JSContext *cx, DOM_StyleToken selector, DOM_StyleToken pseudo) 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 */ -static DOM_StyleToken -GetPseudo(JSContext *cx, DOM_Element *element) +DOM_StyleToken +DOM_GetElementPseudo(JSContext *cx, DOM_Element *element) { DOM_AttributeEntry *entry; - if (!DOM_GetElementAttribute(cx, element, ":pseudoclass", &entry)) + if (!DOM_GetElementAttribute(cx, element, "dom:pseudoclass", &entry)) /* report error? */ return NULL; return entry ? entry->value : NULL; } -#define PSEUDO_MATCHES(p1, p2) (!XP_STRCMP((p1), (p2))) +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 +#ifdef DEBUG_shaver_SME #define MATCH() \ - fprintf(stderr, "selector %s:%s matches element %s\n", \ - sel->selector, sel->pseudo ? sel->pseudo : "", \ - element->tagName); + 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 +#ifdef DEBUG_shaver_SME #define NO_MATCH() \ - fprintf(stderr, "selector %s:%s doesn't match element %s\n", \ - sel->selector, sel->pseudo ? sel->pseudo : "", \ - element->tagName); + 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 -SelectorMatchesToken(JSContext *cx, DOM_StyleSelector *sel, - DOM_StyleToken token, DOM_StyleToken pseudo) -{ - /* XXX handle #ID and .class */ - return !XP_STRCMP(sel->selector, token); -} - -#define ELEMENT_IS_TYPE(element, type) \ - (!XP_STRCMP(((element))->tagName, (type))) - static JSBool SelectorMatchesElement(JSContext *cx, DOM_Element *element, DOM_StyleSelector *sel) { - /* XXX handle class and ID */ - if (ELEMENT_IS_TYPE(element, sel->selector)) { + 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) { - DOM_StyleToken elementPseudo = GetPseudo(cx, element); 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; } -#define SELECTOR_SCORE(sel, specificity) (specificity) - static DOM_AttributeEntry * RuleValueFor(JSContext *cx, DOM_StyleRule *rule, DOM_StyleToken property) { DOM_StyleRule *iter = rule; do { - if (!XP_STRCMP(iter->entry.name, property)) + 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)) + if (SelectorMatchesElement(cx, element, sel)) { return element; + } element = (DOM_Element *)element->node.parent; - } while (element); + } 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. @@ -233,6 +528,7 @@ CheckSelector(JSContext *cx, DOM_Element *element, DOM_StyleSelector *sel, { DOM_AttributeEntry *entry; DOM_Element *next; + DOM_StyleSelector *iter; /* check self */ if (SelectorMatchesElement(cx, element, sel)) { @@ -243,22 +539,34 @@ CheckSelector(JSContext *cx, DOM_Element *element, DOM_StyleSelector *sel, 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\n", - score, entry->value); + 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 */ - if (sel->enclosing) { - next = AncestorOfType(cx, element, sel->enclosing); - if (next) - if (!CheckSelector(cx, next, sel->enclosing, property, entryp, + 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; } } @@ -274,67 +582,171 @@ CheckSelector(JSContext *cx, DOM_Element *element, DOM_StyleSelector *sel, JSBool DOM_StyleGetProperty(JSContext *cx, DOM_StyleDatabase *db, DOM_Node *node, DOM_StyleToken property, - DOM_StyleToken pseudo, DOM_AttributeEntry **entryp) + DOM_AttributeEntry **entryp) { DOM_StyleSelector *sel; - DOM_Element *element; + 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; - XP_ASSERT(element->node.type == NODE_TYPE_ELEMENT); + if (!element || element->node.type != NODE_TYPE_ELEMENT) + return JS_TRUE; } else { element = (DOM_Element *)node; } - *entryp = NULL; - - sel = GetBaseSelector(cx, db, element->tagName, GetPseudo(cx, element)); - if (!sel) - return JS_TRUE; + 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 /* - * CheckSelector will recursively find the best match for a given - * property. + * 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. */ - return CheckSelector(cx, element, sel, property, entryp, &best, 1); + 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_StyleSelector * -DOM_StyleFindSelector(JSContext *cx, DOM_StyleDatabase *db, - DOM_StyleSelector *base, DOM_StyleToken enclosing, - DOM_StyleToken pseudo) +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, enclosing, pseudo); - if (!sel) - sel = NewSelector(cx, enclosing, pseudo); - if (sel) + 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; } - - if (!base->enclosing) { - sel = NewSelector(cx, enclosing, pseudo); - if (!sel) - return NULL; - base->enclosing = sel; - return sel; - } - + /* check existing enclosing selectors */ - sel = base->enclosing; - do { - if (SelectorMatchesToken(cx, sel, enclosing, pseudo)) + for(sel = base->enclosing; sel; sel = sel->sibling) { + if (SelectorMatchesToken(cx, sel, type, enclosing, extra, pseudo, + JS_TRUE)) return sel; - sel = sel->sibling; - } while (sel); + } /* nothing found that matches, so create a new one */ - sel = NewSelector(cx, enclosing, pseudo); + sel = NewSelector(cx, type, enclosing, extra, pseudo); if (!sel) return NULL; sel->sibling = base->enclosing; @@ -342,3 +754,544 @@ DOM_StyleFindSelector(JSContext *cx, DOM_StyleDatabase *db, 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; +} diff --git a/lib/libdom/domstyle.h b/lib/libdom/domstyle.h index 54e7744e9b0..4cdab3874b9 100644 --- a/lib/libdom/domstyle.h +++ b/lib/libdom/domstyle.h @@ -17,8 +17,7 @@ */ /* - * Style things for the DOM. - * Very purty. Makes it all go. + * Perignon: store style information in the DOM, using CSS-1 selectors. */ #include "jsapi.h" @@ -40,11 +39,6 @@ typedef struct DOM_StyleRule DOM_StyleRule; /* this may become int or something later, for speed */ typedef const char *DOM_StyleToken; -#define DOM_STYLE_PSEUDO_TAG (1 << 7) -#define DOM_SELECTOR_IS_PSEUDO(sel) ((sel) & DOM_STYLE_PSEUDO_TAG) -#define DOM_STYLE_SELECTOR_TYPE(sel) ((sel) & ~DOM_STYLE_PSEUDO_TAG) -#define DOM_PSEUDOIZE(sel) ((sel) | DOM_STYLE_PSEUDO_TAG) - enum { SELECTOR_UNKNOWN = 0, SELECTOR_ID, @@ -53,19 +47,33 @@ enum { }; struct DOM_StyleDatabase { - PLHashTable *ht; /* PRHash, from js/ref or nsprpub, depending? */ + PLHashTable *ht; }; DOM_StyleDatabase * DOM_NewStyleDatabase(JSContext *cx); +void +DOM_DestroyStyleDatabase(JSContext *cx, DOM_StyleDatabase *db); + +/* + * Find or create the StyleDatabase for the given JSContext. + * The embedder must provide an implementation, or #define MOZILLA_CLIENT + * to get the Mozilla-specific one which depends on MochaDecoder and + * MWContext and lo_TopState and stuff. + */ +DOM_StyleDatabase * +DOM_StyleDatabaseFromContext(JSContext *cx); + struct DOM_StyleSelector { int8 type; DOM_StyleToken selector; DOM_StyleToken pseudo; + DOM_StyleToken extra; DOM_StyleSelector *enclosing; DOM_StyleSelector *sibling; DOM_StyleRule *rules; + JSObject *mocha_object; /* reflection for this selector's rules */ }; /* @@ -80,14 +88,25 @@ struct DOM_StyleSelector { * Now find/create a selector for "CODE B": * sel2 = DOM_StyleFindSelector(cx, db, sel, "CODE", NULL); * - * And for "A:visited CODE B": - * sel3 = DOM_StyleFindSelector(cx, db, sel2, "A", "visited"); + * And ".myclass CODE B": + * sel3 = DOM_StyleFindSelector(cx, db, sel2, ".myclass", NULL); */ DOM_StyleSelector * DOM_StyleFindSelector(JSContext *cx, DOM_StyleDatabase *db, DOM_StyleSelector *base, DOM_StyleToken enclosing, DOM_StyleToken pseudo); +/* + * As above, but take type explicitly rather than parsing leading # or . for + * ID or class. For classes or extra is a tag or NULL. For tags, extra is an + * ID or NULL. (For ID, extra is ignored.) + */ +DOM_StyleSelector * +DOM_StyleFindSelectorFull(JSContext *cx, DOM_StyleDatabase *db, + DOM_StyleSelector *base, uint8 type, + DOM_StyleToken enclosing, DOM_StyleToken extra, + DOM_StyleToken pseudo); + struct DOM_StyleRule { DOM_AttributeEntry entry; int16 weight; @@ -96,7 +115,7 @@ struct DOM_StyleRule { /* * Parses a style rule and adds it to the style database. - * If len is 0, rule is presumed to be NUL-terminated. + * If len is 0, rule is presumed to be NUL-terminated. (XXX NYI) * * Usage example: * @@ -119,6 +138,8 @@ DOM_StyleParseRule(JSContext *cx, DOM_StyleDatabase *db, const char *rule, * enclosing Element is used for finding matches. The implementation is * necessarily somewhat hairy. See domstyle.c for details. * + * If db is NULL, DOM_StyleDatabaseFromContext is used to find it. + * * Usage examples: * * Get the color for a section of text: @@ -130,16 +151,38 @@ DOM_StyleParseRule(JSContext *cx, DOM_StyleDatabase *db, const char *rule, JSBool DOM_StyleGetProperty(JSContext *cx, DOM_StyleDatabase *db, DOM_Node *node, - DOM_StyleToken property, DOM_StyleToken psuedo, - DOM_AttributeEntry **entryp); + DOM_StyleToken property, DOM_AttributeEntry **entryp); + +/* + * Get/set the pseudoclass for an element + */ +DOM_StyleToken +DOM_GetElementPseudo(JSContext *cx, DOM_Element *element); + +JSBool +DOM_SetElementPseudo(JSContext *cx, DOM_Element *element, + DOM_StyleToken pseudo); /* * Add a property to the provided selector. * * DOM_StyleAddRule(cx, db, sel, "color", "blue"); */ -JSBool +DOM_AttributeEntry * DOM_StyleAddRule(JSContext *cx, DOM_StyleDatabase *db, DOM_StyleSelector *sel, DOM_StyleToken name, const char *value); +/* + * Resolve classes, tags, ids, contextual on the given object. + */ +JSBool +DOM_DocObjectResolveStyleProps(JSContext *cx, JSObject *obj, jsval id); + +/* + * The contextual selector JS function: contextual("H1", "EM"); + */ +JSBool +DOM_JSContextual(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval); + #endif /* DOM_STYLE_H */ diff --git a/lib/libdom/domtext.c b/lib/libdom/domtext.c index 50b72de5d4d..7d7dc959ced 100644 --- a/lib/libdom/domtext.c +++ b/lib/libdom/domtext.c @@ -373,6 +373,7 @@ DOM_NewText(const char *data, int64 length, DOM_CDataOp notify, LL_L2I(nbytes, length); cdata = (DOM_CharacterData *)text; + cdata->len = length; cdata->data = XP_ALLOC(nbytes); cdata->notify = notify; if (!cdata->data) {