0,0 → 1,3312 |
/* |
* Copyright 2005 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk> |
* Copyright 2006 Richard Wilson <info@tinct.net> |
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> |
* |
* This file is part of NetSurf, http://www.netsurf-browser.org/ |
* |
* NetSurf is free software; you can redistribute it and/or modify |
* it under the terms of the GNU General Public License as published by |
* the Free Software Foundation; version 2 of the License. |
* |
* NetSurf is distributed in the hope that it will be useful, |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
* GNU General Public License for more details. |
* |
* You should have received a copy of the GNU General Public License |
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
*/ |
|
/** \file |
* Conversion of XML tree to box tree (implementation). |
*/ |
|
#include <assert.h> |
#include <ctype.h> |
#include <stdio.h> |
#include <stdbool.h> |
#include <stdlib.h> |
#include <string.h> |
#include <strings.h> |
#include "utils/config.h" |
#include "content/content_protected.h" |
#include "css/css.h" |
#include "css/utils.h" |
#include "css/select.h" |
#include "desktop/options.h" |
#include "render/box.h" |
#include "render/form.h" |
#include "render/html_internal.h" |
#include "utils/corestrings.h" |
#include "utils/locale.h" |
#include "utils/log.h" |
#include "utils/messages.h" |
#include "utils/schedule.h" |
#include "utils/talloc.h" |
#include "utils/url.h" |
#include "utils/utils.h" |
|
/** |
* Context for box tree construction |
*/ |
struct box_construct_ctx { |
html_content *content; /**< Content we're constructing for */ |
|
dom_node *n; /**< Current node to process */ |
|
struct box *root_box; /**< Root box in the tree */ |
|
box_construct_complete_cb cb; /**< Callback to invoke on completion */ |
|
int *bctx; /**< talloc context */ |
}; |
|
/** |
* Transient properties for construction of current node |
*/ |
struct box_construct_props { |
/** Style from which to inherit, or NULL if none */ |
const css_computed_style *parent_style; |
/** Current link target, or NULL if none */ |
nsurl *href; |
/** Current frame target, or NULL if none */ |
const char *target; |
/** Current title attribute, or NULL if none */ |
const char *title; |
/** Identity of the current block-level container */ |
struct box *containing_block; |
/** Current container for inlines, or NULL if none |
* \note If non-NULL, will be the last child of containing_block */ |
struct box *inline_container; |
/** Whether the current node is the root of the DOM tree */ |
bool node_is_root; |
}; |
|
static const content_type image_types = CONTENT_IMAGE; |
|
/* the strings are not important, since we just compare the pointers */ |
const char *TARGET_SELF = "_self"; |
const char *TARGET_PARENT = "_parent"; |
const char *TARGET_TOP = "_top"; |
const char *TARGET_BLANK = "_blank"; |
|
static void convert_xml_to_box(struct box_construct_ctx *ctx); |
static bool box_construct_element(struct box_construct_ctx *ctx, |
bool *convert_children); |
static void box_construct_element_after(dom_node *n, html_content *content); |
static bool box_construct_text(struct box_construct_ctx *ctx); |
static css_select_results * box_get_style(html_content *c, |
const css_computed_style *parent_style, dom_node *n); |
static void box_text_transform(char *s, unsigned int len, |
enum css_text_transform_e tt); |
#define BOX_SPECIAL_PARAMS dom_node *n, html_content *content, \ |
struct box *box, bool *convert_children |
static bool box_a(BOX_SPECIAL_PARAMS); |
static bool box_body(BOX_SPECIAL_PARAMS); |
static bool box_br(BOX_SPECIAL_PARAMS); |
static bool box_image(BOX_SPECIAL_PARAMS); |
static bool box_textarea(BOX_SPECIAL_PARAMS); |
static bool box_select(BOX_SPECIAL_PARAMS); |
static bool box_input(BOX_SPECIAL_PARAMS); |
static bool box_input_text(BOX_SPECIAL_PARAMS, bool password); |
static bool box_button(BOX_SPECIAL_PARAMS); |
static bool box_frameset(BOX_SPECIAL_PARAMS); |
static bool box_create_frameset(struct content_html_frames *f, dom_node *n, |
html_content *content); |
static bool box_select_add_option(struct form_control *control, dom_node *n); |
static bool box_noscript(BOX_SPECIAL_PARAMS); |
static bool box_object(BOX_SPECIAL_PARAMS); |
static bool box_embed(BOX_SPECIAL_PARAMS); |
static bool box_pre(BOX_SPECIAL_PARAMS); |
static bool box_iframe(BOX_SPECIAL_PARAMS); |
static bool box_get_attribute(dom_node *n, const char *attribute, |
void *context, char **value); |
static struct frame_dimension *box_parse_multi_lengths(const char *s, |
unsigned int *count); |
|
/* element_table must be sorted by name */ |
struct element_entry { |
char name[10]; /* element type */ |
bool (*convert)(BOX_SPECIAL_PARAMS); |
}; |
static const struct element_entry element_table[] = { |
{"a", box_a}, |
{"body", box_body}, |
{"br", box_br}, |
{"button", box_button}, |
{"embed", box_embed}, |
{"frameset", box_frameset}, |
{"iframe", box_iframe}, |
{"image", box_image}, |
{"img", box_image}, |
{"input", box_input}, |
{"noscript", box_noscript}, |
{"object", box_object}, |
{"pre", box_pre}, |
{"select", box_select}, |
{"textarea", box_textarea} |
}; |
#define ELEMENT_TABLE_COUNT (sizeof(element_table) / sizeof(element_table[0])) |
|
/** |
* Construct a box tree from an xml tree and stylesheets. |
* |
* \param n xml tree |
* \param c content of type CONTENT_HTML to construct box tree in |
* \param cb callback to report conversion completion |
* \return netsurf error code indicating status of call |
*/ |
|
nserror dom_to_box(dom_node *n, html_content *c, box_construct_complete_cb cb) |
{ |
struct box_construct_ctx *ctx; |
|
if (c->bctx == NULL) { |
/* create a context allocation for this box tree */ |
c->bctx = talloc_zero(0, int); |
if (c->bctx == NULL) { |
return NSERROR_NOMEM; |
} |
} |
|
ctx = malloc(sizeof(*ctx)); |
if (ctx == NULL) { |
return NSERROR_NOMEM; |
} |
|
ctx->content = c; |
ctx->n = dom_node_ref(n); |
ctx->root_box = NULL; |
ctx->cb = cb; |
ctx->bctx = c->bctx; |
|
schedule(0, (schedule_callback_fn) convert_xml_to_box, ctx); |
|
return NSERROR_OK; |
} |
|
/* mapping from CSS display to box type |
* this table must be in sync with libcss' css_display enum */ |
static const box_type box_map[] = { |
0, /*CSS_DISPLAY_INHERIT,*/ |
BOX_INLINE, /*CSS_DISPLAY_INLINE,*/ |
BOX_BLOCK, /*CSS_DISPLAY_BLOCK,*/ |
BOX_BLOCK, /*CSS_DISPLAY_LIST_ITEM,*/ |
BOX_INLINE, /*CSS_DISPLAY_RUN_IN,*/ |
BOX_INLINE_BLOCK, /*CSS_DISPLAY_INLINE_BLOCK,*/ |
BOX_TABLE, /*CSS_DISPLAY_TABLE,*/ |
BOX_TABLE, /*CSS_DISPLAY_INLINE_TABLE,*/ |
BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_ROW_GROUP,*/ |
BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_HEADER_GROUP,*/ |
BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_FOOTER_GROUP,*/ |
BOX_TABLE_ROW, /*CSS_DISPLAY_TABLE_ROW,*/ |
BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN_GROUP,*/ |
BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN,*/ |
BOX_TABLE_CELL, /*CSS_DISPLAY_TABLE_CELL,*/ |
BOX_INLINE, /*CSS_DISPLAY_TABLE_CAPTION,*/ |
BOX_NONE /*CSS_DISPLAY_NONE*/ |
}; |
|
/** Key for box userdata on DOM elements (== '__ns_box') */ |
static dom_string *kstr_box_key; |
static dom_string *kstr_title; |
static dom_string *kstr_id; |
static dom_string *kstr_colspan; |
static dom_string *kstr_rowspan; |
static dom_string *kstr_style; |
static dom_string *kstr_href; |
static dom_string *kstr_name; |
static dom_string *kstr_target; |
static dom_string *kstr_alt; |
static dom_string *kstr_src; |
static dom_string *kstr_codebase; |
static dom_string *kstr_classid; |
static dom_string *kstr_data; |
static dom_string *kstr_rows; |
static dom_string *kstr_cols; |
static dom_string *kstr_border; |
static dom_string *kstr_frameborder; |
static dom_string *kstr_bordercolor; |
static dom_string *kstr_noresize; |
static dom_string *kstr_scrolling; |
static dom_string *kstr_marginwidth; |
static dom_string *kstr_marginheight; |
static dom_string *kstr_type; |
static dom_string *kstr_value; |
static dom_string *kstr_selected; |
|
nserror box_construct_init(void) |
{ |
dom_exception err; |
|
err = dom_string_create_interned((const uint8_t *) "__ns_box", |
SLEN("__ns_box"), &kstr_box_key); |
if (err != DOM_NO_ERR || kstr_box_key == NULL) |
goto error; |
|
#define BOX_CONSTRUCT_STRING_INTERN(NAME) \ |
err = dom_string_create_interned((const uint8_t *)#NAME, \ |
sizeof(#NAME) - 1, \ |
&kstr_##NAME ); \ |
if ((err != DOM_NO_ERR) || (kstr_##NAME == NULL)) \ |
goto error |
|
BOX_CONSTRUCT_STRING_INTERN(title); |
BOX_CONSTRUCT_STRING_INTERN(id); |
BOX_CONSTRUCT_STRING_INTERN(colspan); |
BOX_CONSTRUCT_STRING_INTERN(rowspan); |
BOX_CONSTRUCT_STRING_INTERN(style); |
BOX_CONSTRUCT_STRING_INTERN(href); |
BOX_CONSTRUCT_STRING_INTERN(name); |
BOX_CONSTRUCT_STRING_INTERN(target); |
BOX_CONSTRUCT_STRING_INTERN(alt); |
BOX_CONSTRUCT_STRING_INTERN(src); |
BOX_CONSTRUCT_STRING_INTERN(codebase); |
BOX_CONSTRUCT_STRING_INTERN(classid); |
BOX_CONSTRUCT_STRING_INTERN(data); |
BOX_CONSTRUCT_STRING_INTERN(rows); |
BOX_CONSTRUCT_STRING_INTERN(cols); |
BOX_CONSTRUCT_STRING_INTERN(border); |
BOX_CONSTRUCT_STRING_INTERN(frameborder); |
BOX_CONSTRUCT_STRING_INTERN(bordercolor); |
BOX_CONSTRUCT_STRING_INTERN(noresize); |
BOX_CONSTRUCT_STRING_INTERN(scrolling); |
BOX_CONSTRUCT_STRING_INTERN(marginwidth); |
BOX_CONSTRUCT_STRING_INTERN(marginheight); |
BOX_CONSTRUCT_STRING_INTERN(type); |
BOX_CONSTRUCT_STRING_INTERN(value); |
BOX_CONSTRUCT_STRING_INTERN(selected); |
|
#undef BOX_CONSTRUCT_STRING_INTERN |
|
return NSERROR_OK; |
|
error: |
return NSERROR_NOMEM; |
} |
|
void box_construct_fini(void) |
{ |
if (kstr_box_key != NULL) { |
dom_string_unref(kstr_box_key); |
kstr_box_key = NULL; |
} |
|
#define BOX_CONSTRUCT_STRING_UNREF(NAME) \ |
do { \ |
if (kstr_##NAME != NULL) { \ |
dom_string_unref(kstr_##NAME); \ |
kstr_##NAME = NULL; \ |
} \ |
} while (0) \ |
|
BOX_CONSTRUCT_STRING_UNREF(title); |
BOX_CONSTRUCT_STRING_UNREF(id); |
BOX_CONSTRUCT_STRING_UNREF(colspan); |
BOX_CONSTRUCT_STRING_UNREF(rowspan); |
BOX_CONSTRUCT_STRING_UNREF(style); |
BOX_CONSTRUCT_STRING_UNREF(href); |
BOX_CONSTRUCT_STRING_UNREF(name); |
BOX_CONSTRUCT_STRING_UNREF(target); |
BOX_CONSTRUCT_STRING_UNREF(alt); |
BOX_CONSTRUCT_STRING_UNREF(src); |
BOX_CONSTRUCT_STRING_UNREF(codebase); |
BOX_CONSTRUCT_STRING_UNREF(classid); |
BOX_CONSTRUCT_STRING_UNREF(data); |
BOX_CONSTRUCT_STRING_UNREF(rows); |
BOX_CONSTRUCT_STRING_UNREF(cols); |
BOX_CONSTRUCT_STRING_UNREF(border); |
BOX_CONSTRUCT_STRING_UNREF(frameborder); |
BOX_CONSTRUCT_STRING_UNREF(bordercolor); |
BOX_CONSTRUCT_STRING_UNREF(noresize); |
BOX_CONSTRUCT_STRING_UNREF(scrolling); |
BOX_CONSTRUCT_STRING_UNREF(marginwidth); |
BOX_CONSTRUCT_STRING_UNREF(marginheight); |
BOX_CONSTRUCT_STRING_UNREF(type); |
BOX_CONSTRUCT_STRING_UNREF(value); |
BOX_CONSTRUCT_STRING_UNREF(selected); |
|
#undef BOX_CONSTRUCT_DOM_STRING_UNREF |
} |
|
static inline struct box *box_for_node(dom_node *n) |
{ |
struct box *box = NULL; |
dom_exception err; |
|
err = dom_node_get_user_data(n, kstr_box_key, (void *) &box); |
if (err != DOM_NO_ERR) |
return NULL; |
|
return box; |
} |
|
static inline bool box_is_root(dom_node *n) |
{ |
dom_node *parent; |
dom_node_type type; |
dom_exception err; |
|
err = dom_node_get_parent_node(n, &parent); |
if (err != DOM_NO_ERR) |
return false; |
|
if (parent != NULL) { |
err = dom_node_get_node_type(parent, &type); |
|
dom_node_unref(parent); |
|
if (err != DOM_NO_ERR) |
return false; |
|
if (type != DOM_DOCUMENT_NODE) |
return false; |
} |
|
return true; |
} |
|
/** |
* Find the next node in the DOM tree, completing |
* element construction where appropriate. |
* |
* \param n Current node |
* \param content Containing content |
* \param convert_children Whether to consider children of \a n |
* \return Next node to process, or NULL if complete |
* |
* \note \a n will be unreferenced |
*/ |
static dom_node *next_node(dom_node *n, html_content *content, |
bool convert_children) |
{ |
dom_node *next = NULL; |
bool has_children; |
dom_exception err; |
|
err = dom_node_has_child_nodes(n, &has_children); |
if (err != DOM_NO_ERR) { |
dom_node_unref(n); |
return NULL; |
} |
|
if (convert_children && has_children) { |
err = dom_node_get_first_child(n, &next); |
if (err != DOM_NO_ERR) { |
dom_node_unref(n); |
return NULL; |
} |
dom_node_unref(n); |
} else { |
err = dom_node_get_next_sibling(n, &next); |
if (err != DOM_NO_ERR) { |
dom_node_unref(n); |
return NULL; |
} |
|
if (next != NULL) { |
if (box_for_node(n) != NULL) |
box_construct_element_after(n, content); |
dom_node_unref(n); |
} else { |
if (box_for_node(n) != NULL) |
box_construct_element_after(n, content); |
|
while (box_is_root(n) == false) { |
dom_node *parent = NULL; |
dom_node *parent_next = NULL; |
|
err = dom_node_get_parent_node(n, &parent); |
if (err != DOM_NO_ERR) { |
dom_node_unref(n); |
return NULL; |
} |
|
assert(parent != NULL); |
|
err = dom_node_get_next_sibling(parent, |
&parent_next); |
if (err != DOM_NO_ERR) { |
dom_node_unref(parent); |
dom_node_unref(n); |
return NULL; |
} |
|
if (parent_next != NULL) { |
dom_node_unref(parent_next); |
dom_node_unref(parent); |
break; |
} |
|
dom_node_unref(n); |
n = parent; |
parent = NULL; |
|
if (box_for_node(n) != NULL) { |
box_construct_element_after( |
n, content); |
} |
} |
|
if (box_is_root(n) == false) { |
dom_node *parent = NULL; |
|
err = dom_node_get_parent_node(n, &parent); |
if (err != DOM_NO_ERR) { |
dom_node_unref(n); |
return NULL; |
} |
|
assert(parent != NULL); |
|
err = dom_node_get_next_sibling(parent, &next); |
if (err != DOM_NO_ERR) { |
dom_node_unref(parent); |
dom_node_unref(n); |
return NULL; |
} |
|
if (box_for_node(parent) != NULL) { |
box_construct_element_after(parent, |
content); |
} |
|
dom_node_unref(parent); |
} |
|
dom_node_unref(n); |
} |
} |
|
return next; |
} |
|
/** |
* Convert an ELEMENT node to a box tree fragment, |
* then schedule conversion of the next ELEMENT node |
*/ |
void convert_xml_to_box(struct box_construct_ctx *ctx) |
{ |
dom_node *next; |
bool convert_children; |
uint32_t num_processed = 0; |
const uint32_t max_processed_before_yield = 10; |
|
do { |
convert_children = true; |
|
assert(ctx->n != NULL); |
|
if (box_construct_element(ctx, &convert_children) == false) { |
ctx->cb(ctx->content, false); |
dom_node_unref(ctx->n); |
free(ctx); |
return; |
} |
|
/* Find next element to process, converting text nodes as we go */ |
next = next_node(ctx->n, ctx->content, convert_children); |
while (next != NULL) { |
dom_node_type type; |
dom_exception err; |
|
err = dom_node_get_node_type(next, &type); |
if (err != DOM_NO_ERR) { |
ctx->cb(ctx->content, false); |
dom_node_unref(next); |
free(ctx); |
return; |
} |
|
if (type == DOM_ELEMENT_NODE) |
break; |
|
if (type == DOM_TEXT_NODE) { |
ctx->n = next; |
if (box_construct_text(ctx) == false) { |
ctx->cb(ctx->content, false); |
dom_node_unref(ctx->n); |
free(ctx); |
return; |
} |
} |
|
next = next_node(next, ctx->content, true); |
} |
|
ctx->n = next; |
|
if (next == NULL) { |
/* Conversion complete */ |
struct box root; |
|
memset(&root, 0, sizeof(root)); |
|
root.type = BOX_BLOCK; |
root.children = root.last = ctx->root_box; |
root.children->parent = &root; |
|
/** \todo Remove box_normalise_block */ |
if (box_normalise_block(&root, ctx->content) == false) { |
ctx->cb(ctx->content, false); |
} else { |
ctx->content->layout = root.children; |
ctx->content->layout->parent = NULL; |
|
ctx->cb(ctx->content, true); |
} |
|
assert(ctx->n == NULL); |
|
free(ctx); |
return; |
} |
} while (++num_processed < max_processed_before_yield); |
|
/* More work to do: schedule a continuation */ |
schedule(0, (schedule_callback_fn) convert_xml_to_box, ctx); |
} |
|
/** |
* Construct a list marker box |
* |
* \param box Box to attach marker to |
* \param title Current title attribute |
* \param content Containing content |
* \param parent Current block-level container |
* \return True on success, false on memory exhaustion |
*/ |
static bool box_construct_marker(struct box *box, const char *title, |
struct box_construct_ctx *ctx, struct box *parent) |
{ |
lwc_string *image_uri; |
struct box *marker; |
|
marker = box_create(NULL, box->style, false, NULL, NULL, title, |
NULL, ctx->bctx); |
if (marker == false) |
return false; |
|
marker->type = BOX_BLOCK; |
|
/** \todo marker content (list-style-type) */ |
switch (css_computed_list_style_type(box->style)) { |
case CSS_LIST_STYLE_TYPE_DISC: |
/* 2022 BULLET */ |
marker->text = (char *) "\342\200\242"; |
marker->length = 3; |
break; |
case CSS_LIST_STYLE_TYPE_CIRCLE: |
/* 25CB WHITE CIRCLE */ |
marker->text = (char *) "\342\227\213"; |
marker->length = 3; |
break; |
case CSS_LIST_STYLE_TYPE_SQUARE: |
/* 25AA BLACK SMALL SQUARE */ |
marker->text = (char *) "\342\226\252"; |
marker->length = 3; |
break; |
case CSS_LIST_STYLE_TYPE_DECIMAL: |
case CSS_LIST_STYLE_TYPE_LOWER_ALPHA: |
case CSS_LIST_STYLE_TYPE_LOWER_ROMAN: |
case CSS_LIST_STYLE_TYPE_UPPER_ALPHA: |
case CSS_LIST_STYLE_TYPE_UPPER_ROMAN: |
default: |
if (parent->last) { |
struct box *last = parent->last; |
|
/* Drill down into last child of parent |
* to find the list marker (if any) |
* |
* Floated list boxes end up as: |
* |
* parent |
* BOX_INLINE_CONTAINER |
* BOX_FLOAT_{LEFT,RIGHT} |
* BOX_BLOCK <-- list box |
* ... |
*/ |
while (last != NULL) { |
if (last->list_marker != NULL) |
break; |
|
last = last->last; |
} |
|
if (last && last->list_marker) { |
marker->rows = last->list_marker->rows + 1; |
} |
} |
|
marker->text = talloc_array(ctx->bctx, char, 20); |
if (marker->text == NULL) |
return false; |
|
snprintf(marker->text, 20, "%u.", marker->rows); |
marker->length = strlen(marker->text); |
break; |
case CSS_LIST_STYLE_TYPE_NONE: |
marker->text = 0; |
marker->length = 0; |
break; |
} |
|
if (css_computed_list_style_image(box->style, &image_uri) == CSS_LIST_STYLE_IMAGE_URI && |
(image_uri != NULL) && |
(nsoption_bool(foreground_images) == true)) { |
nsurl *url; |
nserror error; |
|
/* TODO: we get a url out of libcss as a lwc string, but |
* earlier we already had it as a nsurl after we |
* nsurl_joined it. Can this be improved? |
* For now, just making another nsurl. */ |
error = nsurl_create(lwc_string_data(image_uri), &url); |
if (error != NSERROR_OK) |
return false; |
|
if (html_fetch_object(ctx->content, url, marker, image_types, |
ctx->content->base.available_width, 1000, false) == |
false) { |
nsurl_unref(url); |
return false; |
} |
nsurl_unref(url); |
} |
|
box->list_marker = marker; |
marker->parent = box; |
|
return true; |
} |
|
/** |
* Construct the box required for a generated element. |
* |
* \param n XML node of type XML_ELEMENT_NODE |
* \param content Content of type CONTENT_HTML that is being processed |
* \param box Box which may have generated content |
* \param style Complete computed style for pseudo element, or NULL |
* |
* TODO: |
* This is currently incomplete. It just does enough to support the clearfix |
* hack. ( http://www.positioniseverything.net/easyclearing.html ) |
*/ |
static void box_construct_generate(dom_node *n, html_content *content, |
struct box *box, const css_computed_style *style) |
{ |
struct box *gen = NULL; |
const css_computed_content_item *c_item; |
|
/* Nothing to generate if the parent box is not a block */ |
if (box->type != BOX_BLOCK) |
return; |
|
/* To determine if an element has a pseudo element, we select |
* for it and test to see if the returned style's content |
* property is set to normal. */ |
if (style == NULL || |
css_computed_content(style, &c_item) == |
CSS_CONTENT_NORMAL) { |
/* No pseudo element */ |
return; |
} |
|
/* create box for this element */ |
if (css_computed_display(style, box_is_root(n)) == CSS_DISPLAY_BLOCK) { |
/* currently only support block level elements */ |
|
/** \todo Not wise to drop const from the computed style */ |
gen = box_create(NULL, (css_computed_style *) style, |
false, NULL, NULL, NULL, NULL, content->bctx); |
if (gen == NULL) { |
return; |
} |
|
/* set box type from computed display */ |
gen->type = box_map[css_computed_display( |
style, box_is_root(n))]; |
|
box_add_child(box, gen); |
} |
} |
|
/** |
* Extract transient construction properties |
* |
* \param n Current DOM node to convert |
* \param props Property object to populate |
*/ |
static void box_extract_properties(dom_node *n, |
struct box_construct_props *props) |
{ |
memset(props, 0, sizeof(*props)); |
|
props->node_is_root = box_is_root(n); |
|
/* Extract properties from containing DOM node */ |
if (props->node_is_root == false) { |
dom_node *current_node = n; |
dom_node *parent_node = NULL; |
struct box *parent_box; |
dom_exception err; |
|
/* Find ancestor node containing parent box */ |
while (true) { |
err = dom_node_get_parent_node(current_node, |
&parent_node); |
if (err != DOM_NO_ERR || parent_node == NULL) |
break; |
|
parent_box = box_for_node(parent_node); |
|
if (parent_box != NULL) { |
props->parent_style = parent_box->style; |
props->href = parent_box->href; |
props->target = parent_box->target; |
props->title = parent_box->title; |
|
dom_node_unref(parent_node); |
break; |
} else { |
if (current_node != n) |
dom_node_unref(current_node); |
current_node = parent_node; |
parent_node = NULL; |
} |
} |
|
/* Find containing block (may be parent) */ |
while (true) { |
struct box *b; |
|
err = dom_node_get_parent_node(current_node, |
&parent_node); |
if (err != DOM_NO_ERR || parent_node == NULL) { |
if (current_node != n) |
dom_node_unref(current_node); |
break; |
} |
|
if (current_node != n) |
dom_node_unref(current_node); |
|
b = box_for_node(parent_node); |
|
/* Children of nodes that created an inline box |
* will generate boxes which are attached as |
* _siblings_ of the box generated for their |
* parent node. Note, however, that we'll still |
* use the parent node's styling as the parent |
* style, above. */ |
if (b != NULL && b->type != BOX_INLINE && |
b->type != BOX_BR) { |
props->containing_block = b; |
|
dom_node_unref(parent_node); |
break; |
} else { |
current_node = parent_node; |
parent_node = NULL; |
} |
} |
} |
|
/* Compute current inline container, if any */ |
if (props->containing_block != NULL && |
props->containing_block->last != NULL && |
props->containing_block->last->type == |
BOX_INLINE_CONTAINER) |
props->inline_container = props->containing_block->last; |
} |
|
/** |
* Construct the box tree for an XML element. |
* |
* \param ctx Tree construction context |
* \param convert_children Whether to convert children |
* \return true on success, false on memory exhaustion |
*/ |
|
bool box_construct_element(struct box_construct_ctx *ctx, |
bool *convert_children) |
{ |
dom_string *title0, *s; |
lwc_string *id = NULL; |
struct box *box = NULL, *old_box; |
css_select_results *styles = NULL; |
struct element_entry *element; |
lwc_string *bgimage_uri; |
dom_exception err; |
struct box_construct_props props; |
|
assert(ctx->n != NULL); |
|
box_extract_properties(ctx->n, &props); |
|
if (props.containing_block != NULL) { |
/* In case the containing block is a pre block, we clear |
* the PRE_STRIP flag since it is not used if we follow |
* the pre with a tag */ |
props.containing_block->flags &= ~PRE_STRIP; |
} |
|
styles = box_get_style(ctx->content, props.parent_style, ctx->n); |
if (styles == NULL) |
return false; |
|
/* Extract title attribute, if present */ |
err = dom_element_get_attribute(ctx->n, kstr_title, &title0); |
if (err != DOM_NO_ERR) |
return false; |
|
if (title0 != NULL) { |
char *t = squash_whitespace(dom_string_data(title0)); |
|
dom_string_unref(title0); |
|
if (t == NULL) |
return false; |
|
props.title = talloc_strdup(ctx->bctx, t); |
|
free(t); |
|
if (props.title == NULL) |
return false; |
} |
|
/* Extract id attribute, if present */ |
err = dom_element_get_attribute(ctx->n, kstr_id, &s); |
if (err != DOM_NO_ERR) |
return false; |
|
if (s != NULL) { |
err = dom_string_intern(s, &id); |
if (err != DOM_NO_ERR) |
id = NULL; |
|
dom_string_unref(s); |
} |
|
box = box_create(styles, styles->styles[CSS_PSEUDO_ELEMENT_NONE], false, |
props.href, props.target, props.title, id, |
ctx->bctx); |
if (box == NULL) |
return false; |
|
/* If this is the root box, add it to the context */ |
if (props.node_is_root) |
ctx->root_box = box; |
|
/* Deal with colspan/rowspan */ |
err = dom_element_get_attribute(ctx->n, kstr_colspan, &s); |
if (err != DOM_NO_ERR) |
return false; |
|
if (s != NULL) { |
const char *val = dom_string_data(s); |
|
if ('0' <= val[0] && val[0] <= '9') |
box->columns = strtol(val, NULL, 10); |
|
dom_string_unref(s); |
} |
|
err = dom_element_get_attribute(ctx->n, kstr_rowspan, &s); |
if (err != DOM_NO_ERR) |
return false; |
|
if (s != NULL) { |
const char *val = dom_string_data(s); |
|
if ('0' <= val[0] && val[0] <= '9') |
box->rows = strtol(val, NULL, 10); |
|
dom_string_unref(s); |
} |
|
/* Set box type from computed display */ |
if ((css_computed_position(box->style) == CSS_POSITION_ABSOLUTE || |
css_computed_position(box->style) == |
CSS_POSITION_FIXED) && |
(css_computed_display_static(box->style) == |
CSS_DISPLAY_INLINE || |
css_computed_display_static(box->style) == |
CSS_DISPLAY_INLINE_BLOCK || |
css_computed_display_static(box->style) == |
CSS_DISPLAY_INLINE_TABLE)) { |
/* Special case for absolute positioning: make absolute inlines |
* into inline block so that the boxes are constructed in an |
* inline container as if they were not absolutely positioned. |
* Layout expects and handles this. */ |
box->type = box_map[CSS_DISPLAY_INLINE_BLOCK]; |
} else { |
/* Normal mapping */ |
box->type = box_map[css_computed_display(box->style, |
props.node_is_root)]; |
} |
|
/* Handle the :before pseudo element */ |
box_construct_generate(ctx->n, ctx->content, box, |
box->styles->styles[CSS_PSEUDO_ELEMENT_BEFORE]); |
|
err = dom_node_get_node_name(ctx->n, &s); |
if (err != DOM_NO_ERR || s == NULL) |
return false; |
|
/* Special elements */ |
element = bsearch(dom_string_data(s), element_table, |
ELEMENT_TABLE_COUNT, sizeof(element_table[0]), |
(int (*)(const void *, const void *)) strcasecmp); |
|
dom_string_unref(s); |
|
if (element != NULL) { |
/* A special convert function exists for this element */ |
if (element->convert(ctx->n, ctx->content, box, |
convert_children) == false) |
return false; |
} |
|
if (box->type == BOX_NONE || css_computed_display(box->style, |
props.node_is_root) == CSS_DISPLAY_NONE) { |
css_select_results_destroy(styles); |
box->styles = NULL; |
box->style = NULL; |
|
/* Invalidate associated gadget, if any */ |
if (box->gadget != NULL) { |
box->gadget->box = NULL; |
box->gadget = NULL; |
} |
|
/* Can't do this, because the lifetimes of boxes and gadgets |
* are inextricably linked. Fortunately, talloc will save us |
* (for now) */ |
/* box_free_box(box); */ |
|
*convert_children = false; |
|
return true; |
} |
|
/* Attach DOM node to box */ |
err = dom_node_set_user_data(ctx->n, kstr_box_key, box, NULL, |
(void *) &old_box); |
if (err != DOM_NO_ERR) |
return false; |
|
/* Attach box to DOM node */ |
box->node = dom_node_ref(ctx->n); |
|
if (props.inline_container == NULL && |
(box->type == BOX_INLINE || |
box->type == BOX_BR || |
box->type == BOX_INLINE_BLOCK || |
css_computed_float(box->style) == CSS_FLOAT_LEFT || |
css_computed_float(box->style) == CSS_FLOAT_RIGHT)) { |
/* Found an inline child of a block without a current container |
* (i.e. this box is the first child of its parent, or was |
* preceded by block-level siblings) */ |
assert(props.containing_block != NULL && |
"Root box must not be inline or floated"); |
|
props.inline_container = box_create(NULL, NULL, false, NULL, |
NULL, NULL, NULL, ctx->bctx); |
if (props.inline_container == NULL) |
return false; |
|
props.inline_container->type = BOX_INLINE_CONTAINER; |
|
box_add_child(props.containing_block, props.inline_container); |
} |
|
/* Kick off fetch for any background image */ |
if (css_computed_background_image(box->style, &bgimage_uri) == |
CSS_BACKGROUND_IMAGE_IMAGE && bgimage_uri != NULL && |
nsoption_bool(background_images) == true) { |
nsurl *url; |
nserror error; |
|
/* TODO: we get a url out of libcss as a lwc string, but |
* earlier we already had it as a nsurl after we |
* nsurl_joined it. Can this be improved? |
* For now, just making another nsurl. */ |
error = nsurl_create(lwc_string_data(bgimage_uri), &url); |
if (error != NSERROR_OK) |
return false; |
|
if (html_fetch_object(ctx->content, url, box, image_types, |
ctx->content->base.available_width, 1000, |
true) == false) { |
nsurl_unref(url); |
return false; |
} |
nsurl_unref(url); |
} |
|
if (*convert_children) |
box->flags |= CONVERT_CHILDREN; |
|
if (box->type == BOX_INLINE || box->type == BOX_BR || |
box->type == BOX_INLINE_BLOCK) { |
/* Inline container must exist, as we'll have |
* created it above if it didn't */ |
assert(props.inline_container != NULL); |
|
box_add_child(props.inline_container, box); |
} else { |
if (css_computed_display(box->style, props.node_is_root) == |
CSS_DISPLAY_LIST_ITEM) { |
/* List item: compute marker */ |
if (box_construct_marker(box, props.title, ctx, |
props.containing_block) == false) |
return false; |
} |
|
if (css_computed_float(box->style) == CSS_FLOAT_LEFT || |
css_computed_float(box->style) == |
CSS_FLOAT_RIGHT) { |
/* Float: insert a float between the parent and box. */ |
struct box *flt = box_create(NULL, NULL, false, |
props.href, props.target, props.title, |
NULL, ctx->bctx); |
if (flt == NULL) |
return false; |
|
if (css_computed_float(box->style) == CSS_FLOAT_LEFT) |
flt->type = BOX_FLOAT_LEFT; |
else |
flt->type = BOX_FLOAT_RIGHT; |
|
box_add_child(props.inline_container, flt); |
box_add_child(flt, box); |
} else { |
/* Non-floated block-level box: add to containing block |
* if there is one. If we're the root box, then there |
* won't be. */ |
if (props.containing_block != NULL) |
box_add_child(props.containing_block, box); |
} |
} |
|
return true; |
} |
|
/** |
* Complete construction of the box tree for an element. |
* |
* \param n DOM node to construct for |
* \param content Containing document |
* |
* This will be called after all children of an element have been processed |
*/ |
void box_construct_element_after(dom_node *n, html_content *content) |
{ |
struct box_construct_props props; |
struct box *box = box_for_node(n); |
|
assert(box != NULL); |
|
box_extract_properties(n, &props); |
|
if (box->type == BOX_INLINE || box->type == BOX_BR) { |
/* Insert INLINE_END into containing block */ |
struct box *inline_end; |
bool has_children; |
dom_exception err; |
|
err = dom_node_has_child_nodes(n, &has_children); |
if (err != DOM_NO_ERR) |
return; |
|
if (has_children == false || |
(box->flags & CONVERT_CHILDREN) == 0) { |
/* No children, or didn't want children converted */ |
return; |
} |
|
if (props.inline_container == NULL) { |
/* Create inline container if we don't have one */ |
props.inline_container = box_create(NULL, NULL, false, |
NULL, NULL, NULL, NULL, content->bctx); |
if (props.inline_container == NULL) |
return; |
|
props.inline_container->type = BOX_INLINE_CONTAINER; |
|
box_add_child(props.containing_block, |
props.inline_container); |
} |
|
inline_end = box_create(NULL, box->style, false, |
box->href, box->target, box->title, |
box->id == NULL ? NULL : |
lwc_string_ref(box->id), content->bctx); |
if (inline_end != NULL) { |
inline_end->type = BOX_INLINE_END; |
|
assert(props.inline_container != NULL); |
|
box_add_child(props.inline_container, inline_end); |
|
box->inline_end = inline_end; |
inline_end->inline_end = box; |
} |
} else { |
/* Handle the :after pseudo element */ |
box_construct_generate(n, content, box, |
box->styles->styles[CSS_PSEUDO_ELEMENT_AFTER]); |
} |
} |
|
/** |
* Construct the box tree for an XML text node. |
* |
* \param ctx Tree construction context |
* \return true on success, false on memory exhaustion |
*/ |
|
bool box_construct_text(struct box_construct_ctx *ctx) |
{ |
struct box_construct_props props; |
struct box *box = NULL; |
dom_string *content; |
dom_exception err; |
|
assert(ctx->n != NULL); |
|
box_extract_properties(ctx->n, &props); |
|
assert(props.containing_block != NULL); |
|
err = dom_characterdata_get_data(ctx->n, &content); |
if (err != DOM_NO_ERR || content == NULL) |
return false; |
|
if (css_computed_white_space(props.parent_style) == |
CSS_WHITE_SPACE_NORMAL || |
css_computed_white_space(props.parent_style) == |
CSS_WHITE_SPACE_NOWRAP) { |
char *text; |
|
text = squash_whitespace(dom_string_data(content)); |
|
dom_string_unref(content); |
|
if (text == NULL) |
return false; |
|
/* if the text is just a space, combine it with the preceding |
* text node, if any */ |
if (text[0] == ' ' && text[1] == 0) { |
if (props.inline_container != NULL) { |
assert(props.inline_container->last != NULL); |
|
props.inline_container->last->space = |
UNKNOWN_WIDTH; |
} |
|
free(text); |
|
return true; |
} |
|
if (props.inline_container == NULL) { |
/* Child of a block without a current container |
* (i.e. this box is the first child of its parent, or |
* was preceded by block-level siblings) */ |
props.inline_container = box_create(NULL, NULL, false, |
NULL, NULL, NULL, NULL, ctx->bctx); |
if (props.inline_container == NULL) { |
free(text); |
return false; |
} |
|
props.inline_container->type = BOX_INLINE_CONTAINER; |
|
box_add_child(props.containing_block, |
props.inline_container); |
} |
|
/** \todo Dropping const here is not clever */ |
box = box_create(NULL, |
(css_computed_style *) props.parent_style, |
false, props.href, props.target, props.title, |
NULL, ctx->bctx); |
if (box == NULL) { |
free(text); |
return false; |
} |
|
box->type = BOX_TEXT; |
|
box->text = talloc_strdup(ctx->bctx, text); |
free(text); |
if (box->text == NULL) |
return false; |
|
box->length = strlen(box->text); |
|
/* strip ending space char off */ |
if (box->length > 1 && box->text[box->length - 1] == ' ') { |
box->space = UNKNOWN_WIDTH; |
box->length--; |
} |
|
if (css_computed_text_transform(props.parent_style) != |
CSS_TEXT_TRANSFORM_NONE) |
box_text_transform(box->text, box->length, |
css_computed_text_transform( |
props.parent_style)); |
|
box_add_child(props.inline_container, box); |
|
if (box->text[0] == ' ') { |
box->length--; |
|
memmove(box->text, &box->text[1], box->length); |
|
if (box->prev != NULL) |
box->prev->space = UNKNOWN_WIDTH; |
} |
} else { |
/* white-space: pre */ |
char *text; |
size_t text_len = dom_string_byte_length(content); |
size_t i; |
char *current; |
enum css_white_space_e white_space = |
css_computed_white_space(props.parent_style); |
|
/* note: pre-wrap/pre-line are unimplemented */ |
assert(white_space == CSS_WHITE_SPACE_PRE || |
white_space == CSS_WHITE_SPACE_PRE_LINE || |
white_space == CSS_WHITE_SPACE_PRE_WRAP); |
|
text = malloc(text_len + 1); |
dom_string_unref(content); |
|
if (text == NULL) |
return false; |
|
memcpy(text, dom_string_data(content), text_len); |
text[text_len] = '\0'; |
|
/* TODO: Handle tabs properly */ |
for (i = 0; i < text_len; i++) |
if (text[i] == '\t') |
text[i] = ' '; |
|
if (css_computed_text_transform(props.parent_style) != |
CSS_TEXT_TRANSFORM_NONE) |
box_text_transform(text, strlen(text), |
css_computed_text_transform( |
props.parent_style)); |
|
current = text; |
|
/* swallow a single leading new line */ |
if (props.containing_block->flags & PRE_STRIP) { |
switch (*current) { |
case '\n': |
current++; |
break; |
case '\r': |
current++; |
if (*current == '\n') |
current++; |
break; |
} |
props.containing_block->flags &= ~PRE_STRIP; |
} |
|
do { |
size_t len = strcspn(current, "\r\n"); |
|
char old = current[len]; |
|
current[len] = 0; |
|
if (props.inline_container == NULL) { |
/* Child of a block without a current container |
* (i.e. this box is the first child of its |
* parent, or was preceded by block-level |
* siblings) */ |
props.inline_container = box_create(NULL, NULL, |
false, NULL, NULL, NULL, NULL, |
ctx->bctx); |
if (props.inline_container == NULL) { |
free(text); |
return false; |
} |
|
props.inline_container->type = |
BOX_INLINE_CONTAINER; |
|
box_add_child(props.containing_block, |
props.inline_container); |
} |
|
/** \todo Dropping const isn't clever */ |
box = box_create(NULL, |
(css_computed_style *) props.parent_style, |
false, props.href, props.target, props.title, |
NULL, ctx->bctx); |
if (box == NULL) { |
free(text); |
return false; |
} |
|
box->type = BOX_TEXT; |
|
box->text = talloc_strdup(ctx->bctx, current); |
if (box->text == NULL) { |
free(text); |
return false; |
} |
|
box->length = strlen(box->text); |
|
box_add_child(props.inline_container, box); |
|
current[len] = old; |
|
current += len; |
|
if (current[0] != '\0') { |
/* Linebreak: create new inline container */ |
props.inline_container = box_create(NULL, NULL, |
false, NULL, NULL, NULL, NULL, |
ctx->bctx); |
if (props.inline_container == NULL) { |
free(text); |
return false; |
} |
|
props.inline_container->type = |
BOX_INLINE_CONTAINER; |
|
box_add_child(props.containing_block, |
props.inline_container); |
|
if (current[0] == '\r' && current[1] == '\n') |
current += 2; |
else |
current++; |
} |
} while (*current); |
|
free(text); |
} |
|
return true; |
} |
|
/** |
* Get the style for an element. |
* |
* \param c content of type CONTENT_HTML that is being processed |
* \param parent_style style at this point in xml tree, or NULL for root |
* \param n node in xml tree |
* \return the new style, or NULL on memory exhaustion |
*/ |
css_select_results *box_get_style(html_content *c, |
const css_computed_style *parent_style, dom_node *n) |
{ |
dom_string *s; |
dom_exception err; |
int pseudo_element; |
css_error error; |
css_stylesheet *inline_style = NULL; |
css_select_results *styles; |
nscss_select_ctx ctx; |
|
/* Firstly, construct inline stylesheet, if any */ |
err = dom_element_get_attribute(n, kstr_style, &s); |
if (err != DOM_NO_ERR) |
return NULL; |
|
if (s != NULL) { |
inline_style = nscss_create_inline_style( |
(const uint8_t *) dom_string_data(s), |
dom_string_byte_length(s), |
c->encoding, |
nsurl_access(content_get_url(&c->base)), |
c->quirks != DOM_DOCUMENT_QUIRKS_MODE_NONE, |
box_style_alloc, NULL); |
|
dom_string_unref(s); |
|
if (inline_style == NULL) |
return NULL; |
} |
|
/* Populate selection context */ |
ctx.ctx = c->select_ctx; |
ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); |
ctx.base_url = c->base_url; |
ctx.universal = c->universal; |
|
/* Select partial style for element */ |
styles = nscss_get_style(&ctx, n, CSS_MEDIA_SCREEN, inline_style, |
box_style_alloc, NULL); |
|
/* No longer need inline style */ |
if (inline_style != NULL) |
css_stylesheet_destroy(inline_style); |
|
/* Failed selecting partial style -- bail out */ |
if (styles == NULL) |
return NULL; |
|
/* If there's a parent style, compose with partial to obtain |
* complete computed style for element */ |
if (parent_style != NULL) { |
/* Complete the computed style, by composing with the parent |
* element's style */ |
error = css_computed_style_compose(parent_style, |
styles->styles[CSS_PSEUDO_ELEMENT_NONE], |
nscss_compute_font_size, NULL, |
styles->styles[CSS_PSEUDO_ELEMENT_NONE]); |
if (error != CSS_OK) { |
css_select_results_destroy(styles); |
return NULL; |
} |
} |
|
for (pseudo_element = CSS_PSEUDO_ELEMENT_NONE + 1; |
pseudo_element < CSS_PSEUDO_ELEMENT_COUNT; |
pseudo_element++) { |
|
if (pseudo_element == CSS_PSEUDO_ELEMENT_FIRST_LETTER || |
pseudo_element == CSS_PSEUDO_ELEMENT_FIRST_LINE) |
/* TODO: Handle first-line and first-letter pseudo |
* element computed style completion */ |
continue; |
|
if (styles->styles[pseudo_element] == NULL) |
/* There were no rules concerning this pseudo element */ |
continue; |
|
/* Complete the pseudo element's computed style, by composing |
* with the base element's style */ |
error = css_computed_style_compose( |
styles->styles[CSS_PSEUDO_ELEMENT_NONE], |
styles->styles[pseudo_element], |
nscss_compute_font_size, NULL, |
styles->styles[pseudo_element]); |
if (error != CSS_OK) { |
/* TODO: perhaps this shouldn't be quite so |
* catastrophic? */ |
css_select_results_destroy(styles); |
return NULL; |
} |
} |
|
return styles; |
} |
|
|
/** |
* Apply the CSS text-transform property to given text for its ASCII chars. |
* |
* \param s string to transform |
* \param len length of s |
* \param tt transform type |
*/ |
|
void box_text_transform(char *s, unsigned int len, enum css_text_transform_e tt) |
{ |
unsigned int i; |
if (len == 0) |
return; |
switch (tt) { |
case CSS_TEXT_TRANSFORM_UPPERCASE: |
for (i = 0; i < len; ++i) |
if ((unsigned char) s[i] < 0x80) |
s[i] = ls_toupper(s[i]); |
break; |
case CSS_TEXT_TRANSFORM_LOWERCASE: |
for (i = 0; i < len; ++i) |
if ((unsigned char) s[i] < 0x80) |
s[i] = ls_tolower(s[i]); |
break; |
case CSS_TEXT_TRANSFORM_CAPITALIZE: |
if ((unsigned char) s[0] < 0x80) |
s[0] = ls_toupper(s[0]); |
for (i = 1; i < len; ++i) |
if ((unsigned char) s[i] < 0x80 && |
ls_isspace(s[i - 1])) |
s[i] = ls_toupper(s[i]); |
break; |
default: |
break; |
} |
} |
|
|
/** |
* \name Special case element handlers |
* |
* These functions are called by box_construct_element() when an element is |
* being converted, according to the entries in element_table. |
* |
* The parameters are the xmlNode, the content for the document, and a partly |
* filled in box structure for the element. |
* |
* Return true on success, false on memory exhaustion. Set *convert_children |
* to false if children of this element in the XML tree should be skipped (for |
* example, if they have been processed in some special way already). |
* |
* Elements ordered as in the HTML 4.01 specification. Section numbers in |
* brackets [] refer to the spec. |
* |
* \{ |
*/ |
|
/** |
* Document body [7.5.1]. |
*/ |
|
bool box_body(BOX_SPECIAL_PARAMS) |
{ |
css_color color; |
|
css_computed_background_color(box->style, &color); |
if (nscss_color_is_transparent(color)) |
content->background_colour = NS_TRANSPARENT; |
else |
content->background_colour = nscss_color_to_ns(color); |
|
return true; |
} |
|
|
/** |
* Forced line break [9.3.2]. |
*/ |
|
bool box_br(BOX_SPECIAL_PARAMS) |
{ |
box->type = BOX_BR; |
return true; |
} |
|
/** |
* Preformatted text [9.3.4]. |
*/ |
|
bool box_pre(BOX_SPECIAL_PARAMS) |
{ |
box->flags |= PRE_STRIP; |
return true; |
} |
|
/** |
* Anchor [12.2]. |
*/ |
|
bool box_a(BOX_SPECIAL_PARAMS) |
{ |
bool ok; |
nsurl *url; |
dom_string *s; |
dom_exception err; |
|
err = dom_element_get_attribute(n, kstr_href, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
ok = box_extract_link(dom_string_data(s), |
content->base_url, &url); |
dom_string_unref(s); |
if (!ok) |
return false; |
if (url) { |
if (box->href != NULL) |
nsurl_unref(box->href); |
box->href = url; |
} |
} |
|
/* name and id share the same namespace */ |
err = dom_element_get_attribute(n, kstr_name, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
lwc_string *lwc_name; |
|
err = dom_string_intern(s, &lwc_name); |
|
dom_string_unref(s); |
|
if (err == DOM_NO_ERR) { |
/* name replaces existing id |
* TODO: really? */ |
if (box->id != NULL) |
lwc_string_unref(box->id); |
|
box->id = lwc_name; |
} |
} |
|
/* target frame [16.3] */ |
err = dom_element_get_attribute(n, kstr_target, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc__blank)) |
box->target = TARGET_BLANK; |
else if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc__top)) |
box->target = TARGET_TOP; |
else if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc__parent)) |
box->target = TARGET_PARENT; |
else if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc__self)) |
/* the default may have been overridden by a |
* <base target=...>, so this is different to 0 */ |
box->target = TARGET_SELF; |
else { |
/* 6.16 says that frame names must begin with [a-zA-Z] |
* This doesn't match reality, so just take anything */ |
box->target = talloc_strdup(content->bctx, |
dom_string_data(s)); |
if (!box->target) { |
dom_string_unref(s); |
return false; |
} |
} |
dom_string_unref(s); |
} |
|
return true; |
} |
|
|
/** |
* Embedded image [13.2]. |
*/ |
|
bool box_image(BOX_SPECIAL_PARAMS) |
{ |
bool ok; |
dom_string *s; |
dom_exception err; |
nsurl *url; |
enum css_width_e wtype; |
enum css_height_e htype; |
css_fixed value = 0; |
css_unit wunit = CSS_UNIT_PX; |
css_unit hunit = CSS_UNIT_PX; |
|
if (box->style && css_computed_display(box->style, |
box_is_root(n)) == CSS_DISPLAY_NONE) |
return true; |
|
/* handle alt text */ |
err = dom_element_get_attribute(n, kstr_alt, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
char *alt = squash_whitespace(dom_string_data(s)); |
dom_string_unref(s); |
if (alt == NULL) |
return false; |
box->text = talloc_strdup(content->bctx, alt); |
free(alt); |
if (box->text == NULL) |
return false; |
box->length = strlen(box->text); |
} |
|
if (nsoption_bool(foreground_images) == false) { |
return true; |
} |
|
/* imagemap associated with this image */ |
if (!box_get_attribute(n, "usemap", content->bctx, &box->usemap)) |
return false; |
if (box->usemap && box->usemap[0] == '#') |
box->usemap++; |
|
/* get image URL */ |
err = dom_element_get_attribute(n, kstr_src, &s); |
if (err != DOM_NO_ERR || s == NULL) |
return true; |
|
if (box_extract_link(dom_string_data(s), content->base_url, |
&url) == false) { |
dom_string_unref(s); |
return false; |
} |
|
dom_string_unref(s); |
|
if (url == NULL) |
return true; |
|
/* start fetch */ |
ok = html_fetch_object(content, url, box, image_types, |
content->base.available_width, 1000, false); |
nsurl_unref(url); |
|
wtype = css_computed_width(box->style, &value, &wunit); |
htype = css_computed_height(box->style, &value, &hunit); |
|
if (wtype == CSS_WIDTH_SET && wunit != CSS_UNIT_PCT && |
htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT) { |
/* We know the dimensions the image will be shown at before it's |
* fetched. */ |
box->flags |= REPLACE_DIM; |
} |
|
return ok; |
} |
|
|
/** |
* Noscript element |
*/ |
|
bool box_noscript(BOX_SPECIAL_PARAMS) |
{ |
/* If scripting is enabled, do not display the contents of noscript */ |
if (nsoption_bool(enable_javascript)) |
*convert_children = false; |
|
return true; |
} |
|
|
/** |
* Destructor for object_params, for <object> elements |
* |
* \param b The object params being destroyed. |
* \return 0 to allow talloc to continue destroying the tree. |
*/ |
static int box_object_talloc_destructor(struct object_params *o) |
{ |
if (o->codebase != NULL) |
nsurl_unref(o->codebase); |
if (o->classid != NULL) |
nsurl_unref(o->classid); |
if (o->data != NULL) |
nsurl_unref(o->data); |
|
return 0; |
} |
|
/** |
* Generic embedded object [13.3]. |
*/ |
|
bool box_object(BOX_SPECIAL_PARAMS) |
{ |
struct object_params *params; |
struct object_param *param; |
dom_string *codebase, *classid, *data; |
dom_node *c; |
dom_exception err; |
|
if (box->style && css_computed_display(box->style, |
box_is_root(n)) == CSS_DISPLAY_NONE) |
return true; |
|
if (box_get_attribute(n, "usemap", content->bctx, &box->usemap) == |
false) |
return false; |
if (box->usemap && box->usemap[0] == '#') |
box->usemap++; |
|
params = talloc(content->bctx, struct object_params); |
if (params == NULL) |
return false; |
|
talloc_set_destructor(params, box_object_talloc_destructor); |
|
params->data = NULL; |
params->type = NULL; |
params->codetype = NULL; |
params->codebase = NULL; |
params->classid = NULL; |
params->params = NULL; |
|
/* codebase, classid, and data are URLs |
* (codebase is the base for the other two) */ |
err = dom_element_get_attribute(n, kstr_codebase, &codebase); |
if (err == DOM_NO_ERR && codebase != NULL) { |
if (box_extract_link(dom_string_data(codebase), |
content->base_url, |
¶ms->codebase) == false) { |
dom_string_unref(codebase); |
return false; |
} |
dom_string_unref(codebase); |
} |
if (params->codebase == NULL) |
params->codebase = nsurl_ref(content->base_url); |
|
err = dom_element_get_attribute(n, kstr_classid, &classid); |
if (err == DOM_NO_ERR && classid != NULL) { |
if (box_extract_link(dom_string_data(classid), params->codebase, |
¶ms->classid) == false) { |
dom_string_unref(classid); |
return false; |
} |
dom_string_unref(classid); |
} |
|
err = dom_element_get_attribute(n, kstr_data, &data); |
if (err == DOM_NO_ERR && data != NULL) { |
if (box_extract_link(dom_string_data(data), params->codebase, |
¶ms->data) == false) { |
dom_string_unref(data); |
return false; |
} |
dom_string_unref(data); |
} |
|
if (params->classid == NULL && params->data == NULL) |
/* nothing to embed; ignore */ |
return true; |
|
/* Don't include ourself */ |
if (params->classid != NULL && nsurl_compare(content->base_url, |
params->classid, NSURL_COMPLETE)) |
return true; |
|
if (params->data != NULL && nsurl_compare(content->base_url, |
params->data, NSURL_COMPLETE)) |
return true; |
|
/* codetype and type are MIME types */ |
if (box_get_attribute(n, "codetype", params, |
¶ms->codetype) == false) |
return false; |
if (box_get_attribute(n, "type", params, ¶ms->type) == false) |
return false; |
|
/* classid && !data => classid is used (consult codetype) |
* (classid || !classid) && data => data is used (consult type) |
* !classid && !data => invalid; ignored */ |
|
if (params->classid != NULL && params->data == NULL && |
params->codetype != NULL) { |
lwc_string *icodetype; |
lwc_error lerror; |
|
lerror = lwc_intern_string(params->codetype, |
strlen(params->codetype), &icodetype); |
if (lerror != lwc_error_ok) |
return false; |
|
if (content_factory_type_from_mime_type(icodetype) == |
CONTENT_NONE) { |
/* can't handle this MIME type */ |
lwc_string_unref(icodetype); |
return true; |
} |
|
lwc_string_unref(icodetype); |
} |
|
if (params->data != NULL && params->type != NULL) { |
lwc_string *itype; |
lwc_error lerror; |
|
lerror = lwc_intern_string(params->type, strlen(params->type), |
&itype); |
if (lerror != lwc_error_ok) |
return false; |
|
if (content_factory_type_from_mime_type(itype) == |
CONTENT_NONE) { |
/* can't handle this MIME type */ |
lwc_string_unref(itype); |
return true; |
} |
|
lwc_string_unref(itype); |
} |
|
/* add parameters to linked list */ |
err = dom_node_get_first_child(n, &c); |
if (err != DOM_NO_ERR) |
return false; |
|
while (c != NULL) { |
dom_node *next; |
dom_node_type type; |
|
err = dom_node_get_node_type(c, &type); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
if (type == DOM_ELEMENT_NODE) { |
dom_string *name; |
|
err = dom_node_get_node_name(c, &name); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
if (!dom_string_caseless_lwc_isequal(name, |
corestring_lwc_param)) { |
/* The first non-param child is the start of |
* the alt html. Therefore, we should break |
* out of this loop. */ |
dom_node_unref(c); |
break; |
} |
|
param = talloc(params, struct object_param); |
if (param == NULL) { |
dom_node_unref(c); |
return false; |
} |
param->name = NULL; |
param->value = NULL; |
param->type = NULL; |
param->valuetype = NULL; |
param->next = NULL; |
|
if (box_get_attribute(c, "name", param, |
¶m->name) == false) { |
dom_node_unref(c); |
return false; |
} |
|
if (box_get_attribute(c, "value", param, |
¶m->value) == false) { |
dom_node_unref(c); |
return false; |
} |
|
if (box_get_attribute(c, "type", param, |
¶m->type) == false) { |
dom_node_unref(c); |
return false; |
} |
|
if (box_get_attribute(c, "valuetype", param, |
¶m->valuetype) == false) { |
dom_node_unref(c); |
return false; |
} |
|
if (param->valuetype == NULL) { |
param->valuetype = talloc_strdup(param, "data"); |
if (param->valuetype == NULL) { |
dom_node_unref(c); |
return false; |
} |
} |
|
param->next = params->params; |
params->params = param; |
} |
|
err = dom_node_get_next_sibling(c, &next); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
dom_node_unref(c); |
c = next; |
} |
|
box->object_params = params; |
|
/* start fetch (MIME type is ok or not specified) */ |
if (!html_fetch_object(content, |
params->data ? params->data : params->classid, |
box, CONTENT_ANY, content->base.available_width, 1000, |
false)) |
return false; |
|
*convert_children = false; |
return true; |
} |
|
|
/** |
* Window subdivision [16.2.1]. |
*/ |
|
bool box_frameset(BOX_SPECIAL_PARAMS) |
{ |
bool ok; |
|
if (content->frameset) { |
LOG(("Error: multiple framesets in document.")); |
/* Don't convert children */ |
if (convert_children) |
*convert_children = false; |
/* And ignore this spurious frameset */ |
box->type = BOX_NONE; |
return true; |
} |
|
content->frameset = talloc_zero(content->bctx, struct content_html_frames); |
if (!content->frameset) |
return false; |
|
ok = box_create_frameset(content->frameset, n, content); |
if (ok) |
box->type = BOX_NONE; |
|
if (convert_children) |
*convert_children = false; |
return ok; |
} |
|
|
/** |
* Destructor for content_html_frames, for <frame> elements |
* |
* \param b The frame params being destroyed. |
* \return 0 to allow talloc to continue destroying the tree. |
*/ |
static int box_frames_talloc_destructor(struct content_html_frames *f) |
{ |
if (f->url != NULL) { |
nsurl_unref(f->url); |
f->url = NULL; |
} |
|
return 0; |
} |
|
bool box_create_frameset(struct content_html_frames *f, dom_node *n, |
html_content *content) { |
unsigned int row, col, index, i; |
unsigned int rows = 1, cols = 1; |
dom_string *s; |
dom_exception err; |
nsurl *url; |
struct frame_dimension *row_height = 0, *col_width = 0; |
dom_node *c, *next; |
struct content_html_frames *frame; |
bool default_border = true; |
colour default_border_colour = 0x000000; |
|
/* parse rows and columns */ |
err = dom_element_get_attribute(n, kstr_rows, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
row_height = box_parse_multi_lengths(dom_string_data(s), &rows); |
dom_string_unref(s); |
if (row_height == NULL) |
return false; |
} else { |
row_height = calloc(1, sizeof(struct frame_dimension)); |
if (row_height == NULL) |
return false; |
row_height->value = 100; |
row_height->unit = FRAME_DIMENSION_PERCENT; |
} |
|
err = dom_element_get_attribute(n, kstr_cols, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
col_width = box_parse_multi_lengths(dom_string_data(s), &cols); |
dom_string_unref(s); |
if (col_width == NULL) |
return false; |
} else { |
col_width = calloc(1, sizeof(struct frame_dimension)); |
if (col_width == NULL) |
return false; |
col_width->value = 100; |
col_width->unit = FRAME_DIMENSION_PERCENT; |
} |
|
/* common extension: border="0|1" to control all children */ |
err = dom_element_get_attribute(n, kstr_border, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
if ((dom_string_data(s)[0] == '0') && |
(dom_string_data(s)[1] == '\0')) |
default_border = false; |
dom_string_unref(s); |
} |
|
/* common extension: frameborder="yes|no" to control all children */ |
err = dom_element_get_attribute(n, kstr_frameborder, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc_no) == 0) |
default_border = false; |
dom_string_unref(s); |
} |
|
/* common extension: bordercolor="#RRGGBB|<named colour>" to control |
*all children */ |
err = dom_element_get_attribute(n, kstr_bordercolor, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
css_color color; |
|
if (nscss_parse_colour(dom_string_data(s), &color)) |
default_border_colour = nscss_color_to_ns(color); |
|
dom_string_unref(s); |
} |
|
/* update frameset and create default children */ |
f->cols = cols; |
f->rows = rows; |
f->scrolling = SCROLLING_NO; |
f->children = talloc_array(content->bctx, struct content_html_frames, |
(rows * cols)); |
|
talloc_set_destructor(f->children, box_frames_talloc_destructor); |
|
for (row = 0; row < rows; row++) { |
for (col = 0; col < cols; col++) { |
index = (row * cols) + col; |
frame = &f->children[index]; |
frame->cols = 0; |
frame->rows = 0; |
frame->width = col_width[col]; |
frame->height = row_height[row]; |
frame->margin_width = 0; |
frame->margin_height = 0; |
frame->name = NULL; |
frame->url = NULL; |
frame->no_resize = false; |
frame->scrolling = SCROLLING_AUTO; |
frame->border = default_border; |
frame->border_colour = default_border_colour; |
frame->children = NULL; |
} |
} |
free(col_width); |
free(row_height); |
|
/* create the frameset windows */ |
err = dom_node_get_first_child(n, &c); |
if (err != DOM_NO_ERR) |
return false; |
|
for (row = 0; c != NULL && row < rows; row++) { |
for (col = 0; c != NULL && col < cols; col++) { |
while (c != NULL) { |
dom_node_type type; |
dom_string *name; |
|
err = dom_node_get_node_type(c, &type); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
err = dom_node_get_node_name(c, &name); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
if (type != DOM_ELEMENT_NODE || |
(!dom_string_caseless_lwc_isequal( |
name, |
corestring_lwc_frame) && |
!dom_string_caseless_lwc_isequal( |
name, |
corestring_lwc_frameset |
))) { |
err = dom_node_get_next_sibling(c, |
&next); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
dom_node_unref(c); |
c = next; |
} else { |
/* Got a FRAME or FRAMESET element */ |
break; |
} |
} |
|
if (c == NULL) |
break; |
|
/* get current frame */ |
index = (row * cols) + col; |
frame = &f->children[index]; |
|
/* nest framesets */ |
err = dom_node_get_node_name(c, &s); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc_frameset)) { |
dom_string_unref(s); |
frame->border = 0; |
if (box_create_frameset(frame, c, |
content) == false) { |
dom_node_unref(c); |
return false; |
} |
|
err = dom_node_get_next_sibling(c, &next); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
dom_node_unref(c); |
c = next; |
continue; |
} |
|
dom_string_unref(s); |
|
/* get frame URL (not required) */ |
url = NULL; |
err = dom_element_get_attribute(c, kstr_src, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
box_extract_link(dom_string_data(s), |
content->base_url, &url); |
dom_string_unref(s); |
} |
|
/* copy url */ |
if (url != NULL) { |
/* no self-references */ |
if (nsurl_compare(content->base_url, url, |
NSURL_COMPLETE) == false) |
frame->url = url; |
url = NULL; |
} |
|
/* fill in specified values */ |
err = dom_element_get_attribute(c, kstr_name, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
frame->name = talloc_strdup(content->bctx, |
dom_string_data(s)); |
dom_string_unref(s); |
} |
|
dom_element_has_attribute(c, kstr_noresize, |
&frame->no_resize); |
|
err = dom_element_get_attribute(c, kstr_frameborder, |
&s); |
if (err == DOM_NO_ERR && s != NULL) { |
i = atoi(dom_string_data(s)); |
frame->border = (i != 0); |
dom_string_unref(s); |
} |
|
err = dom_element_get_attribute(c, kstr_scrolling, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc_yes)) |
frame->scrolling = SCROLLING_YES; |
else if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc_no)) |
frame->scrolling = SCROLLING_NO; |
dom_string_unref(s); |
} |
|
err = dom_element_get_attribute(c, kstr_marginwidth, |
&s); |
if (err == DOM_NO_ERR && s != NULL) { |
frame->margin_width = atoi(dom_string_data(s)); |
dom_string_unref(s); |
} |
|
err = dom_element_get_attribute(c, kstr_marginheight, |
&s); |
if (err == DOM_NO_ERR && s != NULL) { |
frame->margin_height = atoi(dom_string_data(s)); |
dom_string_unref(s); |
} |
|
err = dom_element_get_attribute(c, kstr_bordercolor, |
&s); |
if (err == DOM_NO_ERR && s != NULL) { |
css_color color; |
|
if (nscss_parse_colour(dom_string_data(s), |
&color)) |
frame->border_colour = |
nscss_color_to_ns(color); |
|
dom_string_unref(s); |
} |
|
/* advance */ |
err = dom_node_get_next_sibling(c, &next); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
dom_node_unref(c); |
c = next; |
} |
} |
|
return true; |
} |
|
|
/** |
* Destructor for content_html_iframe, for <iframe> elements |
* |
* \param b The iframe params being destroyed. |
* \return 0 to allow talloc to continue destroying the tree. |
*/ |
static int box_iframes_talloc_destructor(struct content_html_iframe *f) |
{ |
if (f->url != NULL) { |
nsurl_unref(f->url); |
f->url = NULL; |
} |
|
return 0; |
} |
|
|
/** |
* Inline subwindow [16.5]. |
*/ |
|
bool box_iframe(BOX_SPECIAL_PARAMS) |
{ |
nsurl *url; |
dom_string *s; |
dom_exception err; |
struct content_html_iframe *iframe; |
int i; |
|
if (box->style && css_computed_display(box->style, |
box_is_root(n)) == CSS_DISPLAY_NONE) |
return true; |
|
if (box->style && css_computed_visibility(box->style) == |
CSS_VISIBILITY_HIDDEN) |
/* Don't create iframe discriptors for invisible iframes |
* TODO: handle hidden iframes at browser_window generation |
* time instead? */ |
return true; |
|
/* get frame URL */ |
err = dom_element_get_attribute(n, kstr_src, &s); |
if (err != DOM_NO_ERR || s == NULL) |
return true; |
if (box_extract_link(dom_string_data(s), content->base_url, |
&url) == false) { |
dom_string_unref(s); |
return false; |
} |
dom_string_unref(s); |
if (url == NULL) |
return true; |
|
/* don't include ourself */ |
if (nsurl_compare(content->base_url, url, NSURL_COMPLETE)) { |
nsurl_unref(url); |
return true; |
} |
|
/* create a new iframe */ |
iframe = talloc(content->bctx, struct content_html_iframe); |
if (iframe == NULL) { |
nsurl_unref(url); |
return false; |
} |
|
talloc_set_destructor(iframe, box_iframes_talloc_destructor); |
|
iframe->box = box; |
iframe->margin_width = 0; |
iframe->margin_height = 0; |
iframe->name = NULL; |
iframe->url = url; |
iframe->scrolling = SCROLLING_AUTO; |
iframe->border = true; |
|
/* Add this iframe to the linked list of iframes */ |
iframe->next = content->iframe; |
content->iframe = iframe; |
|
/* fill in specified values */ |
err = dom_element_get_attribute(n, kstr_name, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
iframe->name = talloc_strdup(content->bctx, dom_string_data(s)); |
dom_string_unref(s); |
} |
|
err = dom_element_get_attribute(n, kstr_frameborder, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
i = atoi(dom_string_data(s)); |
iframe->border = (i != 0); |
dom_string_unref(s); |
} |
|
err = dom_element_get_attribute(n, kstr_bordercolor, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
css_color color; |
|
if (nscss_parse_colour(dom_string_data(s), &color)) |
iframe->border_colour = nscss_color_to_ns(color); |
|
dom_string_unref(s); |
} |
|
err = dom_element_get_attribute(n, kstr_scrolling, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc_yes)) |
iframe->scrolling = SCROLLING_YES; |
else if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc_no)) |
iframe->scrolling = SCROLLING_NO; |
dom_string_unref(s); |
} |
|
err = dom_element_get_attribute(n, kstr_marginwidth, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
iframe->margin_width = atoi(dom_string_data(s)); |
dom_string_unref(s); |
} |
|
err = dom_element_get_attribute(n, kstr_marginheight, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
iframe->margin_height = atoi(dom_string_data(s)); |
dom_string_unref(s); |
} |
|
/* box */ |
assert(box->style); |
box->flags |= IFRAME; |
|
/* Showing iframe, so don't show alternate content */ |
if (convert_children) |
*convert_children = false; |
return true; |
} |
|
|
/** |
* Form control [17.4]. |
*/ |
|
bool box_input(BOX_SPECIAL_PARAMS) |
{ |
struct form_control *gadget = NULL; |
dom_string *type = NULL; |
dom_exception err; |
nsurl *url; |
nserror error; |
|
dom_element_get_attribute(n, kstr_type, &type); |
|
gadget = html_forms_get_control_for_node(content->forms, n); |
if (gadget == NULL) |
goto no_memory; |
box->gadget = gadget; |
gadget->box = box; |
|
if (type && dom_string_caseless_lwc_isequal(type, |
corestring_lwc_password)) { |
if (box_input_text(n, content, box, 0, true) == false) |
goto no_memory; |
|
} else if (type && dom_string_caseless_lwc_isequal(type, |
corestring_lwc_file)) { |
box->type = BOX_INLINE_BLOCK; |
|
} else if (type && dom_string_caseless_lwc_isequal(type, |
corestring_lwc_hidden)) { |
/* no box for hidden inputs */ |
box->type = BOX_NONE; |
|
} else if (type && |
(dom_string_caseless_lwc_isequal(type, |
corestring_lwc_checkbox) || |
dom_string_caseless_lwc_isequal(type, |
corestring_lwc_radio))) { |
|
} else if (type && |
(dom_string_caseless_lwc_isequal(type, |
corestring_lwc_submit) || |
dom_string_caseless_lwc_isequal(type, |
corestring_lwc_reset) || |
dom_string_caseless_lwc_isequal(type, |
corestring_lwc_button))) { |
struct box *inline_container, *inline_box; |
|
if (box_button(n, content, box, 0) == false) |
goto no_memory; |
|
inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, |
content->bctx); |
if (inline_container == NULL) |
goto no_memory; |
|
inline_container->type = BOX_INLINE_CONTAINER; |
|
inline_box = box_create(NULL, box->style, false, 0, 0, |
box->title, 0, content->bctx); |
if (inline_box == NULL) |
goto no_memory; |
|
inline_box->type = BOX_TEXT; |
|
if (box->gadget->value != NULL) |
inline_box->text = talloc_strdup(content->bctx, |
box->gadget->value); |
else if (box->gadget->type == GADGET_SUBMIT) |
inline_box->text = talloc_strdup(content->bctx, |
messages_get("Form_Submit")); |
else if (box->gadget->type == GADGET_RESET) |
inline_box->text = talloc_strdup(content->bctx, |
messages_get("Form_Reset")); |
else |
inline_box->text = talloc_strdup(content->bctx, |
"Button"); |
|
if (inline_box->text == NULL) |
goto no_memory; |
|
inline_box->length = strlen(inline_box->text); |
|
box_add_child(inline_container, inline_box); |
|
box_add_child(box, inline_container); |
|
} else if (type && dom_string_caseless_lwc_isequal(type, |
corestring_lwc_image)) { |
gadget->type = GADGET_IMAGE; |
|
if (box->style && css_computed_display(box->style, |
box_is_root(n)) != CSS_DISPLAY_NONE && |
nsoption_bool(foreground_images) == true) { |
dom_string *s; |
|
err = dom_element_get_attribute(n, kstr_src, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
error = nsurl_join(content->base_url, |
dom_string_data(s), &url); |
dom_string_unref(s); |
if (error != NSERROR_OK) |
goto no_memory; |
|
/* if url is equivalent to the parent's url, |
* we've got infinite inclusion. stop it here |
*/ |
if (nsurl_compare(url, content->base_url, |
NSURL_COMPLETE) == false) { |
if (!html_fetch_object(content, url, |
box, image_types, |
content->base. |
available_width, |
1000, false)) { |
nsurl_unref(url); |
goto no_memory; |
} |
} |
nsurl_unref(url); |
} |
} |
} else { |
/* the default type is "text" */ |
if (box_input_text(n, content, box, 0, false) == false) |
goto no_memory; |
} |
|
if (type) |
dom_string_unref(type); |
|
*convert_children = false; |
return true; |
|
no_memory: |
if (type) |
dom_string_unref(type); |
|
return false; |
} |
|
|
/** |
* Helper function for box_input(). |
*/ |
|
bool box_input_text(BOX_SPECIAL_PARAMS, bool password) |
{ |
struct box *inline_container, *inline_box; |
|
box->type = BOX_INLINE_BLOCK; |
|
inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content->bctx); |
if (!inline_container) |
return false; |
inline_container->type = BOX_INLINE_CONTAINER; |
inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, |
content->bctx); |
if (!inline_box) |
return false; |
inline_box->type = BOX_TEXT; |
if (password) { |
inline_box->length = strlen(box->gadget->value); |
inline_box->text = talloc_array(content->bctx, char, |
inline_box->length + 1); |
if (!inline_box->text) |
return false; |
memset(inline_box->text, '*', inline_box->length); |
inline_box->text[inline_box->length] = '\0'; |
} else { |
/* replace spaces/TABs with hard spaces to prevent line |
* wrapping */ |
char *text = cnv_space2nbsp(box->gadget->value); |
if (!text) |
return false; |
inline_box->text = talloc_strdup(content->bctx, text); |
free(text); |
if (!inline_box->text) |
return false; |
inline_box->length = strlen(inline_box->text); |
} |
box_add_child(inline_container, inline_box); |
box_add_child(box, inline_container); |
|
return true; |
} |
|
|
/** |
* Push button [17.5]. |
*/ |
|
bool box_button(BOX_SPECIAL_PARAMS) |
{ |
struct form_control *gadget; |
|
gadget = html_forms_get_control_for_node(content->forms, n); |
if (!gadget) |
return false; |
|
box->gadget = gadget; |
gadget->box = box; |
|
box->type = BOX_INLINE_BLOCK; |
|
/* Just render the contents */ |
|
return true; |
} |
|
|
/** |
* Option selector [17.6]. |
*/ |
|
bool box_select(BOX_SPECIAL_PARAMS) |
{ |
struct box *inline_container; |
struct box *inline_box; |
struct form_control *gadget; |
dom_node *c, *c2; |
dom_node *next, *next2; |
dom_exception err; |
|
gadget = html_forms_get_control_for_node(content->forms, n); |
if (gadget == NULL) |
return false; |
|
err = dom_node_get_first_child(n, &c); |
if (err != DOM_NO_ERR) |
return false; |
|
while (c != NULL) { |
dom_string *name; |
|
err = dom_node_get_node_name(c, &name); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
if (dom_string_caseless_lwc_isequal(name, |
corestring_lwc_option)) { |
dom_string_unref(name); |
|
if (box_select_add_option(gadget, c) == false) { |
dom_node_unref(c); |
goto no_memory; |
} |
} else if (dom_string_caseless_lwc_isequal(name, |
corestring_lwc_optgroup)) { |
dom_string_unref(name); |
|
err = dom_node_get_first_child(c, &c2); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
while (c2 != NULL) { |
dom_string *c2_name; |
|
err = dom_node_get_node_name(c2, &c2_name); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c2); |
dom_node_unref(c); |
return false; |
} |
|
if (dom_string_caseless_lwc_isequal(c2_name, |
corestring_lwc_option)) { |
dom_string_unref(c2_name); |
|
if (box_select_add_option(gadget, |
c2) == false) { |
dom_node_unref(c2); |
dom_node_unref(c); |
goto no_memory; |
} |
} else { |
dom_string_unref(c2_name); |
} |
|
err = dom_node_get_next_sibling(c2, &next2); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c2); |
dom_node_unref(c); |
return false; |
} |
|
dom_node_unref(c2); |
c2 = next2; |
} |
} else { |
dom_string_unref(name); |
} |
|
err = dom_node_get_next_sibling(c, &next); |
if (err != DOM_NO_ERR) { |
dom_node_unref(c); |
return false; |
} |
|
dom_node_unref(c); |
c = next; |
} |
|
if (gadget->data.select.num_items == 0) { |
/* no options: ignore entire select */ |
return true; |
} |
|
box->type = BOX_INLINE_BLOCK; |
box->gadget = gadget; |
gadget->box = box; |
|
inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content->bctx); |
if (inline_container == NULL) |
goto no_memory; |
inline_container->type = BOX_INLINE_CONTAINER; |
inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, |
content->bctx); |
if (inline_box == NULL) |
goto no_memory; |
inline_box->type = BOX_TEXT; |
box_add_child(inline_container, inline_box); |
box_add_child(box, inline_container); |
|
if (gadget->data.select.multiple == false && |
gadget->data.select.num_selected == 0) { |
gadget->data.select.current = gadget->data.select.items; |
gadget->data.select.current->initial_selected = |
gadget->data.select.current->selected = true; |
gadget->data.select.num_selected = 1; |
} |
|
if (gadget->data.select.num_selected == 0) |
inline_box->text = talloc_strdup(content->bctx, |
messages_get("Form_None")); |
else if (gadget->data.select.num_selected == 1) |
inline_box->text = talloc_strdup(content->bctx, |
gadget->data.select.current->text); |
else |
inline_box->text = talloc_strdup(content->bctx, |
messages_get("Form_Many")); |
if (inline_box->text == NULL) |
goto no_memory; |
|
inline_box->length = strlen(inline_box->text); |
|
*convert_children = false; |
return true; |
|
no_memory: |
return false; |
} |
|
|
/** |
* Add an option to a form select control (helper function for box_select()). |
* |
* \param control select containing the option |
* \param n xml element node for <option> |
* \return true on success, false on memory exhaustion |
*/ |
|
bool box_select_add_option(struct form_control *control, dom_node *n) |
{ |
char *value = NULL; |
char *text = NULL; |
char *text_nowrap = NULL; |
bool selected; |
dom_string *content, *s; |
dom_exception err; |
|
err = dom_node_get_text_content(n, &content); |
if (err != DOM_NO_ERR) |
return false; |
|
if (content != NULL) { |
text = squash_whitespace(dom_string_data(content)); |
dom_string_unref(content); |
} else { |
text = strdup(""); |
} |
|
if (text == NULL) |
goto no_memory; |
|
err = dom_element_get_attribute(n, kstr_value, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
value = strdup(dom_string_data(s)); |
dom_string_unref(s); |
} else { |
value = strdup(text); |
} |
|
if (value == NULL) |
goto no_memory; |
|
dom_element_has_attribute(n, kstr_selected, &selected); |
|
/* replace spaces/TABs with hard spaces to prevent line wrapping */ |
text_nowrap = cnv_space2nbsp(text); |
if (text_nowrap == NULL) |
goto no_memory; |
|
if (form_add_option(control, value, text_nowrap, selected) == false) |
goto no_memory; |
|
free(text); |
|
return true; |
|
no_memory: |
free(value); |
free(text); |
free(text_nowrap); |
return false; |
} |
|
|
/** |
* Multi-line text field [17.7]. |
*/ |
|
bool box_textarea(BOX_SPECIAL_PARAMS) |
{ |
/* A textarea is an INLINE_BLOCK containing a single INLINE_CONTAINER, |
* which contains the text as runs of TEXT separated by BR. There is |
* at least one TEXT. The first and last boxes are TEXT. |
* Consecutive BR may not be present. These constraints are satisfied |
* by using a 0-length TEXT for blank lines. */ |
|
const char *current; |
dom_string *area_data = NULL; |
dom_exception err; |
struct box *inline_container, *inline_box, *br_box; |
char *s; |
size_t len; |
|
box->type = BOX_INLINE_BLOCK; |
box->gadget = html_forms_get_control_for_node(content->forms, n); |
if (box->gadget == NULL) |
return false; |
box->gadget->box = box; |
|
inline_container = box_create(NULL, 0, false, 0, 0, box->title, 0, |
content->bctx); |
if (inline_container == NULL) |
return false; |
inline_container->type = BOX_INLINE_CONTAINER; |
box_add_child(box, inline_container); |
|
err = dom_node_get_text_content(n, &area_data); |
if (err != DOM_NO_ERR) |
return false; |
|
if (area_data != NULL) { |
current = dom_string_data(area_data); |
} else { |
/* No content, or failed reading it: use a blank string */ |
current = ""; |
} |
|
while (true) { |
/* BOX_TEXT */ |
len = strcspn(current, "\r\n"); |
s = talloc_strndup(content->bctx, current, len); |
if (s == NULL) { |
if (area_data != NULL) |
dom_string_unref(area_data); |
return false; |
} |
|
inline_box = box_create(NULL, box->style, false, 0, 0, |
box->title, 0, content->bctx); |
if (inline_box == NULL) { |
if (area_data != NULL) |
dom_string_unref(area_data); |
return false; |
} |
inline_box->type = BOX_TEXT; |
inline_box->text = s; |
inline_box->length = len; |
box_add_child(inline_container, inline_box); |
|
current += len; |
if (current[0] == 0) |
/* finished */ |
break; |
|
/* BOX_BR */ |
br_box = box_create(NULL, box->style, false, 0, 0, box->title, |
0, content->bctx); |
if (br_box == NULL) { |
if (area_data != NULL) |
dom_string_unref(area_data); |
return false; |
} |
br_box->type = BOX_BR; |
box_add_child(inline_container, br_box); |
|
if (current[0] == '\r' && current[1] == '\n') |
current += 2; |
else |
current++; |
} |
|
if (area_data != NULL) |
dom_string_unref(area_data); |
|
*convert_children = false; |
return true; |
} |
|
|
/** |
* Embedded object (not in any HTML specification: |
* see http://wp.netscape.com/assist/net_sites/new_html3_prop.html ) |
*/ |
|
bool box_embed(BOX_SPECIAL_PARAMS) |
{ |
struct object_params *params; |
struct object_param *param; |
dom_namednodemap *attrs; |
unsigned long idx; |
uint32_t num_attrs; |
dom_string *src; |
dom_exception err; |
|
if (box->style && css_computed_display(box->style, |
box_is_root(n)) == CSS_DISPLAY_NONE) |
return true; |
|
params = talloc(content->bctx, struct object_params); |
if (params == NULL) |
return false; |
|
talloc_set_destructor(params, box_object_talloc_destructor); |
|
params->data = NULL; |
params->type = NULL; |
params->codetype = NULL; |
params->codebase = NULL; |
params->classid = NULL; |
params->params = NULL; |
|
/* src is a URL */ |
err = dom_element_get_attribute(n, kstr_src, &src); |
if (err != DOM_NO_ERR || src == NULL) |
return true; |
if (box_extract_link(dom_string_data(src), content->base_url, |
¶ms->data) == false) { |
dom_string_unref(src); |
return false; |
} |
|
dom_string_unref(src); |
|
if (params->data == NULL) |
return true; |
|
/* Don't include ourself */ |
if (nsurl_compare(content->base_url, params->data, NSURL_COMPLETE)) |
return true; |
|
/* add attributes as parameters to linked list */ |
err = dom_node_get_attributes(n, &attrs); |
if (err != DOM_NO_ERR) |
return false; |
|
err = dom_namednodemap_get_length(attrs, &num_attrs); |
if (err != DOM_NO_ERR) { |
dom_namednodemap_unref(attrs); |
return false; |
} |
|
for (idx = 0; idx < num_attrs; idx++) { |
dom_attr *attr; |
dom_string *name, *value; |
|
err = dom_namednodemap_item(attrs, idx, (void *) &attr); |
if (err != DOM_NO_ERR) { |
dom_namednodemap_unref(attrs); |
return false; |
} |
|
err = dom_attr_get_name(attr, &name); |
if (err != DOM_NO_ERR) { |
dom_namednodemap_unref(attrs); |
return false; |
} |
|
if (dom_string_caseless_lwc_isequal(name, corestring_lwc_src)) { |
dom_string_unref(name); |
continue; |
} |
|
err = dom_attr_get_value(attr, &value); |
if (err != DOM_NO_ERR) { |
dom_string_unref(name); |
dom_namednodemap_unref(attrs); |
return false; |
} |
|
param = talloc(content->bctx, struct object_param); |
if (param == NULL) { |
dom_string_unref(value); |
dom_string_unref(name); |
dom_namednodemap_unref(attrs); |
return false; |
} |
|
param->name = talloc_strdup(content->bctx, dom_string_data(name)); |
param->value = talloc_strdup(content->bctx, dom_string_data(value)); |
param->type = NULL; |
param->valuetype = talloc_strdup(content->bctx, "data"); |
param->next = NULL; |
|
dom_string_unref(value); |
dom_string_unref(name); |
|
if (param->name == NULL || param->value == NULL || |
param->valuetype == NULL) { |
dom_namednodemap_unref(attrs); |
return false; |
} |
|
param->next = params->params; |
params->params = param; |
} |
|
dom_namednodemap_unref(attrs); |
|
box->object_params = params; |
|
/* start fetch */ |
return html_fetch_object(content, params->data, box, CONTENT_ANY, |
content->base.available_width, 1000, false); |
} |
|
/** |
* \} |
*/ |
|
|
/** |
* Get the value of an XML element's attribute. |
* |
* \param n xmlNode, of type XML_ELEMENT_NODE |
* \param attribute name of attribute |
* \param context talloc context for result buffer |
* \param value updated to value, if the attribute is present |
* \return true on success, false if attribute present but memory exhausted |
* |
* Note that returning true does not imply that the attribute was found. If the |
* attribute was not found, *value will be unchanged. |
*/ |
|
bool box_get_attribute(dom_node *n, const char *attribute, |
void *context, char **value) |
{ |
char *result; |
dom_string *attr, *attr_name; |
dom_exception err; |
|
err = dom_string_create_interned((const uint8_t *) attribute, |
strlen(attribute), &attr_name); |
if (err != DOM_NO_ERR) |
return false; |
|
err = dom_element_get_attribute(n, attr_name, &attr); |
if (err != DOM_NO_ERR) { |
dom_string_unref(attr_name); |
return false; |
} |
|
dom_string_unref(attr_name); |
|
if (attr != NULL) { |
result = talloc_strdup(context, dom_string_data(attr)); |
|
dom_string_unref(attr); |
|
if (result == NULL) |
return false; |
|
*value = result; |
} |
|
return true; |
} |
|
|
/** |
* Extract a URL from a relative link, handling junk like whitespace and |
* attempting to read a real URL from "javascript:" links. |
* |
* \param rel relative URL taken from page |
* \param base base for relative URLs |
* \param result updated to target URL on heap, unchanged if extract failed |
* \return true on success, false on memory exhaustion |
*/ |
|
bool box_extract_link(const char *rel, nsurl *base, nsurl **result) |
{ |
char *s, *s1, *apos0 = 0, *apos1 = 0, *quot0 = 0, *quot1 = 0; |
unsigned int i, j, end; |
nserror error; |
|
s1 = s = malloc(3 * strlen(rel) + 1); |
if (!s) |
return false; |
|
/* copy to s, removing white space and control characters */ |
for (i = 0; rel[i] && isspace(rel[i]); i++) |
; |
for (end = strlen(rel); end != i && isspace(rel[end - 1]); end--) |
; |
for (j = 0; i != end; i++) { |
if ((unsigned char) rel[i] < 0x20) { |
; /* skip control characters */ |
} else if (rel[i] == ' ') { |
s[j++] = '%'; |
s[j++] = '2'; |
s[j++] = '0'; |
} else { |
s[j++] = rel[i]; |
} |
} |
s[j] = 0; |
|
/* extract first quoted string out of "javascript:" link */ |
if (strncmp(s, "javascript:", 11) == 0) { |
apos0 = strchr(s, '\''); |
if (apos0) |
apos1 = strchr(apos0 + 1, '\''); |
quot0 = strchr(s, '"'); |
if (quot0) |
quot1 = strchr(quot0 + 1, '"'); |
if (apos0 && apos1 && (!quot0 || !quot1 || apos0 < quot0)) { |
*apos1 = 0; |
s1 = apos0 + 1; |
} else if (quot0 && quot1) { |
*quot1 = 0; |
s1 = quot0 + 1; |
} |
} |
|
/* construct absolute URL */ |
error = nsurl_join(base, s1, result); |
free(s); |
if (error != NSERROR_OK) { |
*result = NULL; |
return false; |
} |
|
return true; |
} |
|
|
/** |
* Parse a multi-length-list, as defined by HTML 4.01. |
* |
* \param s string to parse |
* \param count updated to number of entries |
* \return array of struct box_multi_length, or 0 on memory exhaustion |
*/ |
|
struct frame_dimension *box_parse_multi_lengths(const char *s, |
unsigned int *count) |
{ |
char *end; |
unsigned int i, n; |
struct frame_dimension *length; |
|
for (i = 0, n = 1; s[i]; i++) |
if (s[i] == ',') |
n++; |
|
length = calloc(n, sizeof(struct frame_dimension)); |
if (!length) |
return NULL; |
|
for (i = 0; i != n; i++) { |
while (isspace(*s)) |
s++; |
length[i].value = strtof(s, &end); |
if (length[i].value <= 0) |
length[i].value = 1; |
s = end; |
switch (*s) { |
case '%': |
length[i].unit = FRAME_DIMENSION_PERCENT; |
break; |
case '*': |
length[i].unit = FRAME_DIMENSION_RELATIVE; |
break; |
default: |
length[i].unit = FRAME_DIMENSION_PIXELS; |
break; |
} |
while (*s && *s != ',') |
s++; |
if (*s == ',') |
s++; |
} |
|
*count = n; |
return length; |
} |
|