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) {