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.
988 lines
26 KiB
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;
|
|
}
|