PageRenderTime 284ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/peek-build/src/netdepends/libcss/src/select/libcssselect.c

https://bitbucket.org/C0deMaver1ck/peeklinux
C | 1392 lines | 947 code | 194 blank | 251 comment | 420 complexity | 904cf8ddf95ee5f502e61e9f2711ce1a MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, LGPL-2.0
  1. /*
  2. * This file is part of LibCSS
  3. * Licensed under the MIT License,
  4. * http://www.opensource.org/licenses/mit-license.php
  5. * Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
  6. */
  7. #include <assert.h>
  8. #include <string.h>
  9. #include <libcss/select.h>
  10. #include "bytecode/bytecode.h"
  11. #include "bytecode/opcodes.h"
  12. #include "stylesheet.h"
  13. #include "select/computed.h"
  14. #include "select/dispatch.h"
  15. #include "select/hash.h"
  16. #include "select/propset.h"
  17. #include "select/select.h"
  18. #include "utils/parserutilserror.h"
  19. #include "utils/utils.h"
  20. /* Define this to enable verbose messages when matching selector chains */
  21. #undef DEBUG_CHAIN_MATCHING
  22. /**
  23. * Container for stylesheet selection info
  24. */
  25. typedef struct css_select_sheet {
  26. const css_stylesheet *sheet; /**< Stylesheet */
  27. css_origin origin; /**< Stylesheet origin */
  28. uint64_t media; /**< Applicable media */
  29. } css_select_sheet;
  30. /**
  31. * CSS selection context
  32. */
  33. struct css_select_ctx {
  34. uint32_t n_sheets; /**< Number of sheets */
  35. css_select_sheet *sheets; /**< Array of sheets */
  36. css_allocator_fn alloc; /**< Allocation routine */
  37. void *pw; /**< Client-specific private data */
  38. };
  39. static css_error set_hint(css_select_state *state, uint32_t i);
  40. static css_error set_initial(css_select_state *state, uint32_t i, void *parent);
  41. static css_error select_from_sheet(css_select_ctx *ctx,
  42. const css_stylesheet *sheet, css_origin origin,
  43. css_select_state *state);
  44. static css_error intern_strings_for_sheet(css_select_ctx *ctx,
  45. const css_stylesheet *sheet, css_select_state *state);
  46. static css_error match_selectors_in_sheet(css_select_ctx *ctx,
  47. const css_stylesheet *sheet, css_select_state *state);
  48. static css_error match_selector_chain(css_select_ctx *ctx,
  49. const css_selector *selector, css_select_state *state);
  50. static css_error match_named_combinator(css_select_ctx *ctx,
  51. css_combinator type, const css_selector *selector,
  52. css_select_state *state, void *node, void **next_node);
  53. static css_error match_universal_combinator(css_select_ctx *ctx,
  54. css_combinator type, const css_selector *selector,
  55. css_select_state *state, void *node, void **next_node);
  56. static css_error match_details(css_select_ctx *ctx, void *node,
  57. const css_selector_detail *detail, css_select_state *state,
  58. bool *match);
  59. static css_error match_detail(css_select_ctx *ctx, void *node,
  60. const css_selector_detail *detail, css_select_state *state,
  61. bool *match);
  62. static css_error cascade_style(const css_style *style, css_select_state *state);
  63. #ifdef DEBUG_CHAIN_MATCHING
  64. static void dump_chain(const css_selector *selector);
  65. #endif
  66. /**
  67. * Create a selection context
  68. *
  69. * \param alloc Memory (de)allocation function
  70. * \param pw Client-specific private data
  71. * \param result Pointer to location to receive created context
  72. * \return CSS_OK on success, appropriate error otherwise.
  73. */
  74. css_error css_select_ctx_create(css_allocator_fn alloc, void *pw,
  75. css_select_ctx **result)
  76. {
  77. css_select_ctx *c;
  78. if (alloc == NULL || result == NULL)
  79. return CSS_BADPARM;
  80. c = alloc(NULL, sizeof(css_select_ctx), pw);
  81. if (c == NULL)
  82. return CSS_NOMEM;
  83. c->n_sheets = 0;
  84. c->sheets = NULL;
  85. c->alloc = alloc;
  86. c->pw = pw;
  87. *result = c;
  88. return CSS_OK;
  89. }
  90. /**
  91. * Destroy a selection context
  92. *
  93. * \param ctx The context to destroy
  94. * \return CSS_OK on success, appropriate error otherwise
  95. */
  96. css_error css_select_ctx_destroy(css_select_ctx *ctx)
  97. {
  98. if (ctx == NULL)
  99. return CSS_BADPARM;
  100. if (ctx->sheets != NULL)
  101. ctx->alloc(ctx->sheets, 0, ctx->pw);
  102. ctx->alloc(ctx, 0, ctx->pw);
  103. return CSS_OK;
  104. }
  105. /**
  106. * Append a stylesheet to a selection context
  107. *
  108. * \param ctx The context to append to
  109. * \param sheet The sheet to append
  110. * \param origin Origin of the sheet
  111. * \param media Media types to which the sheet applies
  112. * \return CSS_OK on success, appropriate error otherwise
  113. */
  114. css_error css_select_ctx_append_sheet(css_select_ctx *ctx,
  115. const css_stylesheet *sheet, css_origin origin,
  116. uint64_t media)
  117. {
  118. if (ctx == NULL || sheet == NULL)
  119. return CSS_BADPARM;
  120. return css_select_ctx_insert_sheet(ctx, sheet, ctx->n_sheets,
  121. origin, media);
  122. }
  123. /**
  124. * Insert a stylesheet into a selection context
  125. *
  126. * \param ctx The context to insert into
  127. * \param sheet Sheet to insert
  128. * \param index Index in context to insert sheet
  129. * \param origin Origin of the sheet
  130. * \param media Media types to which the sheet applies
  131. * \return CSS_OK on success, appropriate error otherwise
  132. */
  133. css_error css_select_ctx_insert_sheet(css_select_ctx *ctx,
  134. const css_stylesheet *sheet, uint32_t index,
  135. css_origin origin, uint64_t media)
  136. {
  137. css_select_sheet *temp;
  138. if (ctx == NULL || sheet == NULL)
  139. return CSS_BADPARM;
  140. /* Inline styles cannot be inserted into a selection context */
  141. if (sheet->inline_style)
  142. return CSS_INVALID;
  143. /* Index must be in the range [0, n_sheets]
  144. * The latter being equivalent to append */
  145. if (index > ctx->n_sheets)
  146. return CSS_INVALID;
  147. temp = ctx->alloc(ctx->sheets,
  148. (ctx->n_sheets + 1) * sizeof(css_select_sheet),
  149. ctx->pw);
  150. if (temp == NULL)
  151. return CSS_NOMEM;
  152. ctx->sheets = temp;
  153. if (index < ctx->n_sheets) {
  154. memmove(&ctx->sheets[index + 1], &ctx->sheets[index],
  155. (ctx->n_sheets - index) * sizeof(css_select_sheet));
  156. }
  157. ctx->sheets[index].sheet = sheet;
  158. ctx->sheets[index].origin = origin;
  159. ctx->sheets[index].media = media;
  160. ctx->n_sheets++;
  161. return CSS_OK;
  162. }
  163. /**
  164. * Remove a sheet from a selection context
  165. *
  166. * \param ctx The context to remove from
  167. * \param sheet Sheet to remove
  168. * \return CSS_OK on success, appropriate error otherwise
  169. */
  170. css_error css_select_ctx_remove_sheet(css_select_ctx *ctx,
  171. const css_stylesheet *sheet)
  172. {
  173. uint32_t index;
  174. if (ctx == NULL || sheet == NULL)
  175. return CSS_BADPARM;
  176. for (index = 0; index < ctx->n_sheets; index++) {
  177. if (ctx->sheets[index].sheet == sheet)
  178. break;
  179. }
  180. if (index == ctx->n_sheets)
  181. return CSS_INVALID;
  182. memmove(&ctx->sheets[index], &ctx->sheets[index + 1],
  183. (ctx->n_sheets - index) * sizeof(css_select_sheet));
  184. ctx->n_sheets--;
  185. return CSS_OK;
  186. }
  187. /**
  188. * Count the number of top-level sheets in a selection context
  189. *
  190. * \param ctx Context to consider
  191. * \param count Pointer to location to receive count of sheets
  192. * \return CSS_OK on success, appropriate error otherwise
  193. */
  194. css_error css_select_ctx_count_sheets(css_select_ctx *ctx, uint32_t *count)
  195. {
  196. if (ctx == NULL || count == NULL)
  197. return CSS_BADPARM;
  198. *count = ctx->n_sheets;
  199. return CSS_OK;
  200. }
  201. /**
  202. * Retrieve a sheet from a selection context
  203. *
  204. * \param ctx Context to look in
  205. * \param index Index in context to look
  206. * \param sheet Pointer to location to receive sheet
  207. * \return CSS_OK on success, appropriate error otherwise
  208. */
  209. css_error css_select_ctx_get_sheet(css_select_ctx *ctx, uint32_t index,
  210. const css_stylesheet **sheet)
  211. {
  212. if (ctx == NULL || sheet == NULL)
  213. return CSS_BADPARM;
  214. if (index > ctx->n_sheets)
  215. return CSS_INVALID;
  216. *sheet = ctx->sheets[index].sheet;
  217. return CSS_OK;
  218. }
  219. /**
  220. * Select a style for the given node
  221. *
  222. * \param ctx Selection context to use
  223. * \param node Node to select style for
  224. * \param pseudo_element Pseudo element to select for, instead
  225. * \param media Currently active media types
  226. * \param inline_style Corresponding inline style for node, or NULL
  227. * \param result Pointer to style to populate (assumed clean)
  228. * \param handler Dispatch table of handler functions
  229. * \param pw Client-specific private data for handler functions
  230. * \return CSS_OK on success, appropriate error otherwise.
  231. *
  232. * In computing the style, no reference is made to the parent node's
  233. * style. Therefore, the resultant computed style is not ready for
  234. * immediate use, as some properties may be marked as inherited.
  235. * Use css_computed_style_compose() to obtain a fully computed style.
  236. *
  237. * This two-step approach to style computation is designed to allow
  238. * the client to store the partially computed style and efficiently
  239. * update the fully computed style for a node when layout changes.
  240. */
  241. css_error css_select_style(css_select_ctx *ctx, void *node,
  242. uint32_t pseudo_element, uint64_t media,
  243. const css_stylesheet *inline_style,
  244. css_computed_style *result,
  245. css_select_handler *handler, void *pw)
  246. {
  247. uint32_t i;
  248. css_error error;
  249. css_select_state state;
  250. void *parent = NULL;
  251. if (ctx == NULL || node == NULL || result == NULL || handler == NULL)
  252. return CSS_BADPARM;
  253. /* Set up the selection state */
  254. memset(&state, 0, sizeof(css_select_state));
  255. state.node = node;
  256. state.pseudo_element = pseudo_element;
  257. state.media = media;
  258. state.result = result;
  259. state.handler = handler;
  260. state.pw = pw;
  261. error = handler->parent_node(pw, node, &parent);
  262. if (error)
  263. return error;
  264. /* Iterate through the top-level stylesheets, selecting styles
  265. * from those which apply to our current media requirements and
  266. * are not disabled */
  267. for (i = 0; i < ctx->n_sheets; i++) {
  268. if ((ctx->sheets[i].media & media) != 0 &&
  269. ctx->sheets[i].sheet->disabled == false) {
  270. error = select_from_sheet(ctx, ctx->sheets[i].sheet,
  271. ctx->sheets[i].origin, &state);
  272. if (error != CSS_OK)
  273. goto cleanup;
  274. }
  275. }
  276. /* Consider any inline style for the node */
  277. if (inline_style != NULL) {
  278. css_rule_selector *sel =
  279. (css_rule_selector *) inline_style->rule_list;
  280. /* Sanity check style */
  281. if (inline_style->rule_count != 1 ||
  282. inline_style->rule_list->type != CSS_RULE_SELECTOR ||
  283. inline_style->rule_list->items != 0) {
  284. error = CSS_INVALID;
  285. goto cleanup;
  286. }
  287. /* No bytecode if input was empty or wholly invalid */
  288. if (sel->style != NULL) {
  289. error = cascade_style(sel->style, &state);
  290. if (error != CSS_OK)
  291. goto cleanup;
  292. }
  293. }
  294. /* Take account of presentational hints and fix up any remaining
  295. * unset properties. */
  296. for (i = 0; i < CSS_N_PROPERTIES; i++) {
  297. /* If the existing property value came from an author
  298. * stylesheet or a user sheet using !important, then leave
  299. * it alone. */
  300. if (state.props[i].set == false ||
  301. (state.props[i].origin != CSS_ORIGIN_AUTHOR &&
  302. state.props[i].important == false)) {
  303. error = set_hint(&state, i);
  304. if (error != CSS_OK)
  305. goto cleanup;
  306. }
  307. /* If the property is still unset or it's set to inherit and
  308. * we're the root element, then set it to its initial value. */
  309. if (state.props[i].set == false || (parent == NULL &&
  310. state.props[i].inherit == true)) {
  311. error = set_initial(&state, i, parent);
  312. if (error != CSS_OK)
  313. goto cleanup;
  314. }
  315. }
  316. /* If this is the root element, then we must ensure that all
  317. * length values are absolute, display and float are correctly
  318. * computed, and the default border-{top,right,bottom,left}-color
  319. * is set to the computed value of color. */
  320. if (parent == NULL) {
  321. error = compute_absolute_values(NULL, result,
  322. handler->compute_font_size, pw);
  323. if (error != CSS_OK)
  324. goto cleanup;
  325. }
  326. error = CSS_OK;
  327. cleanup:
  328. if (ctx->n_sheets > 0 && ctx->sheets[0].sheet != NULL) {
  329. if (state.universal != NULL)
  330. lwc_string_unref(state.universal);
  331. if (state.first_child != NULL)
  332. lwc_string_unref(state.first_child);
  333. if (state.link != NULL)
  334. lwc_string_unref(state.link);
  335. if (state.visited != NULL)
  336. lwc_string_unref(state.visited);
  337. if (state.hover != NULL)
  338. lwc_string_unref(state.hover);
  339. if (state.active != NULL)
  340. lwc_string_unref(state.active);
  341. if (state.focus != NULL)
  342. lwc_string_unref(state.focus);
  343. if (state.first_line != NULL)
  344. lwc_string_unref(state.first_line);
  345. if (state.first_letter != NULL)
  346. lwc_string_unref(state.first_letter);
  347. if (state.before != NULL)
  348. lwc_string_unref(state.before);
  349. if (state.after != NULL)
  350. lwc_string_unref(state.after);
  351. }
  352. return error;
  353. }
  354. /******************************************************************************
  355. * Selection engine internals below here *
  356. ******************************************************************************/
  357. css_error set_hint(css_select_state *state, uint32_t i)
  358. {
  359. css_hint hint;
  360. css_error error;
  361. /* Initialise hint */
  362. memset(&hint, 0, sizeof(css_hint));
  363. /* Retrieve this property's hint from the client */
  364. error = state->handler->node_presentational_hint(state->pw,
  365. state->node, i, &hint);
  366. if (error != CSS_OK)
  367. return (error == CSS_PROPERTY_NOT_SET) ? CSS_OK : error;
  368. /* Hint defined -- set it in the result */
  369. error = prop_dispatch[i].set_from_hint(&hint, state->result);
  370. if (error != CSS_OK)
  371. return error;
  372. /* Keep selection state in sync with reality */
  373. state->props[i].set = 1;
  374. state->props[i].specificity = 0;
  375. state->props[i].origin = CSS_ORIGIN_AUTHOR;
  376. state->props[i].important = 0;
  377. state->props[i].inherit = (hint.status == 0);
  378. return CSS_OK;
  379. }
  380. css_error set_initial(css_select_state *state, uint32_t i, void *parent)
  381. {
  382. css_error error;
  383. /* Do nothing if this property is inherited (the default state
  384. * of a clean computed style is for everything to be set to inherit)
  385. *
  386. * If the node is tree root, everything should be defaulted.
  387. */
  388. if (prop_dispatch[i].inherited == false || parent == NULL) {
  389. /* Remaining properties are neither inherited nor
  390. * already set. Thus, we set them to their initial
  391. * values here. Except, however, if the property in
  392. * question resides in one of the extension blocks and
  393. * the extension block has yet to be allocated. In that
  394. * case, we do nothing and leave it to the property
  395. * accessors to return the initial values for the
  396. * property.
  397. */
  398. if (prop_dispatch[i].group == GROUP_NORMAL) {
  399. error = prop_dispatch[i].initial(state);
  400. if (error != CSS_OK)
  401. return error;
  402. } else if (prop_dispatch[i].group == GROUP_UNCOMMON &&
  403. state->result->uncommon != NULL) {
  404. error = prop_dispatch[i].initial(state);
  405. if (error != CSS_OK)
  406. return error;
  407. } else if (prop_dispatch[i].group == GROUP_PAGE &&
  408. state->result->page != NULL) {
  409. error = prop_dispatch[i].initial(state);
  410. if (error != CSS_OK)
  411. return error;
  412. } else if (prop_dispatch[i].group == GROUP_AURAL &&
  413. state->result->aural != NULL) {
  414. error = prop_dispatch[i].initial(state);
  415. if (error != CSS_OK)
  416. return error;
  417. }
  418. }
  419. return CSS_OK;
  420. }
  421. css_error select_from_sheet(css_select_ctx *ctx, const css_stylesheet *sheet,
  422. css_origin origin, css_select_state *state)
  423. {
  424. const css_stylesheet *s = sheet;
  425. const css_rule *rule = s->rule_list;
  426. uint32_t sp = 0;
  427. #define IMPORT_STACK_SIZE 256
  428. const css_rule *import_stack[IMPORT_STACK_SIZE];
  429. do {
  430. /* Find first non-charset rule, if we're at the list head */
  431. if (rule == s->rule_list) {
  432. for (; rule != NULL; rule = rule->next) {
  433. if (rule->type != CSS_RULE_CHARSET)
  434. break;
  435. }
  436. }
  437. if (rule != NULL && rule->type == CSS_RULE_IMPORT) {
  438. /* Current rule is an import */
  439. const css_rule_import *import =
  440. (const css_rule_import *) rule;
  441. if (import->sheet != NULL &&
  442. (import->media & state->media) != 0) {
  443. /* It's applicable, so process it */
  444. assert(sp < IMPORT_STACK_SIZE - 1);
  445. import_stack[sp++] = rule;
  446. s = import->sheet;
  447. rule = s->rule_list;
  448. } else {
  449. /* Not applicable; skip over it */
  450. rule = rule->next;
  451. }
  452. } else {
  453. /* Gone past import rules in this sheet */
  454. css_error error;
  455. /* Process this sheet */
  456. state->sheet = s;
  457. state->current_origin = origin;
  458. error = intern_strings_for_sheet(ctx, s, state);
  459. if (error != CSS_OK)
  460. return error;
  461. error = match_selectors_in_sheet(ctx, s, state);
  462. if (error != CSS_OK)
  463. return error;
  464. /* Find next sheet to process */
  465. if (sp > 0) {
  466. sp--;
  467. rule = import_stack[sp]->next;
  468. s = import_stack[sp]->parent;
  469. } else {
  470. s = NULL;
  471. }
  472. }
  473. } while (s != NULL);
  474. return CSS_OK;
  475. }
  476. css_error intern_strings_for_sheet(css_select_ctx *ctx,
  477. const css_stylesheet *sheet, css_select_state *state)
  478. {
  479. lwc_error error;
  480. UNUSED(ctx);
  481. UNUSED(sheet);
  482. /* Universal selector */
  483. if (state->universal != NULL)
  484. return CSS_OK;
  485. error = lwc_intern_string("*", SLEN("*"), &state->universal);
  486. if (error != lwc_error_ok)
  487. return css_error_from_lwc_error(error);
  488. /* Pseudo classes */
  489. error = lwc_intern_string(
  490. "first-child", SLEN("first-child"),
  491. &state->first_child);
  492. if (error != lwc_error_ok)
  493. return css_error_from_lwc_error(error);
  494. error = lwc_intern_string(
  495. "link", SLEN("link"),
  496. &state->link);
  497. if (error != lwc_error_ok)
  498. return css_error_from_lwc_error(error);
  499. error = lwc_intern_string(
  500. "visited", SLEN("visited"),
  501. &state->visited);
  502. if (error != lwc_error_ok)
  503. return css_error_from_lwc_error(error);
  504. error = lwc_intern_string(
  505. "hover", SLEN("hover"),
  506. &state->hover);
  507. if (error != lwc_error_ok)
  508. return css_error_from_lwc_error(error);
  509. error = lwc_intern_string(
  510. "active", SLEN("active"),
  511. &state->active);
  512. if (error != lwc_error_ok)
  513. return css_error_from_lwc_error(error);
  514. error = lwc_intern_string(
  515. "focus", SLEN("focus"),
  516. &state->focus);
  517. if (error != lwc_error_ok)
  518. return css_error_from_lwc_error(error);
  519. /* Pseudo elements */
  520. error = lwc_intern_string(
  521. "first-line", SLEN("first-line"),
  522. &state->first_line);
  523. if (error != lwc_error_ok)
  524. return css_error_from_lwc_error(error);
  525. error = lwc_intern_string(
  526. "first_letter", SLEN("first-letter"),
  527. &state->first_letter);
  528. if (error != lwc_error_ok)
  529. return css_error_from_lwc_error(error);
  530. error = lwc_intern_string(
  531. "before", SLEN("before"),
  532. &state->before);
  533. if (error != lwc_error_ok)
  534. return css_error_from_lwc_error(error);
  535. error = lwc_intern_string(
  536. "after", SLEN("after"),
  537. &state->after);
  538. if (error != lwc_error_ok)
  539. return css_error_from_lwc_error(error);
  540. return CSS_OK;
  541. }
  542. static bool _selectors_pending(const css_selector **node,
  543. const css_selector **id, const css_selector ***classes,
  544. uint32_t n_classes, const css_selector **univ)
  545. {
  546. bool pending = false;
  547. uint32_t i;
  548. pending |= *node != NULL;
  549. pending |= *id != NULL;
  550. pending |= *univ != NULL;
  551. if (classes != NULL && n_classes > 0) {
  552. for (i = 0; i < n_classes; i++)
  553. pending |= *(classes[i]) != NULL;
  554. }
  555. return pending;
  556. }
  557. static inline bool _selector_less_specific(const css_selector *ref,
  558. const css_selector *cand)
  559. {
  560. bool result = true;
  561. if (cand == NULL)
  562. return false;
  563. if (ref == NULL)
  564. return true;
  565. /* Sort by specificity */
  566. if (cand->specificity < ref->specificity) {
  567. result = true;
  568. } else if (ref->specificity < cand->specificity) {
  569. result = false;
  570. } else {
  571. /* Then by rule index -- earliest wins */
  572. if (cand->rule->index < ref->rule->index)
  573. result = true;
  574. else
  575. result = false;
  576. }
  577. return result;
  578. }
  579. static const css_selector *_selector_next(const css_selector **node,
  580. const css_selector **id, const css_selector ***classes,
  581. uint32_t n_classes, const css_selector **univ)
  582. {
  583. const css_selector *ret = NULL;
  584. if (_selector_less_specific(ret, *node))
  585. ret = *node;
  586. if (_selector_less_specific(ret, *id))
  587. ret = *id;
  588. if (_selector_less_specific(ret, *univ))
  589. ret = *univ;
  590. if (classes != NULL && n_classes > 0) {
  591. uint32_t i;
  592. for (i = 0; i < n_classes; i++) {
  593. if (_selector_less_specific(ret, *(classes[i])))
  594. ret = *(classes[i]);
  595. }
  596. }
  597. return ret;
  598. }
  599. css_error match_selectors_in_sheet(css_select_ctx *ctx,
  600. const css_stylesheet *sheet, css_select_state *state)
  601. {
  602. static const css_selector *empty_selector = NULL;
  603. lwc_string *element = NULL;
  604. lwc_string *id = NULL;
  605. lwc_string **classes = NULL;
  606. uint32_t n_classes = 0, i = 0;
  607. const css_selector **node_selectors = &empty_selector;
  608. css_selector_hash_iterator node_iterator;
  609. const css_selector **id_selectors = &empty_selector;
  610. css_selector_hash_iterator id_iterator;
  611. const css_selector ***class_selectors = NULL;
  612. css_selector_hash_iterator class_iterator;
  613. const css_selector **univ_selectors = &empty_selector;
  614. css_selector_hash_iterator univ_iterator;
  615. css_error error;
  616. /* Get node's name */
  617. error = state->handler->node_name(state->pw, state->node,
  618. &element);
  619. if (error != CSS_OK)
  620. return error;
  621. /* Get node's ID, if any */
  622. error = state->handler->node_id(state->pw, state->node,
  623. &id);
  624. if (error != CSS_OK)
  625. goto cleanup;
  626. /* Get node's classes, if any */
  627. /** \todo Do we really want to force the client to allocate a new array
  628. * every time we call this? It seems hugely inefficient, given they can
  629. * cache the data. */
  630. error = state->handler->node_classes(state->pw, state->node,
  631. &classes, &n_classes);
  632. if (error != CSS_OK)
  633. goto cleanup;
  634. /* Find hash chain that applies to current node */
  635. error = css_selector_hash_find(sheet->selectors, element,
  636. &node_iterator, &node_selectors);
  637. if (error != CSS_OK)
  638. goto cleanup;
  639. if (classes != NULL && n_classes > 0) {
  640. /* Find hash chains for node classes */
  641. class_selectors = ctx->alloc(NULL,
  642. n_classes * sizeof(css_selector **), ctx->pw);
  643. if (class_selectors == NULL) {
  644. error = CSS_NOMEM;
  645. goto cleanup;
  646. }
  647. for (i = 0; i < n_classes; i++) {
  648. error = css_selector_hash_find_by_class(
  649. sheet->selectors, classes[i],
  650. &class_iterator, &class_selectors[i]);
  651. if (error != CSS_OK)
  652. goto cleanup;
  653. }
  654. }
  655. if (id != NULL) {
  656. /* Find hash chain for node ID */
  657. error = css_selector_hash_find_by_id(sheet->selectors, id,
  658. &id_iterator, &id_selectors);
  659. if (error != CSS_OK)
  660. goto cleanup;
  661. }
  662. /* Find hash chain for universal selector */
  663. error = css_selector_hash_find_universal(sheet->selectors,
  664. &univ_iterator, &univ_selectors);
  665. if (error != CSS_OK)
  666. goto cleanup;
  667. /* Process matching selectors, if any */
  668. while (_selectors_pending(node_selectors, id_selectors,
  669. class_selectors, n_classes, univ_selectors)) {
  670. const css_selector *selector;
  671. css_rule *rule, *parent;
  672. bool process = true;
  673. /* Selectors must be matched in ascending order of specificity
  674. * and rule index. (c.f. outranks_existing())
  675. *
  676. * Pick the least specific/earliest occurring selector.
  677. */
  678. selector = _selector_next(node_selectors, id_selectors,
  679. class_selectors, n_classes, univ_selectors);
  680. /* Ignore any selectors contained in rules which are a child
  681. * of an @media block that doesn't match the current media
  682. * requirements. */
  683. for (rule = selector->rule; rule != NULL; rule = parent) {
  684. if (rule->type == CSS_RULE_MEDIA &&
  685. (((css_rule_media *) rule)->media &
  686. state->media) == 0) {
  687. process = false;
  688. break;
  689. }
  690. if (rule->ptype != CSS_RULE_PARENT_STYLESHEET)
  691. parent = rule->parent;
  692. else
  693. parent = NULL;
  694. }
  695. if (process) {
  696. error = match_selector_chain(ctx, selector, state);
  697. if (error != CSS_OK)
  698. goto cleanup;
  699. }
  700. /* Advance to next selector in whichever chain we extracted
  701. * the processed selector from. */
  702. if (selector == *node_selectors) {
  703. error = node_iterator(sheet->selectors,
  704. node_selectors, &node_selectors);
  705. } else if (selector == *id_selectors) {
  706. error = id_iterator(sheet->selectors,
  707. id_selectors, &id_selectors);
  708. } else if (selector == *univ_selectors) {
  709. error = univ_iterator(sheet->selectors,
  710. univ_selectors, &univ_selectors);
  711. } else {
  712. for (i = 0; i < n_classes; i++) {
  713. if (selector == *(class_selectors[i])) {
  714. error = class_iterator(sheet->selectors,
  715. class_selectors[i],
  716. &class_selectors[i]);
  717. break;
  718. }
  719. }
  720. }
  721. if (error != CSS_OK)
  722. goto cleanup;
  723. }
  724. error = CSS_OK;
  725. cleanup:
  726. if (class_selectors != NULL)
  727. ctx->alloc(class_selectors, 0, ctx->pw);
  728. if (classes != NULL) {
  729. for (i = 0; i < n_classes; i++)
  730. lwc_string_unref(classes[i]);
  731. ctx->alloc(classes, 0, ctx->pw);
  732. }
  733. if (id != NULL)
  734. lwc_string_unref(id);
  735. lwc_string_unref(element);
  736. return error;
  737. }
  738. css_error match_selector_chain(css_select_ctx *ctx,
  739. const css_selector *selector, css_select_state *state)
  740. {
  741. const css_selector *s = selector;
  742. void *node = state->node;
  743. css_error error;
  744. #ifdef DEBUG_CHAIN_MATCHING
  745. fprintf(stderr, "matching: ");
  746. dump_chain(selector);
  747. fprintf(stderr, "\n");
  748. #endif
  749. do {
  750. void *next_node = NULL;
  751. const css_selector_detail *detail = &s->data;
  752. bool match = false;
  753. /* If this is the first selector in the chain, we must match
  754. * its details. The details of subsequent selectors will be
  755. * matched when processing the combinator. */
  756. if (s == selector) {
  757. /* Match details on this selector */
  758. error = match_details(ctx, node, detail, state, &match);
  759. if (error != CSS_OK)
  760. return error;
  761. /* Details don't match, so reject selector chain */
  762. if (match == false)
  763. return CSS_OK;
  764. }
  765. /* Consider any combinator on this selector */
  766. if (s->data.comb != CSS_COMBINATOR_NONE &&
  767. s->combinator->data.name != state->universal) {
  768. /* Named combinator */
  769. error = match_named_combinator(ctx, s->data.comb,
  770. s->combinator, state, node, &next_node);
  771. if (error != CSS_OK)
  772. return error;
  773. /* No match for combinator, so reject selector chain */
  774. if (next_node == NULL)
  775. return CSS_OK;
  776. } else if (s->data.comb != CSS_COMBINATOR_NONE &&
  777. s->combinator->data.name == state->universal) {
  778. /* Universal combinator */
  779. error = match_universal_combinator(ctx, s->data.comb,
  780. s->combinator, state, node, &next_node);
  781. if (error != CSS_OK)
  782. return error;
  783. /* No match for combinator, so reject selector chain */
  784. if (next_node == NULL)
  785. return CSS_OK;
  786. }
  787. /* Details matched, so progress to combining selector */
  788. s = s->combinator;
  789. node = next_node;
  790. } while (s != NULL);
  791. /* If we got here, then the entire selector chain matched, so cascade */
  792. state->current_specificity = selector->specificity;
  793. /* No bytecode if rule body is empty or wholly invalid */
  794. if (((css_rule_selector *) selector->rule)->style == NULL)
  795. return CSS_OK;
  796. return cascade_style(((css_rule_selector *) selector->rule)->style,
  797. state);
  798. }
  799. css_error match_named_combinator(css_select_ctx *ctx, css_combinator type,
  800. const css_selector *selector, css_select_state *state,
  801. void *node, void **next_node)
  802. {
  803. const css_selector_detail *detail = &selector->data;
  804. void *n = node;
  805. css_error error;
  806. do {
  807. bool match = false;
  808. /* Find candidate node */
  809. switch (type) {
  810. case CSS_COMBINATOR_ANCESTOR:
  811. error = state->handler->named_ancestor_node(state->pw,
  812. n, selector->data.name, &n);
  813. if (error != CSS_OK)
  814. return error;
  815. break;
  816. case CSS_COMBINATOR_PARENT:
  817. error = state->handler->named_parent_node(state->pw,
  818. n, selector->data.name, &n);
  819. if (error != CSS_OK)
  820. return error;
  821. break;
  822. case CSS_COMBINATOR_SIBLING:
  823. error = state->handler->named_sibling_node(state->pw,
  824. n, selector->data.name, &n);
  825. if (error != CSS_OK)
  826. return error;
  827. break;
  828. case CSS_COMBINATOR_NONE:
  829. break;
  830. }
  831. if (n != NULL) {
  832. /* Match its details */
  833. error = match_details(ctx, n, detail, state, &match);
  834. if (error != CSS_OK)
  835. return error;
  836. /* If we found a match, use it */
  837. if (match == true)
  838. break;
  839. /* For parent and sibling selectors, only adjacent
  840. * nodes are valid. Thus, if we failed to match,
  841. * give up. */
  842. if (type == CSS_COMBINATOR_PARENT ||
  843. type == CSS_COMBINATOR_SIBLING)
  844. n = NULL;
  845. }
  846. } while (n != NULL);
  847. *next_node = n;
  848. return CSS_OK;
  849. }
  850. css_error match_universal_combinator(css_select_ctx *ctx, css_combinator type,
  851. const css_selector *selector, css_select_state *state,
  852. void *node, void **next_node)
  853. {
  854. const css_selector_detail *detail = &selector->data;
  855. void *n = node;
  856. css_error error;
  857. do {
  858. bool match = false;
  859. /* Find candidate node */
  860. switch (type) {
  861. case CSS_COMBINATOR_ANCESTOR:
  862. case CSS_COMBINATOR_PARENT:
  863. error = state->handler->parent_node(state->pw, n, &n);
  864. if (error != CSS_OK)
  865. return error;
  866. break;
  867. case CSS_COMBINATOR_SIBLING:
  868. error = state->handler->sibling_node(state->pw, n, &n);
  869. if (error != CSS_OK)
  870. return error;
  871. break;
  872. case CSS_COMBINATOR_NONE:
  873. break;
  874. }
  875. if (n != NULL) {
  876. /* Match its details */
  877. error = match_details(ctx, n, detail, state, &match);
  878. if (error != CSS_OK)
  879. return error;
  880. /* If we found a match, use it */
  881. if (match == true)
  882. break;
  883. /* For parent and sibling selectors, only adjacent
  884. * nodes are valid. Thus, if we failed to match,
  885. * give up. */
  886. if (type == CSS_COMBINATOR_PARENT ||
  887. type == CSS_COMBINATOR_SIBLING)
  888. n = NULL;
  889. }
  890. } while (n != NULL);
  891. *next_node = n;
  892. return CSS_OK;
  893. }
  894. css_error match_details(css_select_ctx *ctx, void *node,
  895. const css_selector_detail *detail, css_select_state *state,
  896. bool *match)
  897. {
  898. css_error error;
  899. /* We match by default (if there are no details than the element
  900. * selector, then we must match) */
  901. *match = true;
  902. /** \todo Some details are easier to test than others (e.g. dashmatch
  903. * actually requires looking at data rather than simply comparing
  904. * pointers). Should we consider sorting the detail list such that the
  905. * simpler details come first (and thus the expensive match routines
  906. * can be avoided unless absolutely necessary)? */
  907. do {
  908. error = match_detail(ctx, node, detail, state, match);
  909. if (error != CSS_OK)
  910. return error;
  911. /* Detail doesn't match, so reject selector chain */
  912. if (*match == false)
  913. return CSS_OK;
  914. if (detail->next)
  915. detail++;
  916. else
  917. detail = NULL;
  918. } while (detail != NULL);
  919. return CSS_OK;
  920. }
  921. css_error match_detail(css_select_ctx *ctx, void *node,
  922. const css_selector_detail *detail, css_select_state *state,
  923. bool *match)
  924. {
  925. css_error error = CSS_OK;
  926. UNUSED(ctx);
  927. switch (detail->type) {
  928. case CSS_SELECTOR_ELEMENT:
  929. if (lwc_string_length(detail->name) == 1 &&
  930. lwc_string_data(detail->name)[0] == '*') {
  931. *match = true;
  932. } else {
  933. error = state->handler->node_has_name(state->pw, node,
  934. detail->name, match);
  935. }
  936. break;
  937. case CSS_SELECTOR_CLASS:
  938. error = state->handler->node_has_class(state->pw, node,
  939. detail->name, match);
  940. break;
  941. case CSS_SELECTOR_ID:
  942. error = state->handler->node_has_id(state->pw, node,
  943. detail->name, match);
  944. break;
  945. case CSS_SELECTOR_PSEUDO_CLASS:
  946. if (detail->name == state->first_child) {
  947. error = state->handler->node_is_first_child(state->pw,
  948. node, match);
  949. } else if (detail->name == state->link) {
  950. error = state->handler->node_is_link(state->pw,
  951. node, match);
  952. } else if (detail->name == state->visited) {
  953. error = state->handler->node_is_visited(state->pw,
  954. node, match);
  955. } else if (detail->name == state->hover) {
  956. error = state->handler->node_is_hover(state->pw,
  957. node, match);
  958. } else if (detail->name == state->active) {
  959. error = state->handler->node_is_active(state->pw,
  960. node, match);
  961. } else if (detail->name == state->focus) {
  962. error = state->handler->node_is_focus(state->pw,
  963. node, match);
  964. } else
  965. *match = false;
  966. break;
  967. case CSS_SELECTOR_PSEUDO_ELEMENT:
  968. if (detail->name == state->first_line &&
  969. state->pseudo_element ==
  970. CSS_PSEUDO_ELEMENT_FIRST_LINE)
  971. *match = true;
  972. else if (detail->name == state->first_letter &&
  973. state->pseudo_element ==
  974. CSS_PSEUDO_ELEMENT_FIRST_LETTER)
  975. *match = true;
  976. else if (detail->name == state->before &&
  977. state->pseudo_element ==
  978. CSS_PSEUDO_ELEMENT_BEFORE)
  979. *match = true;
  980. else if (detail->name == state->after &&
  981. state->pseudo_element ==
  982. CSS_PSEUDO_ELEMENT_AFTER)
  983. *match = true;
  984. else
  985. *match = false;
  986. break;
  987. case CSS_SELECTOR_ATTRIBUTE:
  988. error = state->handler->node_has_attribute(state->pw, node,
  989. detail->name, match);
  990. break;
  991. case CSS_SELECTOR_ATTRIBUTE_EQUAL:
  992. error = state->handler->node_has_attribute_equal(state->pw,
  993. node, detail->name, detail->value, match);
  994. break;
  995. case CSS_SELECTOR_ATTRIBUTE_DASHMATCH:
  996. error = state->handler->node_has_attribute_dashmatch(state->pw,
  997. node, detail->name, detail->value, match);
  998. break;
  999. case CSS_SELECTOR_ATTRIBUTE_INCLUDES:
  1000. error = state->handler->node_has_attribute_includes(state->pw,
  1001. node, detail->name, detail->value, match);
  1002. break;
  1003. }
  1004. return error;
  1005. }
  1006. css_error cascade_style(const css_style *style, css_select_state *state)
  1007. {
  1008. css_style s = *style;
  1009. while (s.length > 0) {
  1010. opcode_t op;
  1011. css_error error;
  1012. uint32_t opv = *((uint32_t *) s.bytecode);
  1013. advance_bytecode(&s, sizeof(opv));
  1014. op = getOpcode(opv);
  1015. error = prop_dispatch[op].cascade(opv, &s, state);
  1016. if (error != CSS_OK)
  1017. return error;
  1018. }
  1019. return CSS_OK;
  1020. }
  1021. bool outranks_existing(uint16_t op, bool important, css_select_state *state,
  1022. bool inherit)
  1023. {
  1024. prop_state *existing = &state->props[op];
  1025. bool outranks = false;
  1026. /* Sorting on origin & importance gives the following:
  1027. *
  1028. * | UA, - | UA, i | USER, - | USER, i | AUTHOR, - | AUTHOR, i
  1029. * |----------------------------------------------------------
  1030. * UA , - | S S Y Y Y Y
  1031. * UA , i | S S Y Y Y Y
  1032. * USER , - | - - S Y Y Y
  1033. * USER , i | - - - S - -
  1034. * AUTHOR, - | - - - Y S Y
  1035. * AUTHOR, i | - - - Y - S
  1036. *
  1037. * Where the columns represent the origin/importance of the property
  1038. * being considered and the rows represent the origin/importance of
  1039. * the existing property.
  1040. *
  1041. * - means that the existing property must be preserved
  1042. * Y means that the new property must be applied
  1043. * S means that the specificities of the rules must be considered.
  1044. *
  1045. * If specificities are considered, the highest specificity wins.
  1046. * If specificities are equal, then the rule defined last wins.
  1047. *
  1048. * We have no need to explicitly consider the ordering of rules if
  1049. * the specificities are the same because:
  1050. *
  1051. * a) We process stylesheets in order
  1052. * b) The selector hash chains within a sheet are ordered such that
  1053. * more specific rules come after less specific ones and, when
  1054. * specificities are identical, rules defined later occur after
  1055. * those defined earlier.
  1056. *
  1057. * Therefore, where we consider specificity, below, the property
  1058. * currently being considered will always be applied if its specificity
  1059. * is greater than or equal to that of the existing property.
  1060. */
  1061. if (existing->set == 0) {
  1062. /* Property hasn't been set before, new one wins */
  1063. outranks = true;
  1064. } else {
  1065. assert(CSS_ORIGIN_UA < CSS_ORIGIN_USER);
  1066. assert(CSS_ORIGIN_USER < CSS_ORIGIN_AUTHOR);
  1067. if (existing->origin < state->current_origin) {
  1068. /* New origin has more weight than existing one.
  1069. * Thus, new property wins, except when the existing
  1070. * one is USER, i. */
  1071. if (existing->important == 0 ||
  1072. existing->origin != CSS_ORIGIN_USER) {
  1073. outranks = true;
  1074. }
  1075. } else if (existing->origin == state->current_origin) {
  1076. /* Origins are identical, consider importance, except
  1077. * for UA stylesheets, when specificity is always
  1078. * considered (as importance is meaningless) */
  1079. if (existing->origin == CSS_ORIGIN_UA) {
  1080. if (state->current_specificity >=
  1081. existing->specificity) {
  1082. outranks = true;
  1083. }
  1084. } else if (existing->important == 0 && important) {
  1085. /* New is more important than old. */
  1086. outranks = true;
  1087. } else if (existing->important && important == false) {
  1088. /* Old is more important than new */
  1089. } else {
  1090. /* Same importance, consider specificity */
  1091. if (state->current_specificity >=
  1092. existing->specificity) {
  1093. outranks = true;
  1094. }
  1095. }
  1096. } else {
  1097. /* Existing origin has more weight than new one.
  1098. * Thus, existing property wins, except when the new
  1099. * one is USER, i. */
  1100. if (state->current_origin == CSS_ORIGIN_USER &&
  1101. important) {
  1102. outranks = true;
  1103. }
  1104. }
  1105. }
  1106. if (outranks) {
  1107. /* The new property is about to replace the old one.
  1108. * Update our state to reflect this. */
  1109. existing->set = 1;
  1110. existing->specificity = state->current_specificity;
  1111. existing->origin = state->current_origin;
  1112. existing->important = important;
  1113. existing->inherit = inherit;
  1114. }
  1115. return outranks;
  1116. }
  1117. /******************************************************************************
  1118. * Debug helpers *
  1119. ******************************************************************************/
  1120. #ifdef DEBUG_CHAIN_MATCHING
  1121. void dump_chain(const css_selector *selector)
  1122. {
  1123. const css_selector_detail *detail = &selector->data;
  1124. if (selector->data.comb != CSS_COMBINATOR_NONE)
  1125. dump_chain(selector->combinator);
  1126. if (selector->data.comb == CSS_COMBINATOR_ANCESTOR)
  1127. fprintf(stderr, " ");
  1128. else if (selector->data.comb == CSS_COMBINATOR_SIBLING)
  1129. fprintf(stderr, " + ");
  1130. else if (selector->data.comb == CSS_COMBINATOR_PARENT)
  1131. fprintf(stderr, " > ");
  1132. do {
  1133. switch (detail->type) {
  1134. case CSS_SELECTOR_ELEMENT:
  1135. if (lwc_string_length(detail->name) == 1 &&
  1136. lwc_string_data(detail->name)[0] == '*' &&
  1137. detail->next == 1) {
  1138. break;
  1139. }
  1140. fprintf(stderr, "%.*s",
  1141. (int) lwc_string_length(detail->name),
  1142. lwc_string_data(detail->name));
  1143. break;
  1144. case CSS_SELECTOR_CLASS:
  1145. fprintf(stderr, ".%.*s",
  1146. (int) lwc_string_length(detail->name),
  1147. lwc_string_data(detail->name));
  1148. break;
  1149. case CSS_SELECTOR_ID:
  1150. fprintf(stderr, "#%.*s",
  1151. (int) lwc_string_length(detail->name),
  1152. lwc_string_data(detail->name));
  1153. break;
  1154. case CSS_SELECTOR_PSEUDO_CLASS:
  1155. case CSS_SELECTOR_PSEUDO_ELEMENT:
  1156. fprintf(stderr, ":%.*s",
  1157. (int) lwc_string_length(detail->name),
  1158. lwc_string_data(detail->name));
  1159. if (detail->value != NULL) {
  1160. fprintf(stderr, "(%.*s)",
  1161. (int) lwc_string_length(detail->value),
  1162. lwc_string_data(detail->value));
  1163. }
  1164. break;
  1165. case CSS_SELECTOR_ATTRIBUTE:
  1166. fprintf(stderr, "[%.*s]",
  1167. (int) lwc_string_length(detail->name),
  1168. lwc_string_data(detail->name));
  1169. break;
  1170. case CSS_SELECTOR_ATTRIBUTE_EQUAL:
  1171. fprintf(stderr, "[%.*s=\"%.*s\"]",
  1172. (int) lwc_string_length(detail->name),
  1173. lwc_string_data(detail->name),
  1174. (int) lwc_string_length(detail->value),
  1175. lwc_string_data(detail->value));
  1176. break;
  1177. case CSS_SELECTOR_ATTRIBUTE_DASHMATCH:
  1178. fprintf(stderr, "[%.*s|=\"%.*s\"]",
  1179. (int) lwc_string_length(detail->name),
  1180. lwc_string_data(detail->name),
  1181. (int) lwc_string_length(detail->value),
  1182. lwc_string_data(detail->value));
  1183. break;
  1184. case CSS_SELECTOR_ATTRIBUTE_INCLUDES:
  1185. fprintf(stderr, "[%.*s~=\"%.*s\"]",
  1186. (int) lwc_string_length(detail->name),
  1187. lwc_string_data(detail->name),
  1188. (int) lwc_string_length(detail->value),
  1189. lwc_string_data(detail->value));
  1190. break;
  1191. }
  1192. if (detail->next)
  1193. detail++;
  1194. else
  1195. detail = NULL;
  1196. } while (detail);
  1197. }
  1198. #endif