/* * This file is part of LibNSLayout * Licensed under the ISC License, http://opensource.org/licenses/ISC * Copyright 2015 Michael Drake */ /** \file src/dom/watcher.c * Implementation of DOM mutation watching. * * TODO: LibDOM mutation event listeners are really slow. * Need to find a better way to get DOM change notifications. * LibDOM probably needs to gain Mutation Observer support, or * gain a LibDOM-specific extension API. */ #include #include #include #include #include #include "libnslayout/nslayout.h" #include "dom/debug.h" #include "dom/watcher.h" #include "util/dom-str.h" #include "util/util.h" /** * A dom watcher object */ struct nsl_dom_watcher { dom_document *document; /**< DOM document */ dom_event_listener *listener; /**< DOM event listener object */ nsl_dom_watcher_cb watcher_cb; /**< Client callback */ void *pw; /**< Client data */ }; /** * LibDOM event handler * * \param[in] evt The LibDOM event object * \param[in] pw Pointer to our dom watcher object */ static void nsl__dom_event_handler(struct dom_event *evt, void *pw) { const struct nsl_dom_watcher *watcher = pw; enum nsl_dom_watcher_type type; dom_event_target *node = NULL; dom_string *evt_type = NULL; dom_node_type node_type; dom_exception exc; nsl_dom_debug_dump_event(evt); exc = dom_event_get_target(evt, &node); if ((exc != DOM_NO_ERR) || (node == NULL)) { printf("FAILED to get target node!\n"); goto fail; } exc = dom_node_get_node_type(node, &node_type); if (exc != DOM_NO_ERR) { printf("FAILED to get target node type!\n"); goto fail; } exc = dom_event_get_type(evt, &evt_type); if ((exc != DOM_NO_ERR) || (evt_type == NULL)) { printf("FAILED to get event type!\n"); goto fail; } if (dom_string_isequal(evt_type, nsl_dom_str_node_inserted)) { type = NSL_DOM_WATCHER_NODE_INSERTED; } else if (dom_string_isequal(evt_type, nsl_dom_str_node_removed)) { type = NSL_DOM_WATCHER_NODE_REMOVED; } else if (dom_string_isequal(evt_type, nsl_dom_str_subtree_modified)) { type = NSL_DOM_WATCHER_SUBTREE_MODIFIED; } else if (dom_string_isequal(evt_type, nsl_dom_str_attr_modified)) { type = NSL_DOM_WATCHER_ATTR_MODIFIED; } else if (dom_string_isequal(evt_type, nsl_dom_str_characterdata_modified)) { type = NSL_DOM_WATCHER_CHAR_DATA_MODIFIED; } else { printf("FAILED: unrecognised event type: '%s'", dom_string_data(evt_type)); goto fail; } dom_string_unref(evt_type); watcher->watcher_cb(type, node, node_type, watcher->pw); return; fail: if (evt_type != NULL) dom_string_unref(evt_type); if (node != NULL) dom_node_unref(node); } /** * Destroy a DOM document's event listener * * \param[in] listener The listener to destroy * \param[in] document The document that the listener was registerd for. * \return NSLAYOUT_OK on success, appropriate error otherwise. */ static nslayout_error nsl__dom_listener_destroy( dom_event_listener *listener, dom_document *document) { dom_exception exc; /* Passing NULL as type, removes listener for all event types. */ exc = dom_event_target_remove_event_listener( document, NULL, listener, false); if (exc != DOM_NO_ERR) { return NSL_DOM_ERR(exc); } dom_event_listener_unref(listener); return NSLAYOUT_OK; } /** * Create a dom event listener. * * \param[out] listener_out Returns a dom listener for watcher's document. * \param[in] watcher DOM watcher object that listener is used for. * \return NSLAYOUT_OK on success, appropriate error otherwise. */ static nslayout_error nsl__dom_listener_create( dom_event_listener **listener_out, const struct nsl_dom_watcher *watcher) { dom_event_listener *listener; dom_exception exc; /* Create listener */ exc = dom_event_listener_create(nsl__dom_event_handler, (void *) watcher, &listener); if (exc != DOM_NO_ERR) { goto error; } /* Set the event types it should listen to */ exc = dom_event_target_add_event_listener(watcher->document, nsl_dom_str_node_inserted, listener, false); if (exc != DOM_NO_ERR) { goto error; } exc = dom_event_target_add_event_listener(watcher->document, nsl_dom_str_subtree_modified, listener, false); if (exc != DOM_NO_ERR) { goto error; } exc = dom_event_target_add_event_listener(watcher->document, nsl_dom_str_node_removed, listener, false); if (exc != DOM_NO_ERR) { goto error; } exc = dom_event_target_add_event_listener(watcher->document, nsl_dom_str_attr_modified, listener, false); if (exc != DOM_NO_ERR) { goto error; } exc = dom_event_target_add_event_listener(watcher->document, nsl_dom_str_characterdata_modified, listener, false); if (exc != DOM_NO_ERR) { goto error; } *listener_out = listener; return NSLAYOUT_OK; error: nsl__dom_listener_destroy(listener, watcher->document); return NSL_DOM_ERR(exc); } /* Exported function, documented in src/dom/watcher.h */ nslayout_error nsl_dom_watcher_create( struct nsl_dom_watcher **watcher_out, dom_document *document, nsl_dom_watcher_cb watcher_cb, void *pw) { struct nsl_dom_watcher *watcher; nslayout_error err; watcher = malloc(sizeof(*watcher)); if (watcher == NULL) { return NSLAYOUT_NO_MEM; } watcher->document = document; watcher->watcher_cb = watcher_cb; watcher->pw = pw; err = nsl__dom_listener_create(&watcher->listener, watcher); if (err != NSLAYOUT_OK) { free(watcher); return err; } *watcher_out = watcher; return NSLAYOUT_OK; } /* Exported function, documented in src/dom/watcher.h */ nslayout_error nsl_dom_watcher_destroy( struct nsl_dom_watcher *watcher) { nsl__dom_listener_destroy(watcher->listener, watcher->document); free(watcher); return NSLAYOUT_OK; }