You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
netsurf/content/handlers/html/box/manager.c

988 lines
26 KiB

#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <libcss/libcss.h>
#include "css/select.h"
#include "desktop/gui_internal.h"
#include "html/box/manager.h"
#include "html/css.h"
#include "html/private.h"
#include "netsurf/misc.h"
#include "utils/corestrings.h"
#include "utils/log.h"
#include "utils/nsoption.h"
#include "utils/nsurl.h"
/* Flags for DOM Element nodes */
#define BM_DOM_FLAG_NODE_INSERTED 0x00000001
/* This node has been inserted into the DOM tree */
#define BM_DOM_FLAG_PREVIOUS_NODE_REMOVED 0x00000002
/* The node preceding this one was removed from the DOM tree */
#define BM_DOM_FLAG_ID_ATTRIBUTE_CHANGED 0x00000004
/* This node's @id has changed */
#define BM_DOM_FLAG_CLASS_ATTRIBUTE_CHANGED 0x00000008
/* This node's @class has changed */
#define BM_DOM_FLAG_STYLE_ATTRIBUTE_CHANGED 0x00000010
/* This node's @style has changed */
#define BM_DOM_FLAG_ATTRIBUTE_CHANGED 0x00000020
/* A (non-@class/@id/@style) attribute on this node has changed */
#define BM_DOM_FLAG_STYLESHEETS_CHANGED 0x00000800
/* Document stylesheets have changed (when set on root element) */
#define BM_DOM_STRUCTURAL_CHANGE_MASK 0x00000fff
/* If any of the above flags are set on a node: recompute style information
* for it, its descendants, its subsequent siblings, and their descendants.
* If styles have changed, rebuild box subtree */
#define BM_DOM_FLAG_TEXT_CONTENTS_CHANGED 0x00001000
/* Text contents of this node have changed: rebuild box subtree */
struct box_manager
{
struct html_content *c;
dom_document *doc;
dom_event_listener *listener;
css_select_ctx *select_ctx;
/* Whether there is an outstanding callback pending */
bool callback_pending;
dom_node **pending_queue;
size_t pending_queue_idx;
size_t pending_queue_slots;
};
typedef struct
{
box_manager *mgr;
dom_node *document_root;
dom_node *parent;
/* Flag state for our ancestors (and their preceding siblings) */
uint32_t flags_ancestor;
/* Flag state for our preceding siblings */
uint32_t flags_sibling;
} rebuild_ctx;
typedef struct
{
/* Flag state for this node */
uint32_t flags;
/* Computed styles for this node */
css_select_results *styles;
} bm_user_data;
static bm_user_data *
_create_user_data(void)
{
bm_user_data *ud = NULL;
ud = malloc(sizeof(*ud));
if (ud != NULL) {
ud->flags = 0;
ud->styles = NULL;
}
return ud;
}
static void
_destroy_user_data(bm_user_data *ud)
{
if (ud->styles != NULL) {
css_select_results_destroy(ud->styles);
}
free(ud);
}
static void
_user_data_handler(dom_node_operation operation, dom_string *key, void *data,
dom_node *src, dom_node *dst)
{
if (!dom_string_isequal(corestring_dom___ns_key_bm_node_data, key) ||
data == NULL) {
return;
}
switch (operation) {
case DOM_NODE_DELETED:
_destroy_user_data(data);
break;
case DOM_NODE_RENAMED:
case DOM_NODE_IMPORTED:
case DOM_NODE_ADOPTED:
case DOM_NODE_CLONED:
/* Nothing to do: user data will be created when node is
* inserted into tree */
break;
default:
NSLOG(layout, INFO, "User data operation not handled.");
assert(0);
}
}
static dom_exception
_ensure_user_data(dom_node *node)
{
bm_user_data *ud = NULL;
dom_exception exc;
exc = dom_node_get_user_data(node,
corestring_dom___ns_key_bm_node_data,
(void **) &ud);
if (exc == DOM_NO_ERR && ud == NULL) {
ud = _create_user_data();
if (ud == NULL) {
return DOM_NO_MEM_ERR;
}
exc = dom_node_set_user_data(node,
corestring_dom___ns_key_bm_node_data,
ud, _user_data_handler,
(void **) &ud);
if (exc != DOM_NO_ERR) {
_destroy_user_data(ud);
}
}
return exc;
}
static dom_exception
_remove_user_data(dom_node *node)
{
bm_user_data *ud = NULL;
dom_exception exc;
exc = dom_node_set_user_data(node,
corestring_dom___ns_key_bm_node_data,
NULL, NULL, (void **) &ud);
if (exc == DOM_NO_ERR && ud != NULL) {
_destroy_user_data(ud);
}
return exc;
}
static dom_exception
_foreach_child(dom_node *parent,
dom_exception (*cb)(dom_node *, void *), void *pw)
{
dom_node *cur = NULL, *sibling = NULL;
dom_exception exc;
exc = dom_node_get_first_child(parent, &cur);
if (exc != DOM_NO_ERR || cur == NULL) {
return exc;
}
do {
dom_node_type node_type;
exc = dom_node_get_node_type(cur, &node_type);
if (exc == DOM_NO_ERR && node_type == DOM_ELEMENT_NODE) {
exc = cb(cur, pw);
}
if (exc == DOM_NO_ERR) {
exc = dom_node_get_next_sibling(cur, &sibling);
dom_node_unref(cur);
cur = NULL;
if (exc == DOM_NO_ERR && sibling != NULL) {
cur = sibling;
sibling = NULL;
}
}
} while (exc == DOM_NO_ERR && cur != NULL);
dom_node_unref(cur);
return exc;
}
static css_select_results *
_style_for_node(box_manager *mgr,
const css_computed_style *parent_style,
const css_computed_style *root_style,
dom_node *n)
{
dom_string *s = NULL;
css_stylesheet *inline_style = NULL;
css_select_results *styles;
nscss_select_ctx ctx;
/* Firstly, construct inline stylesheet, if any */
if (nsoption_bool(author_level_css)) {
dom_exception err;
err = dom_element_get_attribute(n, corestring_dom_style, &s);
if (err != DOM_NO_ERR) {
NSLOG(layout, DBG,
"bm: Failed getting @style for node %p: %d",
n, err);
return NULL;
}
}
// XXX: we fish around inside the html_content below here.
// XXX: it would be better if we did not need to (e.g. by constructing
// XXX: the box manager with the correct settings in the first place)
if (s != NULL) {
inline_style = nscss_create_inline_style(
(const uint8_t *) dom_string_data(s),
dom_string_byte_length(s),
mgr->c->encoding,
nsurl_access(mgr->c->base_url),
mgr->c->quirks != DOM_DOCUMENT_QUIRKS_MODE_NONE);
dom_string_unref(s);
if (inline_style == NULL) {
NSLOG(layout, DBG,
"bgs: Failed creating inline style for node %p (%.*s)",
n, (int) dom_string_byte_length(s), dom_string_data(s));
return NULL;
}
}
/* Populate selection context */
ctx.ctx = mgr->select_ctx;
ctx.quirks = (mgr->c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL);
ctx.base_url = mgr->c->base_url;
ctx.universal = mgr->c->universal;
ctx.root_style = root_style;
ctx.parent_style = parent_style;
/* Select style for element */
styles = nscss_get_style(&ctx, n, &mgr->c->media,
&mgr->c->unit_len_ctx, inline_style);
/* No longer need inline style */
if (inline_style != NULL)
css_stylesheet_destroy(inline_style);
return styles;
}
static dom_exception
_rebuild_boxes_for_element(dom_node *node, void *pw)
{
rebuild_ctx *ctx = pw;
bm_user_data *ud = NULL;
dom_node * const parent = ctx->parent;
const uint32_t flags_ancestor = ctx->flags_ancestor;
const uint32_t flags_sibling = ctx->flags_sibling;
uint32_t flags_self = 0;
bool styles_changed = false;
dom_exception exc;
/* Invalidate node flags, extracting current value */
exc = dom_node_get_user_data(node,
corestring_dom___ns_key_bm_node_data,
(void **) &ud);
if (exc != DOM_NO_ERR) {
NSLOG(layout, DBG, "bm: Failed getting user data for node %p: %d",
node, exc);
return exc;
}
if (ud == NULL) {
/* No user data means this node is not in the tree, so
* no further processing is required. */
NSLOG(layout, DBG, "bm: No user data for node %p", node);
return DOM_NO_ERR;
}
flags_self = ud->flags;
ud->flags = 0;
/* Consider whether anything has changed that might affect this node's
* styling (either DOM structure or no style information) */
if (((flags_ancestor | flags_sibling | flags_self) &
BM_DOM_STRUCTURAL_CHANGE_MASK) != 0
|| ud->styles == NULL) {
/* DOM structure has changed: (re)select styles for node */
const css_computed_style *parent_style = NULL;
const css_computed_style *root_style = NULL;
css_select_results *styles;
if (node != ctx->document_root) {
bm_user_data *oud = NULL;
exc = dom_node_get_user_data(ctx->parent,
corestring_dom___ns_key_bm_node_data,
(void **) &oud);
if (exc != DOM_NO_ERR) {
NSLOG(layout, DBG,
"bm: Failed getting parent %p user data"
"for node %p: %d",
ctx->parent, node, exc);
return exc;
}
parent_style = oud->styles->styles[CSS_PSEUDO_ELEMENT_NONE];
exc = dom_node_get_user_data(ctx->document_root,
corestring_dom___ns_key_bm_node_data,
(void **) &oud);
if (exc != DOM_NO_ERR) {
NSLOG(layout, DBG,
"bm: Failed getting root %p user data"
"for node %p: %d",
ctx->document_root, node, exc);
return exc;
}
root_style = oud->styles->styles[CSS_PSEUDO_ELEMENT_NONE];
}
styles = _style_for_node(ctx->mgr, parent_style, root_style, node);
if (styles == NULL) {
NSLOG(layout, DBG, "bm: Failed getting styles for node %p",
node);
return DOM_NO_MEM_ERR;
}
// XXX: determine if styles have changed: how?
// XXX: computed styles are interned, so we could do:
// XXX: memcmp(ud->styles, styles, sizeof(*styles)) != 0
// XXX: but, better, would be for libcss to expose an
// XXX: API to do this, so that the implementation detail
// XXX: of style internment is contained there.
if (ud->styles != NULL) {
styles_changed = memcmp(ud->styles, styles,
sizeof(*styles)) == 0;
css_select_results_destroy(ud->styles);
} else {
styles_changed = true;
}
ud->styles = styles;
// XXX: if styles have changed, ensure our box tree segment reflects reality
// XXX: here, or after processing children?
}
/* Prepare to process children */
ctx->flags_ancestor |= (ctx->flags_sibling | flags_self);
ctx->flags_sibling = 0;
ctx->parent = node;
/* Process children */
exc = _foreach_child(node, _rebuild_boxes_for_element, ctx);
/* Restore values */
ctx->flags_ancestor = flags_ancestor;
ctx->flags_sibling = flags_sibling;
ctx->parent = parent;
{
dom_string *name = NULL;
exc = dom_node_get_node_name(node, &name);
if (exc == DOM_NO_ERR && name != NULL) {
NSLOG(layout, DBG, "bm: <%.*s>:"
" ancestor: %08x"
" sibling: %08x"
" self: %08x"
" styles-changed: %d",
(int) dom_string_byte_length(name),
dom_string_data(name),
flags_ancestor,
flags_sibling,
flags_self,
styles_changed);
dom_string_unref(name);
}
}
if ((flags_self & BM_DOM_FLAG_TEXT_CONTENTS_CHANGED) != 0) {
/* Text contents changed: consider inlines */
}
// XXX: regardless, ensure that box tree is in normal form (only need to consider relationship between our box(es) and those of our child elements)
// XXX: then, if the box tree changed (either because our styles changed, or we needed to normalize things):
// a. find the immediate containing block(s) for our segment
// (there may be more than one if posititioned/out-of-flow elements are in play)
// b. emit (a) "box tree modified" event(s) referencing the containing block(s)
//
// Note that our children will also have done this, so there will be
// multiple events if done here and emitted directly; if we want to
// coalesce things, then we'll probably want to fire the events to an
// internal queue that we then flush out after we've finished
// processing the top-level external (e.g. DOM) event. Also, as we're
// (re)building the box tree on the way back up the DOM, it will be
// the case that we do not actually have any containing blocks yet
// (because they'll be constructed when we unwind back up to our
// parent). Given this, some further thought needs to happen here.
//
// Layout calculators will receive the "box tree modified" events and deal with them
/* Update flags for next sibling (if any) */
ctx->flags_sibling |= flags_self;
return exc;
}
static void
box_manager_rebuild_boxes_for_children(box_manager *mgr, dom_node *parent)
{
rebuild_ctx ctx;
dom_exception exc;
/* Prepare context */
ctx.mgr = mgr;
ctx.flags_ancestor = 0;
ctx.flags_sibling = 0;
ctx.parent = parent; /* already hold a ref */
exc = dom_document_get_document_element(mgr->doc,
(void *) &ctx.document_root);
if ((exc != DOM_NO_ERR) || (ctx.document_root == NULL)) {
/* No root element yet: done */
NSLOG(layout, DBG, "bm: rebuild: no root element: %u", exc);
return;
}
// XXX: this recurses. iteration better?
(void) _foreach_child(parent, _rebuild_boxes_for_element, &ctx);
dom_node_unref(ctx.document_root);
}
static void
box_manager_process_events(void *pw)
{
box_manager *mgr = pw;
dom_node **pending_queue;
dom_exception exc;
size_t pending_queue_idx;
size_t i, j;
size_t orphaned_nodes = 0, duplicate_nodes = 0;
size_t descendant_nodes = 0, processed_events = 0;
/* 0. Capture current pending queue and prepare for a new one */
mgr->callback_pending = false;
pending_queue = mgr->pending_queue;
pending_queue_idx = mgr->pending_queue_idx;
mgr->pending_queue = NULL;
mgr->pending_queue_idx = mgr->pending_queue_slots = 0;
// XXX: probably want to make this non-blocking (or, at worst,
// XXX: semi-blocking, by splitting the work into chunks and
// XXX: yielding control between chunks).
/* 1. Eliminate nodes which are no longer in our document, or in a
* detached tree (we expect to have received a corresponding event for
* their parent) */
for (i = pending_queue_idx; i > 0; i--) {
bool contains = false;
if (pending_queue[i-1] == NULL) {
continue;
}
exc = dom_node_contains(mgr->doc, pending_queue[i-1],
&contains);
if (exc == DOM_NO_ERR && contains == false) {
dom_node_unref(pending_queue[i-1]);
pending_queue[i-1] = NULL;
orphaned_nodes++;
continue;
}
/* 2. Eliminate duplicate nodes from the list */
for (j = pending_queue_idx; j > i + 1; j--) {
if (pending_queue[j-1] == NULL) {
continue;
}
if (pending_queue[i-1] == pending_queue[j-1]) {
dom_node_unref(pending_queue[i-1]);
pending_queue[i-1] = NULL;
duplicate_nodes++;
break;
}
}
}
/* 3. Eliminate nodes which are descendants of other nodes in the
* list */
for (i = pending_queue_idx; i > 0; i--) {
if (pending_queue[i-1] == NULL) {
continue;
}
for (j = pending_queue_idx; j > 0; j--) {
bool contains = false;
if (j == i || pending_queue[j-1] == NULL) {
continue;
}
exc = dom_node_contains(pending_queue[j-1],
pending_queue[i-1], &contains);
if (exc == DOM_NO_ERR && contains == true) {
dom_node_unref(pending_queue[i-1]);
pending_queue[i-1] = NULL;
descendant_nodes++;
break;
}
}
}
/* 4. For every remaining node: (re)build the structural box tree
* segment rooted at each of node's children. We do this in receive
* order, though it should make no difference. */
for (i = 0; i < pending_queue_idx; i++) {
if (pending_queue[i] == NULL) {
continue;
}
box_manager_rebuild_boxes_for_children(mgr, pending_queue[i]);
/* We're done with this node */
dom_node_unref(pending_queue[i]);
pending_queue[i] = NULL;
processed_events++;
}
NSLOG(layout, DBG, "bm: received %zu raw events (%zu orphaned, "
"%zu duplicate, %zu descendant) => "
"processed %zu events",
pending_queue_idx, orphaned_nodes,
duplicate_nodes, descendant_nodes,
processed_events);
/* Reschedule ourselves for later */
// XXX: 20ms ~= 50Hz -- enough?
guit->misc->schedule(20, box_manager_process_events, mgr);
mgr->callback_pending = true;
}
static void
box_manager_capture_event_target(box_manager *mgr, dom_event_target *target)
{
if (mgr->pending_queue_idx == mgr->pending_queue_slots) {
size_t slots;
dom_node **tmp;
if (mgr->pending_queue == NULL) {
slots = 1024;
tmp = malloc(sizeof(*tmp) * slots);
} else {
slots = mgr->pending_queue_slots * 2;
tmp = realloc(mgr->pending_queue,
sizeof(*tmp) * slots);
}
if (tmp == NULL) {
/* ENOMEM, but no way to deal with it? */
return;
}
mgr->pending_queue = tmp;
mgr->pending_queue_slots = slots;
}
mgr->pending_queue[mgr->pending_queue_idx++] = (dom_node *) target;
}
static dom_node *
_find_parent_element_or_document(dom_node *target)
{
dom_node *parent = NULL;
dom_exception exc = DOM_NO_ERR;
/* Take ref on target (we'll release it in the loop) */
dom_node_ref(target);
do {
exc = dom_node_get_parent_node(target, &parent);
dom_node_unref(target);
target = NULL;
if (exc == DOM_NO_ERR && parent != NULL) {
dom_node_type node_type;
target = parent;
parent = NULL;
exc = dom_node_get_node_type(target, &node_type);
if (exc == DOM_NO_ERR &&
(node_type == DOM_ELEMENT_NODE ||
node_type == DOM_DOCUMENT_NODE)) {
break;
}
}
} while (exc == DOM_NO_ERR && target != NULL);
if (exc != DOM_NO_ERR) {
dom_node_unref(target);
target = NULL;
}
return target;
}
static dom_exception
_next_element_sibling(dom_node *cur,
dom_exception (*cb)(dom_node *, void *), void *pw)
{
dom_node *sibling = NULL;
dom_exception exc;
/* Take ref on cur (we'll release it in the loop) */
dom_node_ref(cur);
do {
exc = dom_node_get_next_sibling(cur, &sibling);
dom_node_unref(cur);
cur = NULL;
if (exc == DOM_NO_ERR && sibling != NULL) {
dom_node_type sibling_type;
cur = sibling;
sibling = NULL;
exc = dom_node_get_node_type(cur, &sibling_type);
if (exc == DOM_NO_ERR &&
sibling_type == DOM_ELEMENT_NODE) {
exc = cb(cur, pw);
break;
}
}
} while (exc == DOM_NO_ERR && cur != NULL);
dom_node_unref(cur);
return exc;
}
static dom_exception
_set_node_flags(dom_node *node, uint32_t flags_to_set)
{
bm_user_data *ud = NULL;
dom_exception exc;
exc = dom_node_get_user_data(node,
corestring_dom___ns_key_bm_node_data,
(void **) &ud);
/* User data only exists for nodes in the tree.
* If the node is not in the tree, we expect that
* we will set flags when it is inserted (thus
* losing events is not a problem). */
if (exc == DOM_NO_ERR && ud != NULL) {
ud->flags |= flags_to_set;
}
return exc;
}
static dom_exception
_set_node_flags_cb(dom_node *node, void *pw)
{
uintptr_t flags_to_set = (uintptr_t) pw;
return _set_node_flags(node, (uint32_t) flags_to_set);
}
static uint32_t
_attr_name_to_flag(dom_string *attr_name)
{
uint32_t flag = 0;
if (dom_string_isequal(attr_name, corestring_dom_class)) {
flag = BM_DOM_FLAG_CLASS_ATTRIBUTE_CHANGED;
} else if (dom_string_isequal(attr_name, corestring_dom_id)) {
flag = BM_DOM_FLAG_ID_ATTRIBUTE_CHANGED;
} else if (dom_string_isequal(attr_name, corestring_dom_style)) {
flag = BM_DOM_FLAG_STYLE_ATTRIBUTE_CHANGED;
} else {
flag = BM_DOM_FLAG_ATTRIBUTE_CHANGED;
}
return flag;
}
static void
box_manager_handle_dom_event(dom_event *evt, void *pw)
{
box_manager *mgr = pw;
dom_node *parent = NULL;
dom_event_target *target = NULL;
dom_node_type target_type;
dom_string *evt_type = NULL, *computed_type = NULL;
dom_exception exc;
/* 0. During DOM construction, ignore events until we have a
* valid selection context */
if (mgr->select_ctx == NULL) {
css_select_ctx *select_ctx = NULL;
nserror err;
err = html_css_new_selection_context(mgr->c, &select_ctx);
NSLOG(layout, DBG, "bm: select ctx: %u", err);
if (err != NSERROR_OK) {
return;
}
mgr->select_ctx = select_ctx;
}
/* 1. Ignore unknown events */
exc = dom_event_get_type(evt, &evt_type);
if (exc == DOM_NO_ERR) {
/* Select corestring from event type */
if (dom_string_isequal(evt_type,
corestring_dom_DOMNodeInserted)) {
computed_type = corestring_dom_DOMNodeInserted;
} else if (dom_string_isequal(evt_type,
corestring_dom_DOMNodeRemoved)) {
computed_type = corestring_dom_DOMNodeRemoved;
} else if (dom_string_isequal(evt_type,
corestring_dom_DOMAttrModified)) {
computed_type = corestring_dom_DOMAttrModified;
} else if (dom_string_isequal(evt_type,
corestring_dom_DOMCharacterDataModified)) {
computed_type = corestring_dom_DOMCharacterDataModified;
}
}
dom_string_unref(evt_type);
if (exc != DOM_NO_ERR || computed_type == NULL) {
return;
}
/* 2. Ignore events without targets */
exc = dom_event_get_target(evt, &target);
if (exc != DOM_NO_ERR || target == NULL) {
return;
}
/* 3. The event target must be an Element or Text node */
exc = dom_node_get_node_type(target, &target_type);
if (exc != DOM_NO_ERR || (target_type != DOM_ELEMENT_NODE &&
target_type != DOM_TEXT_NODE)) {
dom_node_unref(target);
return;
}
/* 4. Find the parent node whose subtree is now invalid */
parent = _find_parent_element_or_document((dom_node *) target);
if (parent == NULL) {
dom_node_unref(target);
return;
}
/* 5. update node flags */
if (target_type == DOM_ELEMENT_NODE) {
/* Mark nodes as structurally changed */
if (computed_type == corestring_dom_DOMNodeRemoved) {
/* Node removed: subsequent sibling element */
exc = _remove_user_data((dom_node *) target);
if (exc == DOM_NO_ERR) {
exc = _next_element_sibling((dom_node *) target,
_set_node_flags_cb,
(void *) BM_DOM_FLAG_PREVIOUS_NODE_REMOVED);
}
} else if (computed_type == corestring_dom_DOMNodeInserted) {
/* Node inserted: target itself */
exc = _ensure_user_data((dom_node *) target);
if (exc == DOM_NO_ERR) {
exc = _set_node_flags((dom_node *) target,
BM_DOM_FLAG_NODE_INSERTED);
}
} else {
/* Attribute modified: target itself */
dom_string *attr_name = NULL;
exc = dom_mutation_event_get_attr_name(evt,
&attr_name);
if (exc == DOM_NO_ERR && attr_name != NULL) {
exc = _set_node_flags((dom_node *) target,
_attr_name_to_flag(attr_name));
dom_string_unref(attr_name);
}
}
} else if (target_type == DOM_TEXT_NODE) {
/* Mark parent as text content changed */
exc = _set_node_flags((dom_node *) parent,
BM_DOM_FLAG_TEXT_CONTENTS_CHANGED);
}
dom_node_unref(target);
if (exc != DOM_NO_ERR) {
dom_node_unref(parent);
return;
}
/* 6. Capture parent node (ensuring we keep a ref) into list of
* pending events. */
box_manager_capture_event_target(mgr, (dom_event_target *) parent);
}
nserror
box_manager_styles_changed(box_manager *manager)
{
css_select_ctx *select_ctx = NULL;
dom_node *html;
dom_exception exc;
nserror err;
err = html_css_new_selection_context(manager->c, &select_ctx);
NSLOG(layout, DBG, "bm: styles changed: %u", err);
if (err != NSERROR_OK) {
if (err == NSERROR_CSS_BASE) {
/* Base stylesheet not loaded yet: ok */
err = NSERROR_OK;
}
return err;
}
/* Replace selection context with the new one */
if (manager->select_ctx != NULL) {
css_select_ctx_destroy(manager->select_ctx);
}
manager->select_ctx = select_ctx;
/* Set stylesheets changed flag on root element */
exc = dom_document_get_document_element(manager->doc, (void *) &html);
if ((exc != DOM_NO_ERR) || (html == NULL)) {
/* No root element yet: done */
NSLOG(layout, DBG, "bm: styles changed: no root element: %u", exc);
return NSERROR_OK;
}
exc = _set_node_flags(html, BM_DOM_FLAG_STYLESHEETS_CHANGED);
dom_node_unref(html);
if (exc != DOM_NO_ERR) {
/* Failed setting the flag: bail */
NSLOG(layout, DBG, "bm: styles changed: dom: %u", exc);
return NSERROR_DOM;
}
/* Inject a pending event (taking a ref on the parent) */
box_manager_capture_event_target(manager,
(dom_event_target *) dom_node_ref(manager->doc));
return NSERROR_OK;
}
nserror
box_manager_create(struct html_content *c, dom_document *doc,
box_manager **manager)
{
box_manager *mgr;
dom_exception exc;
mgr = malloc(sizeof(*mgr));
if (mgr == NULL) {
return NSERROR_NOMEM;
}
/* Register event listener.
*
* We listen for AttrModified, CharacterDataModified, NodeInserted,
* and NodeRemoved events.
*
* In all cases (where the event target is an Element), the affected
* subtree is rooted at the Element's *parent* Element.
*
* Where the event target is a Text node, the affected Element is the
* closest Element ancestor of the Text node.
*/
exc = dom_event_listener_create(box_manager_handle_dom_event, mgr,
&mgr->listener);
if (exc != DOM_NO_ERR) {
free(mgr);
return NSERROR_DOM;
}
exc = dom_event_target_add_event_listener(doc,
corestring_dom_DOMNodeInserted,
mgr->listener,
true);
if (exc != DOM_NO_ERR) {
dom_event_listener_unref(mgr->listener);
free(mgr);
return NSERROR_DOM;
}
exc = dom_event_target_add_event_listener(doc,
corestring_dom_DOMNodeRemoved,
mgr->listener,
true);
if (exc != DOM_NO_ERR) {
/* Remove all instances of this listener */
dom_event_target_remove_event_listener(doc, NULL,
mgr->listener, true);
dom_event_listener_unref(mgr->listener);
free(mgr);
return NSERROR_DOM;
}
exc = dom_event_target_add_event_listener(doc,
corestring_dom_DOMAttrModified,
mgr->listener,
true);
if (exc != DOM_NO_ERR) {
/* Remove all instances of this listener */
dom_event_target_remove_event_listener(doc, NULL,
mgr->listener, true);
dom_event_listener_unref(mgr->listener);
free(mgr);
return NSERROR_DOM;
}
exc = dom_event_target_add_event_listener(doc,
corestring_dom_DOMCharacterDataModified,
mgr->listener,
true);
if (exc != DOM_NO_ERR) {
/* Remove all instances of this listener */
dom_event_target_remove_event_listener(doc, NULL,
mgr->listener, true);
dom_event_listener_unref(mgr->listener);
free(mgr);
return NSERROR_DOM;
}
mgr->c = c;
mgr->doc = (dom_document *) dom_node_ref(doc);
mgr->select_ctx = NULL;
mgr->callback_pending = false;
mgr->pending_queue = NULL;
mgr->pending_queue_idx = mgr->pending_queue_slots = 0;
/* Schedule a callback to process the events */
// XXX: 20ms ~= 50Hz -- enough?
guit->misc->schedule(20, box_manager_process_events, mgr);
mgr->callback_pending = true;
*manager = mgr;
return NSERROR_OK;
}
nserror
box_manager_destroy(box_manager *manager)
{
if (manager->callback_pending) {
guit->misc->schedule(-1, box_manager_process_events, manager);
}
while (manager->pending_queue_idx > 0) {
manager->pending_queue_idx--;
dom_node_unref(
manager->pending_queue[manager->pending_queue_idx]);
}
free(manager->pending_queue);
(void) dom_event_target_remove_event_listener(manager->doc,
NULL,
manager->listener,
true);
dom_event_listener_unref(manager->listener);
if (manager->select_ctx != NULL) {
css_select_ctx_destroy(manager->select_ctx);
}
dom_node_unref(manager->doc);
free(manager);
return NSERROR_OK;
}