mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-04 02:57:38 +00:00
860 lines
25 KiB
C
860 lines
25 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is Mozilla Communicator client code.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*/
|
|
|
|
/*
|
|
* Glue to connect lib/libdom to lib/layout.
|
|
*/
|
|
|
|
#include "lm.h"
|
|
#include "lm_dom.h"
|
|
#include "layout.h"
|
|
#include "pa_tags.h"
|
|
#include "pa_parse.h"
|
|
#include "intl_csi.h"
|
|
|
|
#ifdef DEBUG_shaver
|
|
#define LOCAL_ASSERT PR_ASSERT
|
|
#else
|
|
#define LOCAL_ASSERT XP_ASSERT
|
|
#endif
|
|
|
|
#ifdef DEBUG_shaver
|
|
/* #define DEBUG_shaver_verbose */
|
|
#define DEBUG_shaver_treegen
|
|
#endif
|
|
|
|
#define LAYLOCKED(code) \
|
|
PR_BEGIN_MACRO \
|
|
LO_LockLayout(); \
|
|
code; \
|
|
LO_UnlockLayout(); \
|
|
PR_END_MACRO
|
|
|
|
JSBool
|
|
lm_CheckNodeDocId(MWContext *context, DOM_HTMLElementPrivate *priv)
|
|
{
|
|
return priv->doc_id == context->doc_id;
|
|
}
|
|
|
|
/* from et_moz.c */
|
|
int
|
|
ET_DOMReflow(MWContext *context, LO_Element *element, PRBool reflow,
|
|
int32 doc_id);
|
|
|
|
static JSBool
|
|
lm_DOMInsertBefore(JSContext *cx, DOM_Node *node, DOM_Node *child,
|
|
DOM_Node *ref, JSBool before)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
lm_DOMReplaceChild(JSContext *cx, DOM_Node *node, DOM_Node *child,
|
|
DOM_Node *old, JSBool before)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
lm_DOMRemoveChild(JSContext *cx, DOM_Node *node, DOM_Node *child,
|
|
JSBool before)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
lm_DOMAppendChild(JSContext *cx, DOM_Node *node, DOM_Node *child,
|
|
JSBool before)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSObject *
|
|
lm_DOMReflectNode(JSContext *cx, DOM_Node *node)
|
|
{
|
|
MochaDecoder *decoder = JS_GetPrivate(cx, JS_GetGlobalObject(cx));
|
|
|
|
if (!decoder || !node)
|
|
return NULL;
|
|
|
|
switch(node->type) {
|
|
case NODE_TYPE_ELEMENT:
|
|
return DOM_NewElementObject(cx, (DOM_Element *)node);
|
|
case NODE_TYPE_TEXT:
|
|
return DOM_NewTextObject(cx, (DOM_Text *)node);
|
|
case NODE_TYPE_ATTRIBUTE:
|
|
return DOM_NewAttributeObject(cx, (DOM_Attribute *)node);
|
|
default:
|
|
XP_ASSERT((0 && "unknown DOM type"));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
DOM_NodeOps lm_NodeOps = {
|
|
lm_DOMInsertBefore, lm_DOMReplaceChild, lm_DOMRemoveChild,
|
|
lm_DOMAppendChild, DOM_DestroyNodeStub, lm_DOMReflectNode
|
|
};
|
|
|
|
/*
|
|
* Signal an exception on doc_id mismatch. Must be called with layout lock
|
|
* held (unlocks on failure).
|
|
*/
|
|
#define CHECK_DOC_ID(context, priv) \
|
|
PR_BEGIN_MACRO \
|
|
if (!lm_CheckNodeDocId(context, priv)) { \
|
|
DOM_SignalException(cx, DOM_WRONG_DOCUMENT_ERR); \
|
|
LO_UnlockLayout(); \
|
|
return JS_FALSE; \
|
|
} \
|
|
PR_END_MACRO
|
|
|
|
static JSBool
|
|
lm_DOMSetAttributes(JSContext *cx, DOM_Element *element, const char *name,
|
|
const char *value)
|
|
{
|
|
JSBool matched = JS_FALSE;
|
|
MochaDecoder *decoder;
|
|
MWContext *context;
|
|
DOM_HTMLElementPrivate *priv;
|
|
void *ele = NULL;
|
|
lo_DocState *doc;
|
|
|
|
decoder = (MochaDecoder *)JS_GetPrivate(cx, JS_GetGlobalObject(cx));
|
|
context = decoder->window_context;
|
|
doc = (lo_FetchTopState(context->doc_id))->doc_state;
|
|
priv = (DOM_HTMLElementPrivate *)element->node.data;
|
|
|
|
switch(priv->tagtype) {
|
|
case P_TABLE_DATA: {
|
|
LO_Element *iter, *start, *end;
|
|
lo_TableCell *cell;
|
|
|
|
LO_LockLayout();
|
|
CHECK_DOC_ID(context, priv);
|
|
cell = (lo_TableCell *)priv->ele_start;
|
|
if (!cell)
|
|
goto out_unlock;
|
|
|
|
if (!XP_STRCASECMP("valign", name)) {
|
|
/* tweak valign */
|
|
} else if(!XP_STRCASECMP("halign", name)) {
|
|
/* tweak halign */
|
|
} else if(!XP_STRCASECMP("bgcolor", name)) {
|
|
LO_Color rgb;
|
|
start = cell->cell->cell_list;
|
|
end = cell->cell->cell_list_end;
|
|
if (!start ||
|
|
!LO_ParseRGB((char *)value, &rgb.red, &rgb.green, &rgb.blue))
|
|
goto out_unlock;
|
|
for (iter = start; iter && iter != end; iter = iter->lo_any.next)
|
|
lo_SetColor(iter, &rgb, doc, TRUE);
|
|
if (iter != start)
|
|
lo_SetColor(start, &rgb, doc, TRUE);
|
|
} else {
|
|
/* No match */
|
|
matched = JS_FALSE;
|
|
LO_UnlockLayout();
|
|
break;
|
|
}
|
|
LO_UnlockLayout();
|
|
matched = JS_TRUE;
|
|
break;
|
|
}
|
|
default:;
|
|
}
|
|
|
|
if (!matched) {
|
|
DOM_SignalException(cx, DOM_INVALID_NAME_ERR);
|
|
return JS_FALSE;
|
|
}
|
|
|
|
return (JSBool)ET_DOMReflow(context, (LO_Element *)ele, PR_TRUE,
|
|
decoder->doc_id);
|
|
out_unlock:
|
|
LO_UnlockLayout();
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static void
|
|
lm_BreakLayoutNodeLinkRecurse(DOM_Node *node)
|
|
{
|
|
DOM_HTMLElementPrivate *priv;
|
|
if (node->type == NODE_TYPE_TEXT ||
|
|
node->type == NODE_TYPE_ELEMENT) {
|
|
priv = (DOM_HTMLElementPrivate *)node->data;
|
|
if (priv)
|
|
priv->ele_start = priv->ele_end = NULL;
|
|
}
|
|
|
|
for (node = node->child; node; node = node->sibling)
|
|
lm_BreakLayoutNodeLinkRecurse(node);
|
|
}
|
|
|
|
void
|
|
lm_DestroyDocumentNodes(MWContext *context)
|
|
{
|
|
JSContext *cx;
|
|
lo_TopState *top;
|
|
cx = context->mocha_context;
|
|
top = lo_FetchTopState(context->doc_id);
|
|
|
|
/* XXX LO_LockLayout(); */
|
|
lm_BreakLayoutNodeLinkRecurse(top->top_node);
|
|
/* XXX LO_UnlockLayout(); */
|
|
DOM_DestroyTree(cx, top->top_node);
|
|
|
|
}
|
|
|
|
DOM_ElementOps lm_ElementOps = {
|
|
lm_DOMSetAttributes, DOM_GetAttributeStub, DOM_GetNumAttrsStub
|
|
};
|
|
|
|
/*
|
|
* Handle text alterations. Most of the time, we're just carving up
|
|
* new text blocks, but sometimes text inside stuff is magic, like
|
|
* <TITLE> and maybe <HREF>.
|
|
*/
|
|
static JSBool
|
|
lm_CDataOp(JSContext *cx, DOM_CharacterData *cdata, DOM_CDataOperationCode op)
|
|
{
|
|
JSBool ok;
|
|
DOM_Node *node;
|
|
MWContext *context;
|
|
MochaDecoder *decoder;
|
|
char *data;
|
|
LO_TextBlock *text;
|
|
|
|
node = cdata->node.parent;
|
|
data = cdata->data;
|
|
|
|
decoder = (MochaDecoder *)JS_GetPrivate(cx, JS_GetGlobalObject(cx));
|
|
context = decoder->window_context;
|
|
|
|
if (node->type == NODE_TYPE_ELEMENT) {
|
|
switch(ELEMENT_PRIV(node)->tagtype) {
|
|
case P_TITLE:
|
|
/*
|
|
* XXX clean up the text, find the current charset, make
|
|
* sure we're not a subdoc, etc. All this logic lives in
|
|
* lo_process_title_tag, and should be refactored.
|
|
*/
|
|
LAYLOCKED(
|
|
CHECK_DOC_ID(context, ELEMENT_PRIV(node));
|
|
FE_SetDocTitle(context, data);
|
|
);
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
text = (LO_TextBlock *)ELEMENT_PRIV(cdata)->ele_start;
|
|
if (!text) {
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "no data for text node on %s %s\n",
|
|
((DOM_Element *)node)->tagName, node->name);
|
|
#endif
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Tell layout to use the new text instead.
|
|
*/
|
|
LAYLOCKED(ok = lo_ChangeText(text, data));
|
|
if (!ok)
|
|
return JS_FALSE;
|
|
|
|
return (JSBool)ET_DOMReflow(context, (LO_Element *)text, PR_TRUE,
|
|
decoder->doc_id);
|
|
|
|
}
|
|
|
|
static DOM_Node *
|
|
lm_NodeForTag(JSContext *cx, PA_Tag *tag, DOM_Node *current,
|
|
MWContext *context, int16 csid)
|
|
{
|
|
DOM_Node *node;
|
|
DOM_HTMLElementPrivate *elepriv;
|
|
char **names, **values;
|
|
uintN nattrs;
|
|
|
|
if (current->type == NODE_TYPE_TEXT)
|
|
/*
|
|
* it's going to get skipped back over by the HTMLPushNode stuff,
|
|
* so build a tag as though we were pushing on its parent.
|
|
*/
|
|
current = current->parent;
|
|
|
|
switch (tag->type) {
|
|
case P_TEXT:
|
|
if (current->type != NODE_TYPE_ELEMENT)
|
|
return NULL;
|
|
/* create Text node */
|
|
node = (DOM_Node *)DOM_NewText((char *)tag->data, tag->data_len,
|
|
lm_CDataOp, &lm_NodeOps);
|
|
if (!node)
|
|
return NULL;
|
|
break;
|
|
#if 0 /* Urgh...we don't reflect comments! */
|
|
case P_COMMENT:
|
|
/* create Comment node */
|
|
break;
|
|
#endif
|
|
case P_UNKNOWN:
|
|
return NULL;
|
|
default:
|
|
/* just a regular old element */
|
|
node = (DOM_Node *)
|
|
DOM_NewElement(PA_TagString(tag->type), &lm_ElementOps,
|
|
(char *)PA_FetchParamValue(tag, "name", csid),
|
|
(char *)PA_FetchParamValue(tag, "class", csid),
|
|
(char *)PA_FetchParamValue(tag, "id", csid),
|
|
&lm_NodeOps);
|
|
if (!node)
|
|
return NULL;
|
|
nattrs = PA_FetchAllNameValues(tag, &names, &values, csid);
|
|
if (!DOM_SetElementAttributes(cx, (DOM_Element *)node,
|
|
(const char**)names,
|
|
(const char **)values, nattrs)) {
|
|
/* XXX free data */
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
elepriv = XP_NEW_ZAP(DOM_HTMLElementPrivate);
|
|
if (!elepriv) {
|
|
XP_FREE(node);
|
|
return NULL;
|
|
}
|
|
elepriv->tagtype = tag->type;
|
|
elepriv->doc_id = context->doc_id;
|
|
node->data = elepriv;
|
|
return node;
|
|
}
|
|
|
|
#ifdef DEBUG_shaver
|
|
static int LM_Node_indent;
|
|
#endif
|
|
|
|
DOM_Node *
|
|
DOM_HTMLPushNode(DOM_Node *node, DOM_Node *parent)
|
|
{
|
|
LOCAL_ASSERT(parent->type == NODE_TYPE_ELEMENT);
|
|
|
|
/* XXX factor this information out, and share with parser */
|
|
if (node->type == NODE_TYPE_ELEMENT) {
|
|
/*
|
|
* Because HTML is such a cool markup language, we have to
|
|
* worry about tags that are implicitly closed by new tags.
|
|
* There are a couple of cases:
|
|
*
|
|
* <P> et alii: they close the ``enclosing'' one of the same type,
|
|
* so that you get:
|
|
* <SOME>
|
|
* <P>
|
|
* <P>
|
|
* and not
|
|
* <SOME>
|
|
* <P>
|
|
* <P>
|
|
*
|
|
* <DL>/<DT>/<DD>: these close up to an enclosing DL element, so
|
|
* that you get:
|
|
* <DL>
|
|
* <DT>
|
|
* <DD>
|
|
* and not
|
|
* <DL>
|
|
* <DT>
|
|
* <DD>
|
|
* We could enforce further stricture here, I guess, by only
|
|
* allowing <DT> and <DD> elements as children of <DL>, etc.
|
|
* Maybe later.
|
|
*/
|
|
TagType type = ELEMENT_PRIV(node)->tagtype;
|
|
DOM_Node *iter;
|
|
TagType breakType = P_UNKNOWN;
|
|
JSBool breakIsNewParent = JS_FALSE;
|
|
|
|
switch(type) {
|
|
case P_DESC_TITLE:
|
|
case P_DESC_TEXT:
|
|
breakType = P_DESC_LIST;
|
|
breakIsNewParent = JS_TRUE;
|
|
/* fallthrough */
|
|
case P_PARAGRAPH:
|
|
case P_LIST_ITEM:
|
|
case P_HEADER_1:
|
|
case P_HEADER_2:
|
|
case P_HEADER_3:
|
|
case P_HEADER_4:
|
|
case P_HEADER_5:
|
|
case P_HEADER_6:
|
|
case P_ANCHOR:
|
|
case P_OPTION:
|
|
/* these don't nest with themselves */
|
|
iter = parent;
|
|
if (breakType == P_UNKNOWN)
|
|
breakType = type;
|
|
while (iter &&
|
|
iter->parent->type == NODE_TYPE_ELEMENT) {
|
|
/* find the enclosing tag for this type */
|
|
if (ELEMENT_PRIV(iter)->tagtype == breakType) {
|
|
if (breakIsNewParent)
|
|
parent = iter;
|
|
else
|
|
parent = iter->parent;
|
|
break;
|
|
}
|
|
/* XXX CLOSE_NODE(iter); */
|
|
iter = iter->parent;
|
|
}
|
|
#ifdef DEBUG_shaver
|
|
LM_Node_indent -= 2;
|
|
#endif
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_shaver
|
|
LM_Node_indent -= 2;
|
|
#endif
|
|
#ifdef DEBUG_shaver_verbose
|
|
{
|
|
TagType dbgtype = 0, partype = 0; /* text */
|
|
if (node->type == NODE_TYPE_ELEMENT)
|
|
dbgtype = ELEMENT_PRIV(node)->tagtype;
|
|
if (parent->type == NODE_TYPE_ELEMENT)
|
|
partype = ELEMENT_PRIV(parent)->tagtype;
|
|
fprintf(stderr, "%*s<%s %s> on <%s %s>\n", LM_Node_indent, "",
|
|
PA_TagString(dbgtype), node->name ? node->name : "",
|
|
PA_TagString(partype), parent->name ? parent->name : "");
|
|
if (dbgtype)
|
|
LM_Node_indent += 2;
|
|
}
|
|
#endif
|
|
|
|
if (!DOM_PushNode(node, parent))
|
|
return NULL;
|
|
LOCAL_ASSERT(node->parent == parent);
|
|
if (node->type == NODE_TYPE_TEXT ||
|
|
(node->type == NODE_TYPE_ELEMENT &&
|
|
lo_IsEmptyTag(ELEMENT_PRIV(node)->tagtype))) {
|
|
return parent;
|
|
} else {
|
|
return node;
|
|
}
|
|
|
|
}
|
|
|
|
void /* DOM_Node */ *
|
|
LM_ReflectTagNode(PA_Tag *tag, void *doc_state, MWContext *context)
|
|
{
|
|
INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(context);
|
|
int16 csid = INTL_GetCSIWinCSID(c);
|
|
lo_DocState *doc = (lo_DocState *)doc_state;
|
|
DOM_Node *node;
|
|
JSContext *cx;
|
|
|
|
cx = context->mocha_context;
|
|
|
|
if (!TOP_NODE(doc)) {
|
|
DOM_HTMLElementPrivate *elepriv;
|
|
#if 0
|
|
node = DOM_NewDocument(context, doc);
|
|
#else
|
|
node = XP_NEW_ZAP(DOM_Node);
|
|
#endif
|
|
if (!node)
|
|
return NULL;
|
|
node->type = NODE_TYPE_DOCUMENT;
|
|
node->name = XP_STRDUP("#document");
|
|
TOP_NODE(doc) = node;
|
|
|
|
/* now put a single <HTML> element as child */
|
|
node = (DOM_Node *)DOM_NewElement ("HTML", &lm_ElementOps,
|
|
NULL, NULL, NULL, &lm_NodeOps);
|
|
if (!node)
|
|
return NULL;
|
|
elepriv = XP_NEW_ZAP(DOM_HTMLElementPrivate);
|
|
if (!elepriv) {
|
|
XP_FREE(node);
|
|
return NULL;
|
|
}
|
|
elepriv->tagtype = P_HTML;
|
|
elepriv->doc_id = context->doc_id;
|
|
|
|
node->data = elepriv;
|
|
TOP_NODE(doc)->child = node;
|
|
node->parent = TOP_NODE(doc);
|
|
CURRENT_NODE(doc) = node;
|
|
ACTIVE_NODE(doc) = node;
|
|
}
|
|
|
|
if (!tag) {
|
|
CURRENT_NODE(doc) = TOP_NODE(doc)->child;
|
|
ACTIVE_NODE(doc) = CURRENT_NODE(doc);
|
|
return CURRENT_NODE(doc);
|
|
}
|
|
|
|
if (tag->type == P_UNKNOWN ||
|
|
tag->type == P_HTML)
|
|
return CURRENT_NODE(doc);
|
|
|
|
if (tag->is_end) {
|
|
DOM_Node *last_node, *closing_node;
|
|
LOCAL_ASSERT(CURRENT_NODE(doc)->type == NODE_TYPE_ELEMENT ||
|
|
CURRENT_NODE(doc)->type == NODE_TYPE_TEXT);
|
|
closing_node = CURRENT_NODE(doc);
|
|
last_node = (DOM_Node *)DOM_HTMLPopElementByType(tag->type,
|
|
(DOM_Element *)CURRENT_NODE(doc));
|
|
if (!last_node)
|
|
return NULL;
|
|
LOCAL_ASSERT(last_node->parent &&
|
|
last_node->type == NODE_TYPE_ELEMENT);
|
|
/*
|
|
* The caller is interested in the node that we just closed.
|
|
*/
|
|
CURRENT_NODE(doc) = last_node;
|
|
ACTIVE_NODE(doc) = CURRENT_NODE(doc);
|
|
return closing_node;
|
|
}
|
|
|
|
node = lm_NodeForTag(cx, tag, CURRENT_NODE(doc), context, csid);
|
|
if (node) {
|
|
DOM_Node *newCurrent = DOM_HTMLPushNode(node, CURRENT_NODE(doc));
|
|
if (!newCurrent) {
|
|
#ifdef DEBUG_shaver
|
|
fprintf(stderr, "bad push of node %d for tag %d\n",
|
|
node->type, tag->type);
|
|
#endif
|
|
if (node->ops && node->ops->destroyNode)
|
|
node->ops->destroyNode(context->mocha_context,
|
|
node);
|
|
return NULL;
|
|
}
|
|
ACTIVE_NODE(doc) = node;
|
|
LOCAL_ASSERT(node->parent &&
|
|
node->parent->type != NODE_TYPE_DOCUMENT);
|
|
CURRENT_NODE(doc) = newCurrent;
|
|
|
|
} else {
|
|
}
|
|
XP_ASSERT(!CURRENT_NODE(doc)->parent ||
|
|
CURRENT_NODE(doc)->parent->type != NODE_TYPE_TEXT);
|
|
|
|
return node;
|
|
}
|
|
|
|
JSBool
|
|
lm_DOMInit(MochaDecoder *decoder)
|
|
{
|
|
JSObject *win = decoder->window_object;
|
|
JSContext *cx = decoder->js_context;
|
|
|
|
return DOM_Init(cx, win);
|
|
}
|
|
|
|
JSObject *
|
|
lm_DOMGetDocumentElement(MochaDecoder *decoder, JSObject *docobj)
|
|
{
|
|
JSObject *obj;
|
|
JSDocument *doc;
|
|
lo_TopState *top_state;
|
|
|
|
doc = JS_GetPrivate(decoder->js_context, docobj);
|
|
if (!doc)
|
|
return NULL;
|
|
obj = doc->dom_documentElement;
|
|
if (obj)
|
|
return obj;
|
|
|
|
top_state = lo_GetMochaTopState(decoder->window_context);
|
|
if (!top_state)
|
|
return NULL;
|
|
|
|
/* XXX really create a DocumentElement
|
|
obj = NewDocument(decoder, docobj, doc);
|
|
*/
|
|
|
|
obj = DOM_NewNodeObject(decoder->js_context,
|
|
top_state->top_node);
|
|
|
|
doc->dom_documentElement = obj;
|
|
return obj;
|
|
}
|
|
|
|
/*
|
|
* Close the element, popping it (and enclosing elements that are marked
|
|
* closed) if it's the current element, otherwise marking it closed and
|
|
* returning.
|
|
*/
|
|
|
|
DOM_Element *
|
|
DOM_HTMLPopElementByType(TagType type, DOM_Element *element)
|
|
{
|
|
DOM_Node *closing;
|
|
#ifdef DEBUG_shaver_verbose
|
|
int new_indent = LM_Node_indent;
|
|
#endif
|
|
|
|
LOCAL_ASSERT(element->node.type != NODE_TYPE_DOCUMENT);
|
|
if (element->node.type == NODE_TYPE_DOCUMENT)
|
|
return element;
|
|
|
|
LOCAL_ASSERT(type != P_HTML && type != NODE_TYPE_TEXT);
|
|
|
|
closing = (DOM_Node *)element;
|
|
|
|
if (closing->type == NODE_TYPE_TEXT)
|
|
/* really, we're closing the enclosing parent */
|
|
closing = closing->parent;
|
|
|
|
/* if we don't match, just mark it closed */
|
|
if (ELEMENT_PRIV(closing)->tagtype != type) {
|
|
#ifdef DEBUG_shaver_treegen
|
|
fprintf(stderr, "</%s> doesn't close <%s> ", PA_TagString(type),
|
|
PA_TagString(ELEMENT_PRIV(closing)->tagtype));
|
|
#endif
|
|
/* find it, mark it closed, and return. */
|
|
do {
|
|
#ifdef DEBUG_shaver_treegen
|
|
fprintf(stderr, "skipping <%s>",
|
|
PA_TagString(ELEMENT_PRIV(closing)->tagtype));
|
|
#endif
|
|
closing = closing->parent;
|
|
} while (closing->type == NODE_TYPE_ELEMENT &&
|
|
ELEMENT_PRIV(closing)->tagtype != type &&
|
|
ELEMENT_PRIV(closing)->tagtype != P_HTML);
|
|
if (closing->type == NODE_TYPE_ELEMENT &&
|
|
ELEMENT_PRIV(closing)->tagtype == type) {
|
|
#ifdef DEBUG_shaver_treegen
|
|
fprintf(stderr, "found <%s>, marking closed\n",
|
|
PA_TagString(ELEMENT_PRIV(closing)->tagtype));
|
|
#endif
|
|
LM_SetNodeFlags(closing, NODE_CLOSED);
|
|
} else {
|
|
#ifdef DEBUG_shaver_treegen
|
|
fprintf(stderr, "didn't find <%s>\n", PA_TagString(type));
|
|
#endif
|
|
}
|
|
return element;
|
|
}
|
|
|
|
/*
|
|
* This matches, so close it off.
|
|
* We should call LO_CloseNode so that it can close layers and the like
|
|
* as appropriate.
|
|
*/
|
|
#ifdef DEBUG_shaver_treegen
|
|
fprintf(stderr, "closing matched <%s> ", PA_TagString(type));
|
|
#endif
|
|
closing = element->node.parent;
|
|
if (ELEMENT_PRIV(closing)->flags & NODE_CLOSED) {
|
|
do {
|
|
#ifdef DEBUG_shaver_treegen
|
|
fprintf(stderr, "<%s> already closed ",
|
|
PA_TagString(ELEMENT_PRIV(closing)->tagtype));
|
|
#endif
|
|
LM_ClearNodeFlags(closing, NODE_CLOSED);
|
|
/* XXX CLOSE_NODE(closing) */
|
|
closing = closing->parent;
|
|
LOCAL_ASSERT(closing);
|
|
} while (closing->type == NODE_TYPE_ELEMENT &&
|
|
ELEMENT_PRIV(closing)->tagtype != P_HTML &&
|
|
(ELEMENT_PRIV(closing)->flags & NODE_CLOSED));
|
|
}
|
|
|
|
fputs("\n", stderr);
|
|
|
|
return (DOM_Element *)closing;
|
|
}
|
|
|
|
JSBool
|
|
LM_SetNodeFlags(DOM_Node *node, uint32 flags)
|
|
{
|
|
DOM_HTMLElementPrivate *priv;
|
|
XP_ASSERT(PR_CurrentThread() == mozilla_thread);
|
|
if (!(node->type == NODE_TYPE_ELEMENT ||
|
|
node->type == NODE_TYPE_TEXT))
|
|
return JS_FALSE;
|
|
priv = ELEMENT_PRIV(node);
|
|
if (!priv)
|
|
return JS_FALSE;
|
|
priv->flags |= flags;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSBool
|
|
LM_ClearNodeFlags(DOM_Node *node, uint32 flags)
|
|
{
|
|
DOM_HTMLElementPrivate *priv = ELEMENT_PRIV(node);
|
|
XP_ASSERT(PR_CurrentThread() == mozilla_thread);
|
|
if (!(node->type == NODE_TYPE_ELEMENT ||
|
|
node->type == NODE_TYPE_TEXT) ||
|
|
!priv)
|
|
return JS_FALSE;
|
|
return JS_FALSE;
|
|
priv->flags &= ~flags;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
#include "laystyle.h"
|
|
#define IMAGE_DEF_ANCHOR_BORDER 2
|
|
#define IMAGE_DEF_VERTICAL_SPACE 0
|
|
|
|
DOM_StyleDatabase *
|
|
DOMMOZ_NewStyleDatabase(JSContext *cx, lo_DocState *state)
|
|
{
|
|
DOM_StyleDatabase *db;
|
|
LO_Color visitCol, linkCol;
|
|
DOM_StyleSelector *sel, *imgsel;
|
|
DOM_AttributeEntry *entry;
|
|
lo_TopState *top = state->top_state;
|
|
|
|
/*
|
|
* Install default rules.
|
|
* In an ideal world (perhaps 5.0?), we would parse .netscape/ua.css
|
|
* at startup and keep the JSSS style buffer around for execution
|
|
* right here. That would be very cool in many ways, including the
|
|
* fact that people could have ua.css at all. We might want to
|
|
* make the weighting stuff work correctly at the same time, too,
|
|
* but I don't think it's vital.
|
|
*/
|
|
db = DOM_NewStyleDatabase(cx);
|
|
if (!db)
|
|
return NULL;
|
|
|
|
linkCol.red = STATE_UNVISITED_ANCHOR_RED(state);
|
|
linkCol.green = STATE_UNVISITED_ANCHOR_GREEN(state);
|
|
linkCol.blue = STATE_UNVISITED_ANCHOR_BLUE(state);
|
|
visitCol.red = STATE_VISITED_ANCHOR_RED(state);
|
|
visitCol.green = STATE_VISITED_ANCHOR_GREEN(state);
|
|
visitCol.blue = STATE_VISITED_ANCHOR_BLUE(state);
|
|
|
|
top->style_db = db;
|
|
|
|
sel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_TAG,
|
|
"A", NULL, "link");
|
|
if (!sel)
|
|
goto error;
|
|
|
|
#define SET_DEFAULT_VALUE(name, value) \
|
|
entry = DOM_StyleAddRule(cx, db, sel, name, "default"); \
|
|
if (!entry) \
|
|
goto error; \
|
|
entry->dirty = JS_FALSE; \
|
|
entry->data = value;
|
|
|
|
/* A:link { color:prefLinkColor } */
|
|
SET_DEFAULT_VALUE(COLOR_STYLE, *(uint32*)&linkCol);
|
|
|
|
/* A:link { text-decoration:underline } */
|
|
if (lo_underline_anchors() &&
|
|
!DOM_StyleAddRule(cx, db, sel, TEXTDECORATION_STYLE, "underline"))
|
|
goto error;
|
|
|
|
sel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_TAG,
|
|
"A", NULL, "visited");
|
|
if (!sel)
|
|
goto error;
|
|
|
|
/* A:visited { color:prefVisitedLinkColor } */
|
|
SET_DEFAULT_VALUE(COLOR_STYLE, *(uint32*)&visitCol);
|
|
|
|
/* A:visited { text-decoration:underline } */
|
|
if (lo_underline_anchors() &&
|
|
!DOM_StyleAddRule(cx, db, sel, TEXTDECORATION_STYLE, "underline"))
|
|
goto error;
|
|
|
|
/* set styles for IMG within A:link and A:visited */
|
|
/* XXX should set (and teach layout about) borderTop/Bottom, etc. */
|
|
imgsel = DOM_StyleFindSelectorFull(cx, db, NULL, SELECTOR_TAG,
|
|
"IMG", NULL, NULL);
|
|
if (!imgsel)
|
|
goto error;
|
|
|
|
/* set border styles for ``A:link IMG'' */
|
|
sel = DOM_StyleFindSelectorFull(cx, db, imgsel, SELECTOR_TAG,
|
|
"A", NULL, "link");
|
|
if (!sel)
|
|
goto error;
|
|
SET_DEFAULT_VALUE(BORDERWIDTH_STYLE, IMAGE_DEF_ANCHOR_BORDER);
|
|
SET_DEFAULT_VALUE(PADDING_STYLE, IMAGE_DEF_VERTICAL_SPACE);
|
|
|
|
/* set border styles for ``A:visited IMG'' */
|
|
sel = DOM_StyleFindSelectorFull(cx, db, imgsel, SELECTOR_TAG,
|
|
"A", NULL, "visited");
|
|
if (!sel)
|
|
goto error;
|
|
SET_DEFAULT_VALUE(BORDERWIDTH_STYLE, IMAGE_DEF_ANCHOR_BORDER);
|
|
SET_DEFAULT_VALUE(PADDING_STYLE, IMAGE_DEF_VERTICAL_SPACE);
|
|
|
|
#ifdef DEBUG_shaver
|
|
fprintf(stderr, "successfully added all default rules to db %p\n",
|
|
db);
|
|
#endif
|
|
return db;
|
|
|
|
error:
|
|
if (db)
|
|
DOM_DestroyStyleDatabase(cx, db);
|
|
return NULL;
|
|
}
|
|
|
|
DOM_StyleDatabase *
|
|
DOM_StyleDatabaseFromContext(JSContext *cx)
|
|
{
|
|
MochaDecoder *decoder;
|
|
lo_TopState *top;
|
|
lo_DocState *state;
|
|
DOM_StyleDatabase *db = NULL;
|
|
|
|
if (!cx)
|
|
return NULL;
|
|
|
|
decoder = JS_GetPrivate(cx, JS_GetGlobalObject(cx));
|
|
if (!decoder)
|
|
return NULL;
|
|
|
|
LO_LockLayout();
|
|
top = lo_FetchTopState(decoder->window_context->doc_id);
|
|
if (!top)
|
|
goto out;
|
|
|
|
if (top->style_db) {
|
|
LO_UnlockLayout();
|
|
return (DOM_StyleDatabase *)top->style_db;
|
|
}
|
|
|
|
state = top->doc_state;
|
|
if (!state)
|
|
goto out;
|
|
|
|
db = DOMMOZ_NewStyleDatabase(cx, state);
|
|
top->style_db = db;
|
|
|
|
out:
|
|
LO_UnlockLayout();
|
|
return db;
|
|
}
|
|
|