1998-09-11 23:46:23 +04:00
|
|
|
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
1998-09-10 18:21:43 +04:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "domstyle.h"
|
|
|
|
|
1998-09-16 18:45:07 +04:00
|
|
|
static int
|
|
|
|
CompareSelectors(const void *v1, const void *v2)
|
|
|
|
{
|
|
|
|
DOM_StyleSelector *s1 = (DOM_StyleSelector *)v1,
|
|
|
|
*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 */
|
|
|
|
(s1->pseudo && s2->pseudo ?
|
|
|
|
XP_STRCMP(s1->pseudo, s2->pseudo) :
|
|
|
|
!(s1 || s2));
|
|
|
|
}
|
|
|
|
|
|
|
|
DOM_StyleDatabase *
|
|
|
|
DOM_NewStyleDatabase(JSContext *cx)
|
|
|
|
{
|
|
|
|
DOM_StyleDatabase *db = XP_NEW_ZAP(DOM_StyleDatabase);
|
|
|
|
if (!db)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
db->ht = PL_NewHashTable(32, PL_HashString, PL_CompareStrings,
|
|
|
|
CompareSelectors, NULL, NULL);
|
|
|
|
if (!db->ht) {
|
|
|
|
XP_FREE(db);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return db;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
DOM_DestroyStyleDatabase(JSContext *cx, DOM_StyleDatabase *db)
|
|
|
|
{
|
|
|
|
PL_HashTableDestroy(db->ht);
|
|
|
|
XP_FREE(db);
|
|
|
|
}
|
|
|
|
|
|
|
|
static JSBool
|
|
|
|
InsertBaseSelector(JSContext *cx, DOM_StyleDatabase *db,
|
|
|
|
DOM_StyleSelector *sel)
|
|
|
|
{
|
|
|
|
return PL_HashTableAdd(db->ht, sel->selector, sel) != NULL;
|
|
|
|
}
|
|
|
|
|
1998-09-10 18:21:43 +04:00
|
|
|
static DOM_StyleSelector *
|
1998-09-11 02:14:52 +04:00
|
|
|
GetBaseSelector(JSContext *cx, DOM_StyleDatabase *db, DOM_StyleToken type,
|
1998-09-11 23:46:23 +04:00
|
|
|
DOM_StyleToken pseudo)
|
1998-09-10 18:21:43 +04:00
|
|
|
{
|
|
|
|
DOM_StyleSelector *sel = NULL;
|
|
|
|
/* sel = HASH_LOOKUP(db->hashtable, type); */
|
|
|
|
return sel;
|
|
|
|
}
|
|
|
|
|
1998-09-11 02:14:52 +04:00
|
|
|
static void
|
|
|
|
DestroySelector(JSContext *cx, DOM_StyleSelector *sel)
|
|
|
|
{
|
1998-09-16 18:45:07 +04:00
|
|
|
XP_FREE((char *)sel->selector);
|
1998-09-11 02:14:52 +04:00
|
|
|
if (sel->pseudo)
|
1998-09-16 18:45:07 +04:00
|
|
|
XP_FREE((char *)sel->pseudo);
|
1998-09-11 02:14:52 +04:00
|
|
|
if (sel->rules) {
|
1998-09-11 23:46:23 +04:00
|
|
|
DOM_StyleRule *iter, *next;
|
|
|
|
iter = sel->rules;
|
|
|
|
do {
|
|
|
|
next = iter->next;
|
1998-09-16 18:45:07 +04:00
|
|
|
XP_FREE((char *)iter->entry.name);
|
|
|
|
XP_FREE((char *)iter->entry.value);
|
1998-09-11 23:46:23 +04:00
|
|
|
XP_FREE(iter);
|
|
|
|
iter = next;
|
|
|
|
} while (iter);
|
1998-09-11 02:14:52 +04:00
|
|
|
}
|
|
|
|
XP_FREE(sel);
|
|
|
|
}
|
|
|
|
|
|
|
|
static DOM_StyleSelector *
|
|
|
|
NewSelector(JSContext *cx, DOM_StyleToken selector, DOM_StyleToken pseudo)
|
|
|
|
{
|
|
|
|
DOM_StyleSelector *sel;
|
|
|
|
sel = XP_NEW_ZAP(DOM_StyleSelector);
|
|
|
|
if (!sel)
|
1998-09-11 23:46:23 +04:00
|
|
|
return NULL;
|
1998-09-11 02:14:52 +04:00
|
|
|
if (selector[0] == '.') {
|
1998-09-11 23:46:23 +04:00
|
|
|
sel->type = SELECTOR_CLASS;
|
|
|
|
selector++;
|
1998-09-11 02:14:52 +04:00
|
|
|
} else if (selector[0] == '#') {
|
1998-09-11 23:46:23 +04:00
|
|
|
sel->type = SELECTOR_ID;
|
|
|
|
selector++;
|
1998-09-11 02:14:52 +04:00
|
|
|
} else {
|
1998-09-11 23:46:23 +04:00
|
|
|
sel->type = SELECTOR_TAG;
|
1998-09-11 02:14:52 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
sel->selector = XP_STRDUP(selector);
|
|
|
|
if (!sel->selector) {
|
1998-09-11 23:46:23 +04:00
|
|
|
DestroySelector(cx, sel);
|
|
|
|
return NULL;
|
1998-09-11 02:14:52 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pseudo) {
|
1998-09-11 23:46:23 +04:00
|
|
|
sel->pseudo = XP_STRDUP(pseudo);
|
|
|
|
if (!sel->pseudo) {
|
|
|
|
DestroySelector(cx, sel);
|
|
|
|
return NULL;
|
|
|
|
}
|
1998-09-11 02:14:52 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return sel;
|
|
|
|
}
|
|
|
|
|
1998-09-10 18:21:43 +04:00
|
|
|
/* the pseudoclass, if any, is stored in a magic attribute */
|
|
|
|
static DOM_StyleToken
|
|
|
|
GetPseudo(JSContext *cx, DOM_Element *element)
|
|
|
|
{
|
|
|
|
DOM_AttributeEntry *entry;
|
|
|
|
|
|
|
|
if (!DOM_GetElementAttribute(cx, element, ":pseudoclass", &entry))
|
1998-09-11 23:46:23 +04:00
|
|
|
/* report error? */
|
|
|
|
return NULL;
|
1998-09-10 18:21:43 +04:00
|
|
|
return entry ? entry->value : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define PSEUDO_MATCHES(p1, p2) (!XP_STRCMP((p1), (p2)))
|
|
|
|
|
1998-09-16 18:45:07 +04:00
|
|
|
#ifdef DEBUG_shaver
|
1998-09-11 23:46:23 +04:00
|
|
|
#define MATCH() \
|
|
|
|
fprintf(stderr, "selector %s:%s matches element %s\n", \
|
|
|
|
sel->selector, sel->pseudo ? sel->pseudo : "", \
|
1998-09-16 18:45:07 +04:00
|
|
|
element->tagName);
|
|
|
|
#else
|
|
|
|
#define MATCH()
|
1998-09-10 18:21:43 +04:00
|
|
|
#endif
|
|
|
|
|
1998-09-16 18:45:07 +04:00
|
|
|
#ifdef DEBUG_shaver
|
1998-09-11 23:46:23 +04:00
|
|
|
#define NO_MATCH() \
|
|
|
|
fprintf(stderr, "selector %s:%s doesn't match element %s\n", \
|
|
|
|
sel->selector, sel->pseudo ? sel->pseudo : "", \
|
1998-09-16 18:45:07 +04:00
|
|
|
element->tagName);
|
|
|
|
#else
|
|
|
|
#define NO_MATCH()
|
1998-09-10 18:21:43 +04:00
|
|
|
#endif
|
|
|
|
|
1998-09-11 02:14:52 +04:00
|
|
|
static JSBool
|
|
|
|
SelectorMatchesToken(JSContext *cx, DOM_StyleSelector *sel,
|
1998-09-11 23:46:23 +04:00
|
|
|
DOM_StyleToken token, DOM_StyleToken pseudo)
|
1998-09-11 02:14:52 +04:00
|
|
|
{
|
|
|
|
/* XXX handle #ID and .class */
|
1998-09-16 18:45:07 +04:00
|
|
|
return !XP_STRCMP(sel->selector, token);
|
1998-09-11 02:14:52 +04:00
|
|
|
}
|
|
|
|
|
1998-09-16 18:45:07 +04:00
|
|
|
#define ELEMENT_IS_TYPE(element, type) \
|
|
|
|
(!XP_STRCMP(((element))->tagName, (type)))
|
|
|
|
|
1998-09-10 18:21:43 +04:00
|
|
|
static JSBool
|
|
|
|
SelectorMatchesElement(JSContext *cx, DOM_Element *element,
|
1998-09-11 23:46:23 +04:00
|
|
|
DOM_StyleSelector *sel)
|
1998-09-10 18:21:43 +04:00
|
|
|
{
|
|
|
|
/* XXX handle class and ID */
|
|
|
|
if (ELEMENT_IS_TYPE(element, sel->selector)) {
|
1998-09-11 23:46:23 +04:00
|
|
|
/* check pseudo, if any */
|
|
|
|
if (sel->pseudo) {
|
1998-09-16 18:45:07 +04:00
|
|
|
DOM_StyleToken elementPseudo = GetPseudo(cx, element);
|
1998-09-11 23:46:23 +04:00
|
|
|
if (PSEUDO_MATCHES(sel->pseudo, elementPseudo)) {
|
|
|
|
MATCH();
|
|
|
|
return JS_TRUE;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
MATCH();
|
|
|
|
return JS_TRUE;
|
|
|
|
}
|
1998-09-10 18:21:43 +04:00
|
|
|
}
|
|
|
|
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 {
|
1998-09-11 23:46:23 +04:00
|
|
|
if (!XP_STRCMP(iter->entry.name, property))
|
|
|
|
return &iter->entry;
|
|
|
|
iter = iter->next;
|
1998-09-10 18:21:43 +04:00
|
|
|
} while (iter);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Find the parent element of appropriate type for given element */
|
|
|
|
static DOM_Element *
|
|
|
|
AncestorOfType(JSContext *cx, DOM_Element *element, DOM_StyleSelector *sel)
|
|
|
|
{
|
|
|
|
do {
|
1998-09-11 23:46:23 +04:00
|
|
|
/* check type */
|
|
|
|
if (SelectorMatchesElement(cx, element, sel))
|
|
|
|
return element;
|
1998-09-16 18:45:07 +04:00
|
|
|
element = (DOM_Element *)element->node.parent;
|
1998-09-10 18:21:43 +04:00
|
|
|
} while (element);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Determine if the given selector is a best-yet match for element, and
|
|
|
|
* recurse/iterate appropriately over enclosing and sibling elements.
|
|
|
|
*/
|
|
|
|
static JSBool
|
|
|
|
CheckSelector(JSContext *cx, DOM_Element *element, DOM_StyleSelector *sel,
|
1998-09-11 23:46:23 +04:00
|
|
|
DOM_StyleToken property, DOM_AttributeEntry **entryp,
|
|
|
|
uintN *best, uintN specificity)
|
1998-09-10 18:21:43 +04:00
|
|
|
{
|
|
|
|
DOM_AttributeEntry *entry;
|
|
|
|
DOM_Element *next;
|
|
|
|
|
|
|
|
/* check self */
|
|
|
|
if (SelectorMatchesElement(cx, element, sel)) {
|
1998-09-11 23:46:23 +04:00
|
|
|
/* if we have rules, maybe get an entry from them */
|
|
|
|
if (sel->rules) {
|
|
|
|
uintN score = SELECTOR_SCORE(sel, specificity);
|
|
|
|
if (score > *best) { /* are we the best so far? */
|
1998-09-16 18:45:07 +04:00
|
|
|
entry = RuleValueFor(cx, sel->rules, property);
|
1998-09-11 23:46:23 +04:00
|
|
|
if (entry) { /* do we have a value for this property? */
|
1998-09-10 18:21:43 +04:00
|
|
|
#ifdef DEBUG_shaver
|
1998-09-11 23:46:23 +04:00
|
|
|
fprintf(stderr, "- score %d, value %s\n",
|
|
|
|
score, entry->value);
|
1998-09-10 18:21:43 +04:00
|
|
|
#endif
|
1998-09-11 23:46:23 +04:00
|
|
|
*best = score;
|
|
|
|
*entryp = entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
1998-09-10 18:21:43 +04:00
|
|
|
|
1998-09-11 23:46:23 +04:00
|
|
|
/* now, check our enclosing selector */
|
|
|
|
if (sel->enclosing) {
|
|
|
|
next = AncestorOfType(cx, element, sel->enclosing);
|
|
|
|
if (next)
|
|
|
|
if (!CheckSelector(cx, next, sel->enclosing, property, entryp,
|
|
|
|
best, specificity + 1))
|
|
|
|
return JS_FALSE;
|
|
|
|
}
|
1998-09-10 18:21:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* check our sibling */
|
|
|
|
if (sel->sibling)
|
1998-09-11 23:46:23 +04:00
|
|
|
if (!CheckSelector(cx, element, sel->sibling, property, entryp,
|
|
|
|
best, specificity))
|
|
|
|
return JS_FALSE;
|
1998-09-10 18:21:43 +04:00
|
|
|
|
|
|
|
return JS_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSBool
|
|
|
|
DOM_StyleGetProperty(JSContext *cx, DOM_StyleDatabase *db,
|
1998-09-16 18:45:07 +04:00
|
|
|
DOM_Node *node, DOM_StyleToken property,
|
1998-09-11 23:46:23 +04:00
|
|
|
DOM_StyleToken pseudo, DOM_AttributeEntry **entryp)
|
1998-09-10 18:21:43 +04:00
|
|
|
{
|
|
|
|
DOM_StyleSelector *sel;
|
|
|
|
DOM_Element *element;
|
|
|
|
uintN best = 0;
|
|
|
|
|
|
|
|
if (node->type != NODE_TYPE_ELEMENT) {
|
1998-09-11 23:46:23 +04:00
|
|
|
element = (DOM_Element *)node->parent;
|
|
|
|
XP_ASSERT(element->node.type == NODE_TYPE_ELEMENT);
|
1998-09-10 18:21:43 +04:00
|
|
|
} else {
|
1998-09-11 23:46:23 +04:00
|
|
|
element = (DOM_Element *)node;
|
1998-09-10 18:21:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
*entryp = NULL;
|
|
|
|
|
1998-09-16 18:45:07 +04:00
|
|
|
sel = GetBaseSelector(cx, db, element->tagName, GetPseudo(cx, element));
|
1998-09-10 18:21:43 +04:00
|
|
|
if (!sel)
|
1998-09-11 23:46:23 +04:00
|
|
|
return JS_TRUE;
|
1998-09-10 18:21:43 +04:00
|
|
|
|
1998-09-11 02:14:52 +04:00
|
|
|
/*
|
|
|
|
* CheckSelector will recursively find the best match for a given
|
|
|
|
* property.
|
|
|
|
*/
|
|
|
|
return CheckSelector(cx, element, sel, property, entryp, &best, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
DOM_StyleSelector *
|
|
|
|
DOM_StyleFindSelector(JSContext *cx, DOM_StyleDatabase *db,
|
1998-09-11 23:46:23 +04:00
|
|
|
DOM_StyleSelector *base, DOM_StyleToken enclosing,
|
|
|
|
DOM_StyleToken pseudo)
|
1998-09-11 02:14:52 +04:00
|
|
|
{
|
|
|
|
DOM_StyleSelector *sel;
|
|
|
|
|
|
|
|
/* looking for the base one */
|
|
|
|
if (!base) {
|
1998-09-11 23:46:23 +04:00
|
|
|
sel = GetBaseSelector(cx, db, enclosing, pseudo);
|
|
|
|
if (!sel)
|
|
|
|
sel = NewSelector(cx, enclosing, pseudo);
|
|
|
|
if (sel)
|
|
|
|
InsertBaseSelector(cx, db, sel);
|
|
|
|
return sel;
|
1998-09-11 02:14:52 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!base->enclosing) {
|
1998-09-11 23:46:23 +04:00
|
|
|
sel = NewSelector(cx, enclosing, pseudo);
|
|
|
|
if (!sel)
|
|
|
|
return NULL;
|
|
|
|
base->enclosing = sel;
|
|
|
|
return sel;
|
1998-09-11 02:14:52 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* check existing enclosing selectors */
|
|
|
|
sel = base->enclosing;
|
|
|
|
do {
|
1998-09-11 23:46:23 +04:00
|
|
|
if (SelectorMatchesToken(cx, sel, enclosing, pseudo))
|
|
|
|
return sel;
|
|
|
|
sel = sel->sibling;
|
1998-09-11 02:14:52 +04:00
|
|
|
} while (sel);
|
|
|
|
|
|
|
|
/* nothing found that matches, so create a new one */
|
|
|
|
sel = NewSelector(cx, enclosing, pseudo);
|
|
|
|
if (!sel)
|
1998-09-11 23:46:23 +04:00
|
|
|
return NULL;
|
1998-09-11 02:14:52 +04:00
|
|
|
sel->sibling = base->enclosing;
|
|
|
|
base->enclosing = sel;
|
|
|
|
|
|
|
|
return sel;
|
1998-09-10 18:21:43 +04:00
|
|
|
}
|