File: | content/handlers/html/box_construct.c |
Warning: | line 597, column 7 Access to field 'type' results in a dereference of a null pointer (loaded from field 'containing_block') |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* | |||
2 | * Copyright 2005 James Bursa <bursa@users.sourceforge.net> | |||
3 | * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> | |||
4 | * Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk> | |||
5 | * Copyright 2006 Richard Wilson <info@tinct.net> | |||
6 | * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> | |||
7 | * | |||
8 | * This file is part of NetSurf, http://www.netsurf-browser.org/ | |||
9 | * | |||
10 | * NetSurf is free software; you can redistribute it and/or modify | |||
11 | * it under the terms of the GNU General Public License as published by | |||
12 | * the Free Software Foundation; version 2 of the License. | |||
13 | * | |||
14 | * NetSurf is distributed in the hope that it will be useful, | |||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
17 | * GNU General Public License for more details. | |||
18 | * | |||
19 | * You should have received a copy of the GNU General Public License | |||
20 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
21 | */ | |||
22 | ||||
23 | /** | |||
24 | * \file | |||
25 | * Implementation of conversion from DOM tree to box tree. | |||
26 | */ | |||
27 | ||||
28 | #include <string.h> | |||
29 | #include <dom/dom.h> | |||
30 | ||||
31 | #include "utils/errors.h" | |||
32 | #include "utils/nsoption.h" | |||
33 | #include "utils/corestrings.h" | |||
34 | #include "utils/talloc.h" | |||
35 | #include "utils/string.h" | |||
36 | #include "utils/ascii.h" | |||
37 | #include "utils/nsurl.h" | |||
38 | #include "netsurf/misc.h" | |||
39 | #include "css/select.h" | |||
40 | #include "desktop/gui_internal.h" | |||
41 | ||||
42 | #include "html/private.h" | |||
43 | #include "html/object.h" | |||
44 | #include "html/box.h" | |||
45 | #include "html/box_manipulate.h" | |||
46 | #include "html/box_construct.h" | |||
47 | #include "html/box_special.h" | |||
48 | #include "html/box_normalise.h" | |||
49 | #include "html/form_internal.h" | |||
50 | ||||
51 | /** | |||
52 | * Context for box tree construction | |||
53 | */ | |||
54 | struct box_construct_ctx { | |||
55 | html_content *content; /**< Content we're constructing for */ | |||
56 | ||||
57 | dom_node *n; /**< Current node to process */ | |||
58 | ||||
59 | struct box *root_box; /**< Root box in the tree */ | |||
60 | ||||
61 | box_construct_complete_cb cb; /**< Callback to invoke on completion */ | |||
62 | ||||
63 | int *bctx; /**< talloc context */ | |||
64 | }; | |||
65 | ||||
66 | /** | |||
67 | * Transient properties for construction of current node | |||
68 | */ | |||
69 | struct box_construct_props { | |||
70 | /** Style from which to inherit, or NULL if none */ | |||
71 | const css_computed_style *parent_style; | |||
72 | /** Current link target, or NULL if none */ | |||
73 | struct nsurl *href; | |||
74 | /** Current frame target, or NULL if none */ | |||
75 | const char *target; | |||
76 | /** Current title attribute, or NULL if none */ | |||
77 | const char *title; | |||
78 | /** Identity of the current block-level container */ | |||
79 | struct box *containing_block; | |||
80 | /** Current container for inlines, or NULL if none | |||
81 | * \note If non-NULL, will be the last child of containing_block */ | |||
82 | struct box *inline_container; | |||
83 | /** Whether the current node is the root of the DOM tree */ | |||
84 | bool_Bool node_is_root; | |||
85 | }; | |||
86 | ||||
87 | static const content_type image_types = CONTENT_IMAGE; | |||
88 | ||||
89 | /* mapping from CSS display to box type | |||
90 | * this table must be in sync with libcss' css_display enum */ | |||
91 | static const box_type box_map[] = { | |||
92 | BOX_BLOCK, /* CSS_DISPLAY_INHERIT */ | |||
93 | BOX_INLINE, /* CSS_DISPLAY_INLINE */ | |||
94 | BOX_BLOCK, /* CSS_DISPLAY_BLOCK */ | |||
95 | BOX_BLOCK, /* CSS_DISPLAY_LIST_ITEM */ | |||
96 | BOX_INLINE, /* CSS_DISPLAY_RUN_IN */ | |||
97 | BOX_INLINE_BLOCK, /* CSS_DISPLAY_INLINE_BLOCK */ | |||
98 | BOX_TABLE, /* CSS_DISPLAY_TABLE */ | |||
99 | BOX_TABLE, /* CSS_DISPLAY_INLINE_TABLE */ | |||
100 | BOX_TABLE_ROW_GROUP, /* CSS_DISPLAY_TABLE_ROW_GROUP */ | |||
101 | BOX_TABLE_ROW_GROUP, /* CSS_DISPLAY_TABLE_HEADER_GROUP */ | |||
102 | BOX_TABLE_ROW_GROUP, /* CSS_DISPLAY_TABLE_FOOTER_GROUP */ | |||
103 | BOX_TABLE_ROW, /* CSS_DISPLAY_TABLE_ROW */ | |||
104 | BOX_NONE, /* CSS_DISPLAY_TABLE_COLUMN_GROUP */ | |||
105 | BOX_NONE, /* CSS_DISPLAY_TABLE_COLUMN */ | |||
106 | BOX_TABLE_CELL, /* CSS_DISPLAY_TABLE_CELL */ | |||
107 | BOX_INLINE, /* CSS_DISPLAY_TABLE_CAPTION */ | |||
108 | BOX_NONE, /* CSS_DISPLAY_NONE */ | |||
109 | BOX_FLEX, /* CSS_DISPLAY_FLEX */ | |||
110 | BOX_INLINE_FLEX, /* CSS_DISPLAY_INLINE_FLEX */ | |||
111 | BOX_BLOCK, /* CSS_DISPLAY_GRID */ | |||
112 | BOX_INLINE_BLOCK, /* CSS_DISPLAY_INLINE_GRID */ | |||
113 | }; | |||
114 | ||||
115 | ||||
116 | /** | |||
117 | * determine if a box is the root node | |||
118 | * | |||
119 | * \param n node to check | |||
120 | * \return true if node is root else false. | |||
121 | */ | |||
122 | static inline bool_Bool box_is_root(dom_node *n) | |||
123 | { | |||
124 | dom_node *parent; | |||
125 | dom_node_type type; | |||
126 | dom_exception err; | |||
127 | ||||
128 | err = dom_node_get_parent_node(n, &parent)dom_node_get_parent_node( (dom_node *) (n), (dom_node **) (& parent)); | |||
129 | if (err != DOM_NO_ERR) | |||
130 | return false0; | |||
131 | ||||
132 | if (parent != NULL((void*)0)) { | |||
133 | err = dom_node_get_node_type(parent, &type)dom_node_get_node_type( (dom_node *) (parent), (dom_node_type *) (&type)); | |||
134 | ||||
135 | dom_node_unref(parent)dom_node_unref((dom_node *) (parent)); | |||
136 | ||||
137 | if (err != DOM_NO_ERR) | |||
138 | return false0; | |||
139 | ||||
140 | if (type != DOM_DOCUMENT_NODE) | |||
141 | return false0; | |||
142 | } | |||
143 | ||||
144 | return true1; | |||
145 | } | |||
146 | ||||
147 | /** | |||
148 | * Extract transient construction properties | |||
149 | * | |||
150 | * \param n Current DOM node to convert | |||
151 | * \param props Property object to populate | |||
152 | */ | |||
153 | static void | |||
154 | box_extract_properties(dom_node *n, struct box_construct_props *props) | |||
155 | { | |||
156 | memset(props, 0, sizeof(*props)); | |||
157 | ||||
158 | props->node_is_root = box_is_root(n); | |||
159 | ||||
160 | /* Extract properties from containing DOM node */ | |||
161 | if (props->node_is_root == false0) { | |||
162 | dom_node *current_node = n; | |||
163 | dom_node *parent_node = NULL((void*)0); | |||
164 | struct box *parent_box; | |||
165 | dom_exception err; | |||
166 | ||||
167 | /* Find ancestor node containing parent box */ | |||
168 | while (true1) { | |||
169 | err = dom_node_get_parent_node(current_node,dom_node_get_parent_node( (dom_node *) (current_node), (dom_node **) (&parent_node)) | |||
170 | &parent_node)dom_node_get_parent_node( (dom_node *) (current_node), (dom_node **) (&parent_node)); | |||
171 | if (err != DOM_NO_ERR || parent_node == NULL((void*)0)) | |||
172 | break; | |||
173 | ||||
174 | parent_box = box_for_node(parent_node); | |||
175 | ||||
176 | if (parent_box != NULL((void*)0)) { | |||
177 | props->parent_style = parent_box->style; | |||
178 | props->href = parent_box->href; | |||
179 | props->target = parent_box->target; | |||
180 | props->title = parent_box->title; | |||
181 | ||||
182 | dom_node_unref(parent_node)dom_node_unref((dom_node *) (parent_node)); | |||
183 | break; | |||
184 | } else { | |||
185 | if (current_node != n) | |||
186 | dom_node_unref(current_node)dom_node_unref((dom_node *) (current_node)); | |||
187 | current_node = parent_node; | |||
188 | parent_node = NULL((void*)0); | |||
189 | } | |||
190 | } | |||
191 | ||||
192 | /* Find containing block (may be parent) */ | |||
193 | while (true1) { | |||
194 | struct box *b; | |||
195 | ||||
196 | err = dom_node_get_parent_node(current_node,dom_node_get_parent_node( (dom_node *) (current_node), (dom_node **) (&parent_node)) | |||
197 | &parent_node)dom_node_get_parent_node( (dom_node *) (current_node), (dom_node **) (&parent_node)); | |||
198 | if (err != DOM_NO_ERR || parent_node == NULL((void*)0)) { | |||
199 | if (current_node != n) | |||
200 | dom_node_unref(current_node)dom_node_unref((dom_node *) (current_node)); | |||
201 | break; | |||
202 | } | |||
203 | ||||
204 | if (current_node != n) | |||
205 | dom_node_unref(current_node)dom_node_unref((dom_node *) (current_node)); | |||
206 | ||||
207 | b = box_for_node(parent_node); | |||
208 | ||||
209 | /* Children of nodes that created an inline box | |||
210 | * will generate boxes which are attached as | |||
211 | * _siblings_ of the box generated for their | |||
212 | * parent node. Note, however, that we'll still | |||
213 | * use the parent node's styling as the parent | |||
214 | * style, above. */ | |||
215 | if (b != NULL((void*)0) && b->type != BOX_INLINE && | |||
216 | b->type != BOX_BR) { | |||
217 | props->containing_block = b; | |||
218 | ||||
219 | dom_node_unref(parent_node)dom_node_unref((dom_node *) (parent_node)); | |||
220 | break; | |||
221 | } else { | |||
222 | current_node = parent_node; | |||
223 | parent_node = NULL((void*)0); | |||
224 | } | |||
225 | } | |||
226 | } | |||
227 | ||||
228 | /* Compute current inline container, if any */ | |||
229 | if (props->containing_block != NULL((void*)0) && | |||
230 | props->containing_block->last != NULL((void*)0) && | |||
231 | props->containing_block->last->type == | |||
232 | BOX_INLINE_CONTAINER) | |||
233 | props->inline_container = props->containing_block->last; | |||
234 | } | |||
235 | ||||
236 | ||||
237 | /** | |||
238 | * Get the style for an element. | |||
239 | * | |||
240 | * \param c content of type CONTENT_HTML that is being processed | |||
241 | * \param parent_style style at this point in xml tree, or NULL for root | |||
242 | * \param root_style root node's style, or NULL for root | |||
243 | * \param n node in xml tree | |||
244 | * \return the new style, or NULL on memory exhaustion | |||
245 | */ | |||
246 | static css_select_results * | |||
247 | box_get_style(html_content *c, | |||
248 | const css_computed_style *parent_style, | |||
249 | const css_computed_style *root_style, | |||
250 | dom_node *n) | |||
251 | { | |||
252 | dom_string *s = NULL((void*)0); | |||
253 | css_stylesheet *inline_style = NULL((void*)0); | |||
254 | css_select_results *styles; | |||
255 | nscss_select_ctx ctx; | |||
256 | ||||
257 | /* Firstly, construct inline stylesheet, if any */ | |||
258 | if (nsoption_bool(author_level_css)(nsoptions[NSOPTION_author_level_css].value.b)) { | |||
259 | dom_exception err; | |||
260 | err = dom_element_get_attribute(n, corestring_dom_style, &s)dom_element_get_attribute( (dom_element *) (n), (corestring_dom_style ), (&s)); | |||
261 | if (err != DOM_NO_ERR) { | |||
262 | return NULL((void*)0); | |||
263 | } | |||
264 | } | |||
265 | ||||
266 | if (s != NULL((void*)0)) { | |||
267 | inline_style = nscss_create_inline_style( | |||
268 | (const uint8_t *) dom_string_data(s), | |||
269 | dom_string_byte_length(s), | |||
270 | c->encoding, | |||
271 | nsurl_access(c->base_url), | |||
272 | c->quirks != DOM_DOCUMENT_QUIRKS_MODE_NONE); | |||
273 | ||||
274 | dom_string_unref(s); | |||
275 | ||||
276 | if (inline_style == NULL((void*)0)) | |||
277 | return NULL((void*)0); | |||
278 | } | |||
279 | ||||
280 | /* Populate selection context */ | |||
281 | ctx.ctx = c->select_ctx; | |||
282 | ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); | |||
283 | ctx.base_url = c->base_url; | |||
284 | ctx.universal = c->universal; | |||
285 | ctx.root_style = root_style; | |||
286 | ctx.parent_style = parent_style; | |||
287 | ||||
288 | /* Select style for element */ | |||
289 | styles = nscss_get_style(&ctx, n, &c->media, &c->unit_len_ctx, | |||
290 | inline_style); | |||
291 | ||||
292 | /* No longer need inline style */ | |||
293 | if (inline_style != NULL((void*)0)) | |||
294 | css_stylesheet_destroy(inline_style); | |||
295 | ||||
296 | return styles; | |||
297 | } | |||
298 | ||||
299 | ||||
300 | /** | |||
301 | * Construct the box required for a generated element. | |||
302 | * | |||
303 | * \param n XML node of type XML_ELEMENT_NODE | |||
304 | * \param content Content of type CONTENT_HTML that is being processed | |||
305 | * \param box Box which may have generated content | |||
306 | * \param style Complete computed style for pseudo element, or NULL | |||
307 | * | |||
308 | * \todo This is currently incomplete. It just does enough to support | |||
309 | * the clearfix hack. (http://www.positioniseverything.net/easyclearing.html ) | |||
310 | */ | |||
311 | static void | |||
312 | box_construct_generate(dom_node *n, | |||
313 | html_content *content, | |||
314 | struct box *box, | |||
315 | const css_computed_style *style) | |||
316 | { | |||
317 | struct box *gen = NULL((void*)0); | |||
318 | enum css_display_e computed_display; | |||
319 | const css_computed_content_item *c_item; | |||
320 | ||||
321 | /* Nothing to generate if the parent box is not a block */ | |||
322 | if (box->type != BOX_BLOCK) | |||
323 | return; | |||
324 | ||||
325 | /* To determine if an element has a pseudo element, we select | |||
326 | * for it and test to see if the returned style's content | |||
327 | * property is set to normal. */ | |||
328 | if (style == NULL((void*)0) || | |||
329 | css_computed_content(style, &c_item) == | |||
330 | CSS_CONTENT_NORMAL) { | |||
331 | /* No pseudo element */ | |||
332 | return; | |||
333 | } | |||
334 | ||||
335 | /* create box for this element */ | |||
336 | computed_display = ns_computed_display(style, box_is_root(n)); | |||
337 | if (computed_display == CSS_DISPLAY_BLOCK || | |||
338 | computed_display == CSS_DISPLAY_TABLE) { | |||
339 | /* currently only support block level boxes */ | |||
340 | ||||
341 | /** \todo Not wise to drop const from the computed style */ | |||
342 | gen = box_create(NULL((void*)0), (css_computed_style *) style, | |||
343 | false0, NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0), content->bctx); | |||
344 | if (gen == NULL((void*)0)) { | |||
345 | return; | |||
346 | } | |||
347 | ||||
348 | /* set box type from computed display */ | |||
349 | gen->type = box_map[ns_computed_display( | |||
350 | style, box_is_root(n))]; | |||
351 | ||||
352 | box_add_child(box, gen); | |||
353 | } | |||
354 | } | |||
355 | ||||
356 | ||||
357 | /** | |||
358 | * Construct a list marker box | |||
359 | * | |||
360 | * \param box Box to attach marker to | |||
361 | * \param title Current title attribute | |||
362 | * \param ctx Box construction context | |||
363 | * \param parent Current block-level container | |||
364 | * \return true on success, false on memory exhaustion | |||
365 | */ | |||
366 | static bool_Bool | |||
367 | box_construct_marker(struct box *box, | |||
368 | const char *title, | |||
369 | struct box_construct_ctx *ctx, | |||
370 | struct box *parent) | |||
371 | { | |||
372 | lwc_string *image_uri; | |||
373 | struct box *marker; | |||
374 | enum css_list_style_type_e list_style_type; | |||
375 | ||||
376 | marker = box_create(NULL((void*)0), box->style, false0, NULL((void*)0), NULL((void*)0), title, | |||
377 | NULL((void*)0), ctx->bctx); | |||
378 | if (marker == false0) | |||
379 | return false0; | |||
380 | ||||
381 | marker->type = BOX_BLOCK; | |||
382 | ||||
383 | list_style_type = css_computed_list_style_type(box->style); | |||
384 | ||||
385 | /** \todo marker content (list-style-type) */ | |||
386 | switch (list_style_type) { | |||
387 | case CSS_LIST_STYLE_TYPE_DISC: | |||
388 | /* 2022 BULLET */ | |||
389 | marker->text = (char *) "\342\200\242"; | |||
390 | marker->length = 3; | |||
391 | break; | |||
392 | ||||
393 | case CSS_LIST_STYLE_TYPE_CIRCLE: | |||
394 | /* 25CB WHITE CIRCLE */ | |||
395 | marker->text = (char *) "\342\227\213"; | |||
396 | marker->length = 3; | |||
397 | break; | |||
398 | ||||
399 | case CSS_LIST_STYLE_TYPE_SQUARE: | |||
400 | /* 25AA BLACK SMALL SQUARE */ | |||
401 | marker->text = (char *) "\342\226\252"; | |||
402 | marker->length = 3; | |||
403 | break; | |||
404 | ||||
405 | default: | |||
406 | /* Numerical list counters get handled in layout. */ | |||
407 | /* Fall through. */ | |||
408 | case CSS_LIST_STYLE_TYPE_NONE: | |||
409 | marker->text = NULL((void*)0); | |||
410 | marker->length = 0; | |||
411 | break; | |||
412 | } | |||
413 | ||||
414 | if (css_computed_list_style_image(box->style, &image_uri) == CSS_LIST_STYLE_IMAGE_URI && | |||
415 | (image_uri != NULL((void*)0)) && | |||
416 | (nsoption_bool(foreground_images)(nsoptions[NSOPTION_foreground_images].value.b) == true1)) { | |||
417 | nsurl *url; | |||
418 | nserror error; | |||
419 | ||||
420 | /* TODO: we get a url out of libcss as a lwc string, but | |||
421 | * earlier we already had it as a nsurl after we | |||
422 | * nsurl_joined it. Can this be improved? | |||
423 | * For now, just making another nsurl. */ | |||
424 | error = nsurl_create(lwc_string_data(image_uri)({((image_uri != ((void*)0)) ? (void) (0) : __assert_fail ("image_uri != NULL" , "content/handlers/html/box_construct.c", 424, __extension__ __PRETTY_FUNCTION__)); (const char *)((image_uri)+1);}), &url); | |||
425 | if (error != NSERROR_OK) | |||
426 | return false0; | |||
427 | ||||
428 | if (html_fetch_object(ctx->content, | |||
429 | url, | |||
430 | marker, | |||
431 | image_types, | |||
432 | false0) == false0) { | |||
433 | nsurl_unref(url); | |||
434 | return false0; | |||
435 | } | |||
436 | nsurl_unref(url); | |||
437 | } | |||
438 | ||||
439 | box->list_marker = marker; | |||
440 | marker->parent = box; | |||
441 | ||||
442 | return true1; | |||
443 | } | |||
444 | ||||
445 | static inline bool_Bool box__style_is_float(const struct box *box) | |||
446 | { | |||
447 | return css_computed_float(box->style) == CSS_FLOAT_LEFT || | |||
448 | css_computed_float(box->style) == CSS_FLOAT_RIGHT; | |||
449 | } | |||
450 | ||||
451 | static inline bool_Bool box__is_flex(const struct box *box) | |||
452 | { | |||
453 | return box->type == BOX_FLEX || box->type == BOX_INLINE_FLEX; | |||
454 | } | |||
455 | ||||
456 | static inline bool_Bool box__containing_block_is_flex( | |||
457 | const struct box_construct_props *props) | |||
458 | { | |||
459 | return props->containing_block != NULL((void*)0) && | |||
460 | box__is_flex(props->containing_block); | |||
461 | } | |||
462 | ||||
463 | /** | |||
464 | * Construct the box tree for an XML element. | |||
465 | * | |||
466 | * \param ctx Tree construction context | |||
467 | * \param convert_children Whether to convert children | |||
468 | * \return true on success, false on memory exhaustion | |||
469 | */ | |||
470 | static bool_Bool | |||
471 | box_construct_element(struct box_construct_ctx *ctx, bool_Bool *convert_children) | |||
472 | { | |||
473 | dom_string *title0, *s; | |||
474 | lwc_string *id = NULL((void*)0); | |||
475 | enum css_display_e css_display; | |||
476 | struct box *box = NULL((void*)0), *old_box; | |||
477 | css_select_results *styles = NULL((void*)0); | |||
478 | lwc_string *bgimage_uri; | |||
479 | dom_exception err; | |||
480 | struct box_construct_props props; | |||
481 | const css_computed_style *root_style = NULL((void*)0); | |||
482 | ||||
483 | assert(ctx->n != NULL)((ctx->n != ((void*)0)) ? (void) (0) : __assert_fail ("ctx->n != NULL" , "content/handlers/html/box_construct.c", 483, __extension__ __PRETTY_FUNCTION__)); | |||
| ||||
484 | ||||
485 | box_extract_properties(ctx->n, &props); | |||
486 | ||||
487 | if (props.containing_block != NULL((void*)0)) { | |||
488 | /* In case the containing block is a pre block, we clear | |||
489 | * the PRE_STRIP flag since it is not used if we follow | |||
490 | * the pre with a tag */ | |||
491 | props.containing_block->flags &= ~PRE_STRIP; | |||
492 | } | |||
493 | ||||
494 | if (props.node_is_root == false0) { | |||
495 | root_style = ctx->root_box->style; | |||
496 | } | |||
497 | ||||
498 | styles = box_get_style(ctx->content, props.parent_style, root_style, | |||
499 | ctx->n); | |||
500 | if (styles == NULL((void*)0)) | |||
501 | return false0; | |||
502 | ||||
503 | /* Extract title attribute, if present */ | |||
504 | err = dom_element_get_attribute(ctx->n, corestring_dom_title, &title0)dom_element_get_attribute( (dom_element *) (ctx->n), (corestring_dom_title ), (&title0)); | |||
505 | if (err != DOM_NO_ERR) | |||
506 | return false0; | |||
507 | ||||
508 | if (title0 != NULL((void*)0)) { | |||
509 | char *t = squash_whitespace(dom_string_data(title0)); | |||
510 | ||||
511 | dom_string_unref(title0); | |||
512 | ||||
513 | if (t == NULL((void*)0)) | |||
514 | return false0; | |||
515 | ||||
516 | props.title = talloc_strdup(ctx->bctx, t); | |||
517 | ||||
518 | free(t); | |||
519 | ||||
520 | if (props.title == NULL((void*)0)) | |||
521 | return false0; | |||
522 | } | |||
523 | ||||
524 | /* Extract id attribute, if present */ | |||
525 | err = dom_element_get_attribute(ctx->n, corestring_dom_id, &s)dom_element_get_attribute( (dom_element *) (ctx->n), (corestring_dom_id ), (&s)); | |||
526 | if (err != DOM_NO_ERR) | |||
527 | return false0; | |||
528 | ||||
529 | if (s != NULL((void*)0)) { | |||
530 | err = dom_string_intern(s, &id); | |||
531 | if (err != DOM_NO_ERR) | |||
532 | id = NULL((void*)0); | |||
533 | ||||
534 | dom_string_unref(s); | |||
535 | } | |||
536 | ||||
537 | box = box_create(styles, styles->styles[CSS_PSEUDO_ELEMENT_NONE], false0, | |||
538 | props.href, props.target, props.title, id, | |||
539 | ctx->bctx); | |||
540 | if (box == NULL((void*)0)) | |||
541 | return false0; | |||
542 | ||||
543 | /* If this is the root box, add it to the context */ | |||
544 | if (props.node_is_root
| |||
545 | ctx->root_box = box; | |||
546 | ||||
547 | /* Deal with colspan/rowspan */ | |||
548 | err = dom_element_get_attribute(ctx->n, corestring_dom_colspan, &s)dom_element_get_attribute( (dom_element *) (ctx->n), (corestring_dom_colspan ), (&s)); | |||
549 | if (err != DOM_NO_ERR) | |||
550 | return false0; | |||
551 | ||||
552 | if (s != NULL((void*)0)) { | |||
553 | const char *val = dom_string_data(s); | |||
554 | ||||
555 | if ('0' <= val[0] && val[0] <= '9') | |||
556 | box->columns = strtol(val, NULL((void*)0), 10); | |||
557 | ||||
558 | dom_string_unref(s); | |||
559 | } | |||
560 | ||||
561 | err = dom_element_get_attribute(ctx->n, corestring_dom_rowspan, &s)dom_element_get_attribute( (dom_element *) (ctx->n), (corestring_dom_rowspan ), (&s)); | |||
562 | if (err != DOM_NO_ERR) | |||
563 | return false0; | |||
564 | ||||
565 | if (s != NULL((void*)0)) { | |||
566 | const char *val = dom_string_data(s); | |||
567 | ||||
568 | if ('0' <= val[0] && val[0] <= '9') | |||
569 | box->rows = strtol(val, NULL((void*)0), 10); | |||
570 | ||||
571 | dom_string_unref(s); | |||
572 | } | |||
573 | ||||
574 | css_display = ns_computed_display_static(box->style); | |||
575 | ||||
576 | /* Set box type from computed display */ | |||
577 | if ((css_computed_position(box->style) == CSS_POSITION_ABSOLUTE || | |||
578 | css_computed_position(box->style) == CSS_POSITION_FIXED) && | |||
579 | (css_display == CSS_DISPLAY_INLINE || | |||
580 | css_display == CSS_DISPLAY_INLINE_BLOCK || | |||
581 | css_display == CSS_DISPLAY_INLINE_TABLE || | |||
582 | css_display == CSS_DISPLAY_INLINE_FLEX)) { | |||
583 | /* Special case for absolute positioning: make absolute inlines | |||
584 | * into inline block so that the boxes are constructed in an | |||
585 | * inline container as if they were not absolutely positioned. | |||
586 | * Layout expects and handles this. */ | |||
587 | box->type = box_map[CSS_DISPLAY_INLINE_BLOCK]; | |||
588 | } else if (props.node_is_root
| |||
589 | /* Special case for root element: force it to BLOCK, or the | |||
590 | * rest of the layout will break. */ | |||
591 | box->type = BOX_BLOCK; | |||
592 | } else { | |||
593 | /* Normal mapping */ | |||
594 | box->type = box_map[ns_computed_display(box->style, | |||
595 | props.node_is_root)]; | |||
596 | ||||
597 | if (props.containing_block->type == BOX_FLEX || | |||
| ||||
598 | props.containing_block->type == BOX_INLINE_FLEX) { | |||
599 | /* Blockification */ | |||
600 | switch (box->type) { | |||
601 | case BOX_INLINE_FLEX: | |||
602 | box->type = BOX_FLEX; | |||
603 | break; | |||
604 | case BOX_INLINE_BLOCK: | |||
605 | box->type = BOX_BLOCK; | |||
606 | break; | |||
607 | default: | |||
608 | break; | |||
609 | } | |||
610 | } | |||
611 | } | |||
612 | ||||
613 | if (convert_special_elements(ctx->n, | |||
614 | ctx->content, | |||
615 | box, | |||
616 | convert_children) == false0) { | |||
617 | return false0; | |||
618 | } | |||
619 | ||||
620 | /* Handle the :before pseudo element */ | |||
621 | if (!(box->flags & IS_REPLACED)) { | |||
622 | box_construct_generate(ctx->n, ctx->content, box, | |||
623 | box->styles->styles[CSS_PSEUDO_ELEMENT_BEFORE]); | |||
624 | } | |||
625 | ||||
626 | if (box->type == BOX_NONE || (ns_computed_display(box->style, | |||
627 | props.node_is_root) == CSS_DISPLAY_NONE && | |||
628 | props.node_is_root == false0)) { | |||
629 | css_select_results_destroy(styles); | |||
630 | box->styles = NULL((void*)0); | |||
631 | box->style = NULL((void*)0); | |||
632 | ||||
633 | /* Invalidate associated gadget, if any */ | |||
634 | if (box->gadget != NULL((void*)0)) { | |||
635 | box->gadget->box = NULL((void*)0); | |||
636 | box->gadget = NULL((void*)0); | |||
637 | } | |||
638 | ||||
639 | /* Can't do this, because the lifetimes of boxes and gadgets | |||
640 | * are inextricably linked. Fortunately, talloc will save us | |||
641 | * (for now) */ | |||
642 | /* box_free_box(box); */ | |||
643 | ||||
644 | *convert_children = false0; | |||
645 | ||||
646 | return true1; | |||
647 | } | |||
648 | ||||
649 | /* Attach DOM node to box */ | |||
650 | err = dom_node_set_user_data(ctx->n,dom_node_set_user_data( (dom_node *) (ctx->n), (corestring_dom___ns_key_box_node_data ), (void *) (box), (dom_user_data_handler) ((void*)0), (void * *) ((void *) &old_box)) | |||
651 | corestring_dom___ns_key_box_node_data, box, NULL,dom_node_set_user_data( (dom_node *) (ctx->n), (corestring_dom___ns_key_box_node_data ), (void *) (box), (dom_user_data_handler) ((void*)0), (void * *) ((void *) &old_box)) | |||
652 | (void *) &old_box)dom_node_set_user_data( (dom_node *) (ctx->n), (corestring_dom___ns_key_box_node_data ), (void *) (box), (dom_user_data_handler) ((void*)0), (void * *) ((void *) &old_box)); | |||
653 | if (err != DOM_NO_ERR) | |||
654 | return false0; | |||
655 | ||||
656 | /* Attach box to DOM node */ | |||
657 | box->node = dom_node_ref(ctx->n)dom_node_ref((dom_node *) (ctx->n)); | |||
658 | ||||
659 | if (props.inline_container == NULL((void*)0) && | |||
660 | (box->type == BOX_INLINE || | |||
661 | box->type == BOX_BR || | |||
662 | box->type == BOX_INLINE_BLOCK || | |||
663 | box->type == BOX_INLINE_FLEX || | |||
664 | (box__style_is_float(box) && | |||
665 | !box__containing_block_is_flex(&props))) && | |||
666 | props.node_is_root == false0) { | |||
667 | /* Found an inline child of a block without a current container | |||
668 | * (i.e. this box is the first child of its parent, or was | |||
669 | * preceded by block-level siblings) */ | |||
670 | assert(props.containing_block != NULL &&((props.containing_block != ((void*)0) && "Box must have containing block." ) ? (void) (0) : __assert_fail ("props.containing_block != NULL && \"Box must have containing block.\"" , "content/handlers/html/box_construct.c", 671, __extension__ __PRETTY_FUNCTION__)) | |||
671 | "Box must have containing block.")((props.containing_block != ((void*)0) && "Box must have containing block." ) ? (void) (0) : __assert_fail ("props.containing_block != NULL && \"Box must have containing block.\"" , "content/handlers/html/box_construct.c", 671, __extension__ __PRETTY_FUNCTION__)); | |||
672 | ||||
673 | props.inline_container = box_create(NULL((void*)0), NULL((void*)0), false0, NULL((void*)0), | |||
674 | NULL((void*)0), NULL((void*)0), NULL((void*)0), ctx->bctx); | |||
675 | if (props.inline_container == NULL((void*)0)) | |||
676 | return false0; | |||
677 | ||||
678 | props.inline_container->type = BOX_INLINE_CONTAINER; | |||
679 | ||||
680 | box_add_child(props.containing_block, props.inline_container); | |||
681 | } | |||
682 | ||||
683 | /* Kick off fetch for any background image */ | |||
684 | if (css_computed_background_image(box->style, &bgimage_uri) == | |||
685 | CSS_BACKGROUND_IMAGE_IMAGE && bgimage_uri != NULL((void*)0) && | |||
686 | nsoption_bool(background_images)(nsoptions[NSOPTION_background_images].value.b) == true1) { | |||
687 | nsurl *url; | |||
688 | nserror error; | |||
689 | ||||
690 | /* TODO: we get a url out of libcss as a lwc string, but | |||
691 | * earlier we already had it as a nsurl after we | |||
692 | * nsurl_joined it. Can this be improved? | |||
693 | * For now, just making another nsurl. */ | |||
694 | error = nsurl_create(lwc_string_data(bgimage_uri)({((bgimage_uri != ((void*)0)) ? (void) (0) : __assert_fail ( "bgimage_uri != NULL", "content/handlers/html/box_construct.c" , 694, __extension__ __PRETTY_FUNCTION__)); (const char *)((bgimage_uri )+1);}), &url); | |||
695 | if (error == NSERROR_OK) { | |||
696 | /* Fetch image if we got a valid URL */ | |||
697 | if (html_fetch_object(ctx->content, | |||
698 | url, | |||
699 | box, | |||
700 | image_types, | |||
701 | true1) == false0) { | |||
702 | nsurl_unref(url); | |||
703 | return false0; | |||
704 | } | |||
705 | nsurl_unref(url); | |||
706 | } | |||
707 | } | |||
708 | ||||
709 | if (*convert_children) | |||
710 | box->flags |= CONVERT_CHILDREN; | |||
711 | ||||
712 | if (box->type == BOX_INLINE || box->type == BOX_BR || | |||
713 | box->type == BOX_INLINE_FLEX || | |||
714 | box->type == BOX_INLINE_BLOCK) { | |||
715 | /* Inline container must exist, as we'll have | |||
716 | * created it above if it didn't */ | |||
717 | assert(props.inline_container != NULL)((props.inline_container != ((void*)0)) ? (void) (0) : __assert_fail ("props.inline_container != NULL", "content/handlers/html/box_construct.c" , 717, __extension__ __PRETTY_FUNCTION__)); | |||
718 | ||||
719 | box_add_child(props.inline_container, box); | |||
720 | } else { | |||
721 | if (ns_computed_display(box->style, props.node_is_root) == | |||
722 | CSS_DISPLAY_LIST_ITEM) { | |||
723 | /* List item: compute marker */ | |||
724 | if (box_construct_marker(box, props.title, ctx, | |||
725 | props.containing_block) == false0) | |||
726 | return false0; | |||
727 | } | |||
728 | ||||
729 | if (props.node_is_root == false0 && | |||
730 | box__containing_block_is_flex(&props) == false0 && | |||
731 | (css_computed_float(box->style) == | |||
732 | CSS_FLOAT_LEFT || | |||
733 | css_computed_float(box->style) == | |||
734 | CSS_FLOAT_RIGHT)) { | |||
735 | /* Float: insert a float between the parent and box. */ | |||
736 | struct box *flt = box_create(NULL((void*)0), NULL((void*)0), false0, | |||
737 | props.href, props.target, props.title, | |||
738 | NULL((void*)0), ctx->bctx); | |||
739 | if (flt == NULL((void*)0)) | |||
740 | return false0; | |||
741 | ||||
742 | if (css_computed_float(box->style) == CSS_FLOAT_LEFT) | |||
743 | flt->type = BOX_FLOAT_LEFT; | |||
744 | else | |||
745 | flt->type = BOX_FLOAT_RIGHT; | |||
746 | ||||
747 | box_add_child(props.inline_container, flt); | |||
748 | box_add_child(flt, box); | |||
749 | } else { | |||
750 | /* Non-floated block-level box: add to containing block | |||
751 | * if there is one. If we're the root box, then there | |||
752 | * won't be. */ | |||
753 | if (props.containing_block != NULL((void*)0)) | |||
754 | box_add_child(props.containing_block, box); | |||
755 | } | |||
756 | } | |||
757 | ||||
758 | return true1; | |||
759 | } | |||
760 | ||||
761 | ||||
762 | /** | |||
763 | * Complete construction of the box tree for an element. | |||
764 | * | |||
765 | * \param n DOM node to construct for | |||
766 | * \param content Containing document | |||
767 | * | |||
768 | * This will be called after all children of an element have been processed | |||
769 | */ | |||
770 | static void box_construct_element_after(dom_node *n, html_content *content) | |||
771 | { | |||
772 | struct box_construct_props props; | |||
773 | struct box *box = box_for_node(n); | |||
774 | ||||
775 | assert(box != NULL)((box != ((void*)0)) ? (void) (0) : __assert_fail ("box != NULL" , "content/handlers/html/box_construct.c", 775, __extension__ __PRETTY_FUNCTION__)); | |||
776 | ||||
777 | box_extract_properties(n, &props); | |||
778 | ||||
779 | if (box->type == BOX_INLINE || box->type == BOX_BR) { | |||
780 | /* Insert INLINE_END into containing block */ | |||
781 | struct box *inline_end; | |||
782 | bool_Bool has_children; | |||
783 | dom_exception err; | |||
784 | ||||
785 | err = dom_node_has_child_nodes(n, &has_children)dom_node_has_child_nodes( (dom_node *) (n), (_Bool *) (&has_children )); | |||
786 | if (err != DOM_NO_ERR) | |||
787 | return; | |||
788 | ||||
789 | if (has_children == false0 || | |||
790 | (box->flags & CONVERT_CHILDREN) == 0) { | |||
791 | /* No children, or didn't want children converted */ | |||
792 | return; | |||
793 | } | |||
794 | ||||
795 | if (props.inline_container == NULL((void*)0)) { | |||
796 | /* Create inline container if we don't have one */ | |||
797 | props.inline_container = box_create(NULL((void*)0), NULL((void*)0), false0, | |||
798 | NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0), content->bctx); | |||
799 | if (props.inline_container == NULL((void*)0)) | |||
800 | return; | |||
801 | ||||
802 | props.inline_container->type = BOX_INLINE_CONTAINER; | |||
803 | ||||
804 | box_add_child(props.containing_block, | |||
805 | props.inline_container); | |||
806 | } | |||
807 | ||||
808 | inline_end = box_create(NULL((void*)0), box->style, false0, | |||
809 | box->href, box->target, box->title, | |||
810 | box->id == NULL((void*)0) ? NULL((void*)0) : | |||
811 | lwc_string_ref(box->id)({lwc_string *__lwc_s = (box->id); ((__lwc_s != ((void*)0) ) ? (void) (0) : __assert_fail ("__lwc_s != NULL", "content/handlers/html/box_construct.c" , 811, __extension__ __PRETTY_FUNCTION__)); __lwc_s->refcnt ++; __lwc_s;}), content->bctx); | |||
812 | if (inline_end != NULL((void*)0)) { | |||
813 | inline_end->type = BOX_INLINE_END; | |||
814 | ||||
815 | assert(props.inline_container != NULL)((props.inline_container != ((void*)0)) ? (void) (0) : __assert_fail ("props.inline_container != NULL", "content/handlers/html/box_construct.c" , 815, __extension__ __PRETTY_FUNCTION__)); | |||
816 | ||||
817 | box_add_child(props.inline_container, inline_end); | |||
818 | ||||
819 | box->inline_end = inline_end; | |||
820 | inline_end->inline_end = box; | |||
821 | } | |||
822 | } else if (!(box->flags & IS_REPLACED)) { | |||
823 | /* Handle the :after pseudo element */ | |||
824 | box_construct_generate(n, content, box, | |||
825 | box->styles->styles[CSS_PSEUDO_ELEMENT_AFTER]); | |||
826 | } | |||
827 | } | |||
828 | ||||
829 | ||||
830 | /** | |||
831 | * Find the next node in the DOM tree, completing element construction | |||
832 | * where appropriate. | |||
833 | * | |||
834 | * \param n Current node | |||
835 | * \param content Containing content | |||
836 | * \param convert_children Whether to consider children of \a n | |||
837 | * \return Next node to process, or NULL if complete | |||
838 | * | |||
839 | * \note \a n will be unreferenced | |||
840 | */ | |||
841 | static dom_node * | |||
842 | next_node(dom_node *n, html_content *content, bool_Bool convert_children) | |||
843 | { | |||
844 | dom_node *next = NULL((void*)0); | |||
845 | bool_Bool has_children; | |||
846 | dom_exception err; | |||
847 | ||||
848 | err = dom_node_has_child_nodes(n, &has_children)dom_node_has_child_nodes( (dom_node *) (n), (_Bool *) (&has_children )); | |||
849 | if (err != DOM_NO_ERR) { | |||
850 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
851 | return NULL((void*)0); | |||
852 | } | |||
853 | ||||
854 | if (convert_children && has_children) { | |||
855 | err = dom_node_get_first_child(n, &next)dom_node_get_first_child( (dom_node *) (n), (dom_node **) (& next)); | |||
856 | if (err != DOM_NO_ERR) { | |||
857 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
858 | return NULL((void*)0); | |||
859 | } | |||
860 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
861 | } else { | |||
862 | err = dom_node_get_next_sibling(n, &next)dom_node_get_next_sibling( (dom_node *) (n), (dom_node **) (& next)); | |||
863 | if (err != DOM_NO_ERR) { | |||
864 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
865 | return NULL((void*)0); | |||
866 | } | |||
867 | ||||
868 | if (next != NULL((void*)0)) { | |||
869 | if (box_for_node(n) != NULL((void*)0)) | |||
870 | box_construct_element_after(n, content); | |||
871 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
872 | } else { | |||
873 | if (box_for_node(n) != NULL((void*)0)) | |||
874 | box_construct_element_after(n, content); | |||
875 | ||||
876 | while (box_is_root(n) == false0) { | |||
877 | dom_node *parent = NULL((void*)0); | |||
878 | dom_node *parent_next = NULL((void*)0); | |||
879 | ||||
880 | err = dom_node_get_parent_node(n, &parent)dom_node_get_parent_node( (dom_node *) (n), (dom_node **) (& parent)); | |||
881 | if (err != DOM_NO_ERR) { | |||
882 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
883 | return NULL((void*)0); | |||
884 | } | |||
885 | ||||
886 | assert(parent != NULL)((parent != ((void*)0)) ? (void) (0) : __assert_fail ("parent != NULL" , "content/handlers/html/box_construct.c", 886, __extension__ __PRETTY_FUNCTION__)); | |||
887 | ||||
888 | err = dom_node_get_next_sibling(parent,dom_node_get_next_sibling( (dom_node *) (parent), (dom_node * *) (&parent_next)) | |||
889 | &parent_next)dom_node_get_next_sibling( (dom_node *) (parent), (dom_node * *) (&parent_next)); | |||
890 | if (err != DOM_NO_ERR) { | |||
891 | dom_node_unref(parent)dom_node_unref((dom_node *) (parent)); | |||
892 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
893 | return NULL((void*)0); | |||
894 | } | |||
895 | ||||
896 | if (parent_next != NULL((void*)0)) { | |||
897 | dom_node_unref(parent_next)dom_node_unref((dom_node *) (parent_next)); | |||
898 | dom_node_unref(parent)dom_node_unref((dom_node *) (parent)); | |||
899 | break; | |||
900 | } | |||
901 | ||||
902 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
903 | n = parent; | |||
904 | parent = NULL((void*)0); | |||
905 | ||||
906 | if (box_for_node(n) != NULL((void*)0)) { | |||
907 | box_construct_element_after( | |||
908 | n, content); | |||
909 | } | |||
910 | } | |||
911 | ||||
912 | if (box_is_root(n) == false0) { | |||
913 | dom_node *parent = NULL((void*)0); | |||
914 | ||||
915 | err = dom_node_get_parent_node(n, &parent)dom_node_get_parent_node( (dom_node *) (n), (dom_node **) (& parent)); | |||
916 | if (err != DOM_NO_ERR) { | |||
917 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
918 | return NULL((void*)0); | |||
919 | } | |||
920 | ||||
921 | assert(parent != NULL)((parent != ((void*)0)) ? (void) (0) : __assert_fail ("parent != NULL" , "content/handlers/html/box_construct.c", 921, __extension__ __PRETTY_FUNCTION__)); | |||
922 | ||||
923 | err = dom_node_get_next_sibling(parent, &next)dom_node_get_next_sibling( (dom_node *) (parent), (dom_node * *) (&next)); | |||
924 | if (err != DOM_NO_ERR) { | |||
925 | dom_node_unref(parent)dom_node_unref((dom_node *) (parent)); | |||
926 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
927 | return NULL((void*)0); | |||
928 | } | |||
929 | ||||
930 | if (box_for_node(parent) != NULL((void*)0)) { | |||
931 | box_construct_element_after(parent, | |||
932 | content); | |||
933 | } | |||
934 | ||||
935 | dom_node_unref(parent)dom_node_unref((dom_node *) (parent)); | |||
936 | } | |||
937 | ||||
938 | dom_node_unref(n)dom_node_unref((dom_node *) (n)); | |||
939 | } | |||
940 | } | |||
941 | ||||
942 | return next; | |||
943 | } | |||
944 | ||||
945 | ||||
946 | /** | |||
947 | * Apply the CSS text-transform property to given text for its ASCII chars. | |||
948 | * | |||
949 | * \param s string to transform | |||
950 | * \param len length of s | |||
951 | * \param tt transform type | |||
952 | */ | |||
953 | static void | |||
954 | box_text_transform(char *s, unsigned int len, enum css_text_transform_e tt) | |||
955 | { | |||
956 | unsigned int i; | |||
957 | if (len == 0) | |||
958 | return; | |||
959 | switch (tt) { | |||
960 | case CSS_TEXT_TRANSFORM_UPPERCASE: | |||
961 | for (i = 0; i < len; ++i) | |||
962 | if ((unsigned char) s[i] < 0x80) | |||
963 | s[i] = ascii_to_upper(s[i]); | |||
964 | break; | |||
965 | case CSS_TEXT_TRANSFORM_LOWERCASE: | |||
966 | for (i = 0; i < len; ++i) | |||
967 | if ((unsigned char) s[i] < 0x80) | |||
968 | s[i] = ascii_to_lower(s[i]); | |||
969 | break; | |||
970 | case CSS_TEXT_TRANSFORM_CAPITALIZE: | |||
971 | if ((unsigned char) s[0] < 0x80) | |||
972 | s[0] = ascii_to_upper(s[0]); | |||
973 | for (i = 1; i < len; ++i) | |||
974 | if ((unsigned char) s[i] < 0x80 && | |||
975 | ascii_is_space(s[i - 1])) | |||
976 | s[i] = ascii_to_upper(s[i]); | |||
977 | break; | |||
978 | default: | |||
979 | break; | |||
980 | } | |||
981 | } | |||
982 | ||||
983 | ||||
984 | /** | |||
985 | * Construct the box tree for an XML text node. | |||
986 | * | |||
987 | * \param ctx Tree construction context | |||
988 | * \return true on success, false on memory exhaustion | |||
989 | */ | |||
990 | static bool_Bool box_construct_text(struct box_construct_ctx *ctx) | |||
991 | { | |||
992 | struct box_construct_props props; | |||
993 | struct box *box = NULL((void*)0); | |||
994 | dom_string *content; | |||
995 | dom_exception err; | |||
996 | ||||
997 | assert(ctx->n != NULL)((ctx->n != ((void*)0)) ? (void) (0) : __assert_fail ("ctx->n != NULL" , "content/handlers/html/box_construct.c", 997, __extension__ __PRETTY_FUNCTION__)); | |||
998 | ||||
999 | box_extract_properties(ctx->n, &props); | |||
1000 | ||||
1001 | assert(props.containing_block != NULL)((props.containing_block != ((void*)0)) ? (void) (0) : __assert_fail ("props.containing_block != NULL", "content/handlers/html/box_construct.c" , 1001, __extension__ __PRETTY_FUNCTION__)); | |||
1002 | ||||
1003 | err = dom_characterdata_get_data(ctx->n, &content)dom_characterdata_get_data( (struct dom_characterdata *) (ctx ->n), (&content)); | |||
1004 | if (err != DOM_NO_ERR || content == NULL((void*)0)) | |||
1005 | return false0; | |||
1006 | ||||
1007 | if (css_computed_white_space(props.parent_style) == | |||
1008 | CSS_WHITE_SPACE_NORMAL || | |||
1009 | css_computed_white_space(props.parent_style) == | |||
1010 | CSS_WHITE_SPACE_NOWRAP) { | |||
1011 | char *text; | |||
1012 | ||||
1013 | text = squash_whitespace(dom_string_data(content)); | |||
1014 | ||||
1015 | dom_string_unref(content); | |||
1016 | ||||
1017 | if (text == NULL((void*)0)) | |||
1018 | return false0; | |||
1019 | ||||
1020 | /* if the text is just a space, combine it with the preceding | |||
1021 | * text node, if any */ | |||
1022 | if (text[0] == ' ' && text[1] == 0) { | |||
1023 | if (props.inline_container != NULL((void*)0)) { | |||
1024 | assert(props.inline_container->last != NULL)((props.inline_container->last != ((void*)0)) ? (void) (0) : __assert_fail ("props.inline_container->last != NULL", "content/handlers/html/box_construct.c" , 1024, __extension__ __PRETTY_FUNCTION__)); | |||
1025 | ||||
1026 | props.inline_container->last->space = | |||
1027 | UNKNOWN_WIDTH2147483647; | |||
1028 | } | |||
1029 | ||||
1030 | free(text); | |||
1031 | ||||
1032 | return true1; | |||
1033 | } | |||
1034 | ||||
1035 | if (props.inline_container == NULL((void*)0)) { | |||
1036 | /* Child of a block without a current container | |||
1037 | * (i.e. this box is the first child of its parent, or | |||
1038 | * was preceded by block-level siblings) */ | |||
1039 | props.inline_container = box_create(NULL((void*)0), NULL((void*)0), false0, | |||
1040 | NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0), ctx->bctx); | |||
1041 | if (props.inline_container == NULL((void*)0)) { | |||
1042 | free(text); | |||
1043 | return false0; | |||
1044 | } | |||
1045 | ||||
1046 | props.inline_container->type = BOX_INLINE_CONTAINER; | |||
1047 | ||||
1048 | box_add_child(props.containing_block, | |||
1049 | props.inline_container); | |||
1050 | } | |||
1051 | ||||
1052 | /** \todo Dropping const here is not clever */ | |||
1053 | box = box_create(NULL((void*)0), | |||
1054 | (css_computed_style *) props.parent_style, | |||
1055 | false0, props.href, props.target, props.title, | |||
1056 | NULL((void*)0), ctx->bctx); | |||
1057 | if (box == NULL((void*)0)) { | |||
1058 | free(text); | |||
1059 | return false0; | |||
1060 | } | |||
1061 | ||||
1062 | box->type = BOX_TEXT; | |||
1063 | ||||
1064 | box->text = talloc_strdup(ctx->bctx, text); | |||
1065 | free(text); | |||
1066 | if (box->text == NULL((void*)0)) | |||
1067 | return false0; | |||
1068 | ||||
1069 | box->length = strlen(box->text); | |||
1070 | ||||
1071 | /* strip ending space char off */ | |||
1072 | if (box->length > 1 && box->text[box->length - 1] == ' ') { | |||
1073 | box->space = UNKNOWN_WIDTH2147483647; | |||
1074 | box->length--; | |||
1075 | } | |||
1076 | ||||
1077 | if (css_computed_text_transform(props.parent_style) != | |||
1078 | CSS_TEXT_TRANSFORM_NONE) | |||
1079 | box_text_transform(box->text, box->length, | |||
1080 | css_computed_text_transform( | |||
1081 | props.parent_style)); | |||
1082 | ||||
1083 | box_add_child(props.inline_container, box); | |||
1084 | ||||
1085 | if (box->text[0] == ' ') { | |||
1086 | box->length--; | |||
1087 | ||||
1088 | memmove(box->text, &box->text[1], box->length); | |||
1089 | ||||
1090 | if (box->prev != NULL((void*)0)) | |||
1091 | box->prev->space = UNKNOWN_WIDTH2147483647; | |||
1092 | } | |||
1093 | } else { | |||
1094 | /* white-space: pre */ | |||
1095 | char *text; | |||
1096 | size_t text_len = dom_string_byte_length(content); | |||
1097 | size_t i; | |||
1098 | char *current; | |||
1099 | enum css_white_space_e white_space = | |||
1100 | css_computed_white_space(props.parent_style); | |||
1101 | ||||
1102 | /* note: pre-wrap/pre-line are unimplemented */ | |||
1103 | assert(white_space == CSS_WHITE_SPACE_PRE ||((white_space == CSS_WHITE_SPACE_PRE || white_space == CSS_WHITE_SPACE_PRE_LINE || white_space == CSS_WHITE_SPACE_PRE_WRAP) ? (void) (0) : __assert_fail ("white_space == CSS_WHITE_SPACE_PRE || white_space == CSS_WHITE_SPACE_PRE_LINE || white_space == CSS_WHITE_SPACE_PRE_WRAP" , "content/handlers/html/box_construct.c", 1105, __extension__ __PRETTY_FUNCTION__)) | |||
1104 | white_space == CSS_WHITE_SPACE_PRE_LINE ||((white_space == CSS_WHITE_SPACE_PRE || white_space == CSS_WHITE_SPACE_PRE_LINE || white_space == CSS_WHITE_SPACE_PRE_WRAP) ? (void) (0) : __assert_fail ("white_space == CSS_WHITE_SPACE_PRE || white_space == CSS_WHITE_SPACE_PRE_LINE || white_space == CSS_WHITE_SPACE_PRE_WRAP" , "content/handlers/html/box_construct.c", 1105, __extension__ __PRETTY_FUNCTION__)) | |||
1105 | white_space == CSS_WHITE_SPACE_PRE_WRAP)((white_space == CSS_WHITE_SPACE_PRE || white_space == CSS_WHITE_SPACE_PRE_LINE || white_space == CSS_WHITE_SPACE_PRE_WRAP) ? (void) (0) : __assert_fail ("white_space == CSS_WHITE_SPACE_PRE || white_space == CSS_WHITE_SPACE_PRE_LINE || white_space == CSS_WHITE_SPACE_PRE_WRAP" , "content/handlers/html/box_construct.c", 1105, __extension__ __PRETTY_FUNCTION__)); | |||
1106 | ||||
1107 | text = malloc(text_len + 1); | |||
1108 | dom_string_unref(content); | |||
1109 | ||||
1110 | if (text == NULL((void*)0)) | |||
1111 | return false0; | |||
1112 | ||||
1113 | memcpy(text, dom_string_data(content), text_len); | |||
1114 | text[text_len] = '\0'; | |||
1115 | ||||
1116 | /* TODO: Handle tabs properly */ | |||
1117 | for (i = 0; i < text_len; i++) | |||
1118 | if (text[i] == '\t') | |||
1119 | text[i] = ' '; | |||
1120 | ||||
1121 | if (css_computed_text_transform(props.parent_style) != | |||
1122 | CSS_TEXT_TRANSFORM_NONE) | |||
1123 | box_text_transform(text, strlen(text), | |||
1124 | css_computed_text_transform( | |||
1125 | props.parent_style)); | |||
1126 | ||||
1127 | current = text; | |||
1128 | ||||
1129 | /* swallow a single leading new line */ | |||
1130 | if (props.containing_block->flags & PRE_STRIP) { | |||
1131 | switch (*current) { | |||
1132 | case '\n': | |||
1133 | current++; | |||
1134 | break; | |||
1135 | case '\r': | |||
1136 | current++; | |||
1137 | if (*current == '\n') | |||
1138 | current++; | |||
1139 | break; | |||
1140 | } | |||
1141 | props.containing_block->flags &= ~PRE_STRIP; | |||
1142 | } | |||
1143 | ||||
1144 | do { | |||
1145 | size_t len = strcspn(current, "\r\n"); | |||
1146 | ||||
1147 | char old = current[len]; | |||
1148 | ||||
1149 | current[len] = 0; | |||
1150 | ||||
1151 | if (props.inline_container == NULL((void*)0)) { | |||
1152 | /* Child of a block without a current container | |||
1153 | * (i.e. this box is the first child of its | |||
1154 | * parent, or was preceded by block-level | |||
1155 | * siblings) */ | |||
1156 | props.inline_container = box_create(NULL((void*)0), NULL((void*)0), | |||
1157 | false0, NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0), | |||
1158 | ctx->bctx); | |||
1159 | if (props.inline_container == NULL((void*)0)) { | |||
1160 | free(text); | |||
1161 | return false0; | |||
1162 | } | |||
1163 | ||||
1164 | props.inline_container->type = | |||
1165 | BOX_INLINE_CONTAINER; | |||
1166 | ||||
1167 | box_add_child(props.containing_block, | |||
1168 | props.inline_container); | |||
1169 | } | |||
1170 | ||||
1171 | /** \todo Dropping const isn't clever */ | |||
1172 | box = box_create(NULL((void*)0), | |||
1173 | (css_computed_style *) props.parent_style, | |||
1174 | false0, props.href, props.target, props.title, | |||
1175 | NULL((void*)0), ctx->bctx); | |||
1176 | if (box == NULL((void*)0)) { | |||
1177 | free(text); | |||
1178 | return false0; | |||
1179 | } | |||
1180 | ||||
1181 | box->type = BOX_TEXT; | |||
1182 | ||||
1183 | box->text = talloc_strdup(ctx->bctx, current); | |||
1184 | if (box->text == NULL((void*)0)) { | |||
1185 | free(text); | |||
1186 | return false0; | |||
1187 | } | |||
1188 | ||||
1189 | box->length = strlen(box->text); | |||
1190 | ||||
1191 | box_add_child(props.inline_container, box); | |||
1192 | ||||
1193 | current[len] = old; | |||
1194 | ||||
1195 | current += len; | |||
1196 | ||||
1197 | if (current[0] != '\0') { | |||
1198 | /* Linebreak: create new inline container */ | |||
1199 | props.inline_container = box_create(NULL((void*)0), NULL((void*)0), | |||
1200 | false0, NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0), | |||
1201 | ctx->bctx); | |||
1202 | if (props.inline_container == NULL((void*)0)) { | |||
1203 | free(text); | |||
1204 | return false0; | |||
1205 | } | |||
1206 | ||||
1207 | props.inline_container->type = | |||
1208 | BOX_INLINE_CONTAINER; | |||
1209 | ||||
1210 | box_add_child(props.containing_block, | |||
1211 | props.inline_container); | |||
1212 | ||||
1213 | if (current[0] == '\r' && current[1] == '\n') | |||
1214 | current += 2; | |||
1215 | else | |||
1216 | current++; | |||
1217 | } | |||
1218 | } while (*current); | |||
1219 | ||||
1220 | free(text); | |||
1221 | } | |||
1222 | ||||
1223 | return true1; | |||
1224 | } | |||
1225 | ||||
1226 | ||||
1227 | /** | |||
1228 | * Convert an ELEMENT node to a box tree fragment, | |||
1229 | * then schedule conversion of the next ELEMENT node | |||
1230 | */ | |||
1231 | static void convert_xml_to_box(struct box_construct_ctx *ctx) | |||
1232 | { | |||
1233 | dom_node *next; | |||
1234 | bool_Bool convert_children; | |||
1235 | uint32_t num_processed = 0; | |||
1236 | const uint32_t max_processed_before_yield = 10; | |||
1237 | ||||
1238 | do { | |||
1239 | convert_children = true1; | |||
1240 | ||||
1241 | assert(ctx->n != NULL)((ctx->n != ((void*)0)) ? (void) (0) : __assert_fail ("ctx->n != NULL" , "content/handlers/html/box_construct.c", 1241, __extension__ __PRETTY_FUNCTION__)); | |||
1242 | ||||
1243 | if (box_construct_element(ctx, &convert_children) == false0) { | |||
1244 | ctx->cb(ctx->content, false0); | |||
1245 | dom_node_unref(ctx->n)dom_node_unref((dom_node *) (ctx->n)); | |||
1246 | free(ctx); | |||
1247 | return; | |||
1248 | } | |||
1249 | ||||
1250 | /* Find next element to process, converting text nodes as we go */ | |||
1251 | next = next_node(ctx->n, ctx->content, convert_children); | |||
1252 | while (next != NULL((void*)0)) { | |||
1253 | dom_node_type type; | |||
1254 | dom_exception err; | |||
1255 | ||||
1256 | err = dom_node_get_node_type(next, &type)dom_node_get_node_type( (dom_node *) (next), (dom_node_type * ) (&type)); | |||
1257 | if (err != DOM_NO_ERR) { | |||
1258 | ctx->cb(ctx->content, false0); | |||
1259 | dom_node_unref(next)dom_node_unref((dom_node *) (next)); | |||
1260 | free(ctx); | |||
1261 | return; | |||
1262 | } | |||
1263 | ||||
1264 | if (type == DOM_ELEMENT_NODE) | |||
1265 | break; | |||
1266 | ||||
1267 | if (type == DOM_TEXT_NODE) { | |||
1268 | ctx->n = next; | |||
1269 | if (box_construct_text(ctx) == false0) { | |||
1270 | ctx->cb(ctx->content, false0); | |||
1271 | dom_node_unref(ctx->n)dom_node_unref((dom_node *) (ctx->n)); | |||
1272 | free(ctx); | |||
1273 | return; | |||
1274 | } | |||
1275 | } | |||
1276 | ||||
1277 | next = next_node(next, ctx->content, true1); | |||
1278 | } | |||
1279 | ||||
1280 | ctx->n = next; | |||
1281 | ||||
1282 | if (next == NULL((void*)0)) { | |||
1283 | /* Conversion complete */ | |||
1284 | struct box root; | |||
1285 | ||||
1286 | memset(&root, 0, sizeof(root)); | |||
1287 | ||||
1288 | root.type = BOX_BLOCK; | |||
1289 | root.children = root.last = ctx->root_box; | |||
1290 | root.children->parent = &root; | |||
1291 | ||||
1292 | /** \todo Remove box_normalise_block */ | |||
1293 | if (box_normalise_block(&root, ctx->root_box, | |||
1294 | ctx->content) == false0) { | |||
1295 | ctx->cb(ctx->content, false0); | |||
1296 | } else { | |||
1297 | ctx->content->layout = root.children; | |||
1298 | ctx->content->layout->parent = NULL((void*)0); | |||
1299 | ||||
1300 | ctx->cb(ctx->content, true1); | |||
1301 | } | |||
1302 | ||||
1303 | assert(ctx->n == NULL)((ctx->n == ((void*)0)) ? (void) (0) : __assert_fail ("ctx->n == NULL" , "content/handlers/html/box_construct.c", 1303, __extension__ __PRETTY_FUNCTION__)); | |||
1304 | ||||
1305 | free(ctx); | |||
1306 | return; | |||
1307 | } | |||
1308 | } while (++num_processed < max_processed_before_yield); | |||
1309 | ||||
1310 | /* More work to do: schedule a continuation */ | |||
1311 | guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); | |||
1312 | } | |||
1313 | ||||
1314 | ||||
1315 | /* exported function documented in html/box_construct.h */ | |||
1316 | nserror | |||
1317 | dom_to_box(dom_node *n, | |||
1318 | html_content *c, | |||
1319 | box_construct_complete_cb cb, | |||
1320 | void **box_conversion_context) | |||
1321 | { | |||
1322 | struct box_construct_ctx *ctx; | |||
1323 | ||||
1324 | assert(box_conversion_context != NULL)((box_conversion_context != ((void*)0)) ? (void) (0) : __assert_fail ("box_conversion_context != NULL", "content/handlers/html/box_construct.c" , 1324, __extension__ __PRETTY_FUNCTION__)); | |||
1325 | ||||
1326 | if (c->bctx == NULL((void*)0)) { | |||
1327 | /* create a context allocation for this box tree */ | |||
1328 | c->bctx = talloc_zero(0, int)(int *)_talloc_zero(0, sizeof(int), "int"); | |||
1329 | if (c->bctx == NULL((void*)0)) { | |||
1330 | return NSERROR_NOMEM; | |||
1331 | } | |||
1332 | } | |||
1333 | ||||
1334 | ctx = malloc(sizeof(*ctx)); | |||
1335 | if (ctx == NULL((void*)0)) { | |||
1336 | return NSERROR_NOMEM; | |||
1337 | } | |||
1338 | ||||
1339 | ctx->content = c; | |||
1340 | ctx->n = dom_node_ref(n)dom_node_ref((dom_node *) (n)); | |||
1341 | ctx->root_box = NULL((void*)0); | |||
1342 | ctx->cb = cb; | |||
1343 | ctx->bctx = c->bctx; | |||
1344 | ||||
1345 | *box_conversion_context = ctx; | |||
1346 | ||||
1347 | return guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); | |||
1348 | } | |||
1349 | ||||
1350 | ||||
1351 | /* exported function documented in html/box_construct.h */ | |||
1352 | nserror cancel_dom_to_box(void *box_conversion_context) | |||
1353 | { | |||
1354 | struct box_construct_ctx *ctx = box_conversion_context; | |||
1355 | nserror err; | |||
1356 | ||||
1357 | err = guit->misc->schedule(-1, (void *)convert_xml_to_box, ctx); | |||
1358 | if (err != NSERROR_OK) { | |||
1359 | return err; | |||
1360 | } | |||
1361 | ||||
1362 | dom_node_unref(ctx->n)dom_node_unref((dom_node *) (ctx->n)); | |||
1363 | free(ctx); | |||
1364 | ||||
1365 | return NSERROR_OK; | |||
1366 | } | |||
1367 | ||||
1368 | ||||
1369 | /* exported function documented in html/box_construct.h */ | |||
1370 | struct box *box_for_node(dom_node *n) | |||
1371 | { | |||
1372 | struct box *box = NULL((void*)0); | |||
1373 | dom_exception err; | |||
1374 | ||||
1375 | err = dom_node_get_user_data(n, corestring_dom___ns_key_box_node_data,dom_node_get_user_data( (dom_node *) (n), (corestring_dom___ns_key_box_node_data ), (void **) ((void *) &box)) | |||
1376 | (void *) &box)dom_node_get_user_data( (dom_node *) (n), (corestring_dom___ns_key_box_node_data ), (void **) ((void *) &box)); | |||
1377 | if (err != DOM_NO_ERR) | |||
1378 | return NULL((void*)0); | |||
1379 | ||||
1380 | return box; | |||
1381 | } | |||
1382 | ||||
1383 | /* exported function documented in html/box_construct.h */ | |||
1384 | bool_Bool | |||
1385 | box_extract_link(const html_content *content, | |||
1386 | const dom_string *dsrel, | |||
1387 | nsurl *base, | |||
1388 | nsurl **result) | |||
1389 | { | |||
1390 | char *s, *s1, *apos0 = 0, *apos1 = 0, *quot0 = 0, *quot1 = 0; | |||
1391 | unsigned int i, j, end; | |||
1392 | nserror error; | |||
1393 | const char *rel; | |||
1394 | ||||
1395 | rel = dom_string_data(dsrel); | |||
1396 | ||||
1397 | s1 = s = malloc(3 * strlen(rel) + 1); | |||
1398 | if (!s) | |||
1399 | return false0; | |||
1400 | ||||
1401 | /* copy to s, removing white space and control characters */ | |||
1402 | for (i = 0; rel[i] && ascii_is_space(rel[i]); i++) | |||
1403 | ; | |||
1404 | for (end = strlen(rel); | |||
1405 | (end != i) && ascii_is_space(rel[end - 1]); | |||
1406 | end--) | |||
1407 | ; | |||
1408 | for (j = 0; i != end; i++) { | |||
1409 | if ((unsigned char) rel[i] < 0x20) { | |||
1410 | ; /* skip control characters */ | |||
1411 | } else if (rel[i] == ' ') { | |||
1412 | s[j++] = '%'; | |||
1413 | s[j++] = '2'; | |||
1414 | s[j++] = '0'; | |||
1415 | } else { | |||
1416 | s[j++] = rel[i]; | |||
1417 | } | |||
1418 | } | |||
1419 | s[j] = 0; | |||
1420 | ||||
1421 | if (content->enable_scripting == false0) { | |||
1422 | /* extract first quoted string out of "javascript:" link */ | |||
1423 | if (strncmp(s, "javascript:", 11) == 0) { | |||
1424 | apos0 = strchr(s, '\''); | |||
1425 | if (apos0) | |||
1426 | apos1 = strchr(apos0 + 1, '\''); | |||
1427 | quot0 = strchr(s, '"'); | |||
1428 | if (quot0) | |||
1429 | quot1 = strchr(quot0 + 1, '"'); | |||
1430 | if (apos0 && apos1 && | |||
1431 | (!quot0 || !quot1 || apos0 < quot0)) { | |||
1432 | *apos1 = 0; | |||
1433 | s1 = apos0 + 1; | |||
1434 | } else if (quot0 && quot1) { | |||
1435 | *quot1 = 0; | |||
1436 | s1 = quot0 + 1; | |||
1437 | } | |||
1438 | } | |||
1439 | } | |||
1440 | ||||
1441 | /* construct absolute URL */ | |||
1442 | error = nsurl_join(base, s1, result); | |||
1443 | free(s); | |||
1444 | if (error != NSERROR_OK) { | |||
1445 | *result = NULL((void*)0); | |||
1446 | return false0; | |||
1447 | } | |||
1448 | ||||
1449 | return true1; | |||
1450 | } |