Tue, 07 May 2013 05:04:46 -0400
Rewrite debug window filter in JS.
Note, this does cause a couple regressions, but they are probably not
that big a deal. First, the JS regular expression syntax is slightly
different. Second, the JS regex API lacks a way to reliably determine
the location of matched groups, so we can't highlight just the groups
and must highlight the entire expression.
I suspect that none of our users ever had to use any fancy regex in the
debug window, and that most of our developers didn't even know it could
be done. So I doubt these regressions will cause much pain.
| pidgin/gtkdebug.c | file | annotate | diff | comparison | revisions | |
| pidgin/gtkdebug.html | file | annotate | diff | comparison | revisions |
--- a/pidgin/gtkdebug.c Mon May 06 17:09:25 2013 -0400 +++ b/pidgin/gtkdebug.c Tue May 07 05:04:46 2013 -0400 @@ -166,228 +166,24 @@ } static void -regex_toggle_div(WebKitDOMNode *div) -{ - WebKitDOMDOMTokenList *classes; - - if (!WEBKIT_DOM_IS_HTML_ELEMENT(div)) - return; - -#if (WEBKIT_MAJOR_VERSION == 1 && \ - WEBKIT_MINOR_VERSION == 9 && \ - WEBKIT_MICRO_VERSION == 90) - /* Workaround WebKit API bug. */ - classes = webkit_dom_element_get_class_list(WEBKIT_DOM_ELEMENT(div)); -#else - classes = webkit_dom_html_element_get_class_list(WEBKIT_DOM_HTML_ELEMENT(div)); -#endif - webkit_dom_dom_token_list_toggle(classes, "hide", NULL); - g_object_unref(classes); -} - -static void -regex_highlight_clear(WebKitDOMDocument *dom) -{ - WebKitDOMNodeList *nodes; - gulong i; - - /* Remove highlighting SPANs */ - nodes = webkit_dom_document_get_elements_by_class_name(dom, "regex"); - i = webkit_dom_node_list_get_length(nodes); - while (i--) { - WebKitDOMNode *span, *parent; - char *content; - WebKitDOMText *text; - GError *err = NULL; - - span = webkit_dom_node_list_item(nodes, i); - parent = webkit_dom_node_get_parent_node(span); - - content = webkit_dom_node_get_text_content(span); - text = webkit_dom_document_create_text_node(dom, content); - g_free(content); - - webkit_dom_node_replace_child(parent, WEBKIT_DOM_NODE(text), span, &err); - } - - g_object_unref(nodes); -} - -static void -regex_highlight_text_nodes(WebKitDOMDocument *dom, WebKitDOMNode *div, - gint start_pos, gint end_pos) -{ - GSList *data = NULL; - WebKitDOMNode *node; - WebKitDOMRange *range; - WebKitDOMElement *span; - gint ind, end_ind; - gint this_start, this_end; - - ind = 0; - webkit_dom_node_normalize(div); - node = div; - - /* First, find the container nodes and offsets to apply highlighting. */ - do { - if (webkit_dom_node_get_node_type(node) == 3/*TEXT_NODE*/) { - /* The GObject model does not correctly reflect the type, hence the - regular cast. */ - end_ind = ind + webkit_dom_character_data_get_length((WebKitDOMCharacterData*)node); - - if (start_pos <= ind) - this_start = 0; - else if (start_pos < end_ind) - this_start = start_pos - ind; - else - this_start = -1; - - if (end_pos < end_ind) - this_end = end_pos - ind; - else - this_end = end_ind - ind; - - if (this_start != -1 && this_start < this_end) { - data = g_slist_prepend(data, GINT_TO_POINTER(this_end)); - data = g_slist_prepend(data, GINT_TO_POINTER(this_start)); - data = g_slist_prepend(data, node); - } - - ind = end_ind; - } - - if (webkit_dom_node_has_child_nodes(node)) { - node = webkit_dom_node_get_first_child(node); - } else { - while (node != div) { - WebKitDOMNode *next; - - next = webkit_dom_node_get_next_sibling(node); - if (next) { - node = next; - break; - } else { - node = webkit_dom_node_get_parent_node(node); - } - } - } - } while (node != div); - - /* Second, apply highlighting to saved sections. Changing the DOM is - automatically reflected in all WebKit API, so we have to do this after - finding the offsets, or things could get complicated. */ - while (data) { - node = WEBKIT_DOM_NODE(data->data); - data = g_slist_delete_link(data, data); - this_start = GPOINTER_TO_INT(data->data); - data = g_slist_delete_link(data, data); - this_end = GPOINTER_TO_INT(data->data); - data = g_slist_delete_link(data, data); - - range = webkit_dom_document_create_range(dom); - webkit_dom_range_set_start(range, node, this_start, NULL); - webkit_dom_range_set_end(range, node, this_end, NULL); - span = webkit_dom_document_create_element(dom, "span", NULL); - webkit_dom_html_element_set_class_name(WEBKIT_DOM_HTML_ELEMENT(span), - "regex"); - webkit_dom_range_surround_contents(range, WEBKIT_DOM_NODE(span), NULL); - } -} - -static void -regex_match(DebugWindow *win, WebKitDOMDocument *dom, WebKitDOMNode *div) -{ - GMatchInfo *match_info; - gchar *text; - gchar *plaintext; - - text = webkit_dom_node_get_text_content(div); - if (!text) - return; - - /* I don't like having to do this, but we need it for highlighting. Plus - * it makes the ^ and $ operators work :) - */ - plaintext = purple_markup_strip_html(text); - - /* We do a first pass to see if it matches at all. If it does we work out - * the offsets to highlight. - */ - if (g_regex_match(win->regex, plaintext, 0, &match_info) != win->invert) { - /* If we're not highlighting or the expression is inverted, we're - * done and move on. - */ - if (!win->highlight || win->invert) { - g_free(plaintext); - g_match_info_free(match_info); - return; - } - - do { - gint m, count; - gint start_pos, end_pos; - - if (!g_match_info_matches(match_info)) - break; - - count = g_match_info_get_match_count(match_info); - if (count == 1) - m = 0; - else - m = 1; - - for (; m < count; m++) - { - g_match_info_fetch_pos(match_info, m, &start_pos, &end_pos); - - if (end_pos == -1) - break; - - regex_highlight_text_nodes(dom, div, start_pos, end_pos); - } - } while (g_match_info_next(match_info, NULL)); - - g_match_info_free(match_info); - } else { - regex_toggle_div(div); - } - - g_free(plaintext); - g_free(text); -} - -static void regex_toggle_filter(DebugWindow *win, gboolean filter) { - WebKitDOMDocument *dom; - WebKitDOMNodeList *list; - gulong i; - - dom = webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(win->text)); - - if (win->highlight) - regex_highlight_clear(dom); - - /* Re-show debug lines that didn't match regex */ - list = webkit_dom_document_get_elements_by_class_name(dom, "hide"); - i = webkit_dom_node_list_get_length(list); - - while (i--) { - WebKitDOMNode *div = webkit_dom_node_list_item(list, i); - regex_toggle_div(div); - } - - g_object_unref(list); + gtk_webview_safe_execute_script(GTK_WEBVIEW(win->text), "regex.clear();"); if (filter) { - list = webkit_dom_document_get_elements_by_tag_name(dom, "div"); + const char *text; + char *regex; + char *script; - for (i = 0; i < webkit_dom_node_list_get_length(list); i++) { - WebKitDOMNode *div = webkit_dom_node_list_item(list, i); - regex_match(win, dom, div); - } - - g_object_unref(list); + text = gtk_entry_get_text(GTK_ENTRY(win->expression)); + regex = gtk_webview_quote_js_string(text); + script = g_strdup_printf("regex.filterAll(%s, %s, %s);", + regex, + win->invert ? "true" : "false", + win->highlight ? "true" : "false"); + gtk_webview_safe_execute_script(GTK_WEBVIEW(win->text), script); + g_free(script); + g_free(regex); } } @@ -476,7 +272,11 @@ if (win->regex) g_regex_unref(win->regex); +#if GLIB_CHECK_VERSION(2,34,0) + win->regex = g_regex_new(text, G_REGEX_CASELESS|G_REGEX_JAVASCRIPT_COMPAT, 0, NULL); +#else win->regex = g_regex_new(text, G_REGEX_CASELESS, 0, NULL); +#endif if (win->regex == NULL) { /* failed to compile */ regex_change_color(win->expression, 0xFFFF, 0xAFFF, 0xAFFF); @@ -600,42 +400,6 @@ return FALSE; } -static void -debug_window_appended(DebugWindow *win) -{ - WebKitDOMDocument *dom; - WebKitDOMHTMLElement *body; - WebKitDOMNode *div; - - if (!gtk_toggle_tool_button_get_active( - GTK_TOGGLE_TOOL_BUTTON(win->filter))) - return; - - dom = webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(win->text)); - body = webkit_dom_document_get_body(dom); - div = webkit_dom_node_get_last_child(WEBKIT_DOM_NODE(body)); - - if (webkit_dom_element_webkit_matches_selector( - WEBKIT_DOM_ELEMENT(div), "body>div:not(#pause)", NULL)) - regex_match(win, dom, div); -} - -static gboolean debug_window_alert_cb(WebKitWebView *webview, - WebKitWebFrame *frame, gchar *message, gpointer _win) -{ - DebugWindow *win = _win; - - if (!win || !win->window) - return FALSE; - - if (g_strcmp0(message, "appended") == 0) { - debug_window_appended(win); - return TRUE; - } - - return FALSE; -} - static DebugWindow * debug_window_new(void) { @@ -799,9 +563,6 @@ gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); gtk_widget_show(frame); - g_signal_connect(G_OBJECT(win->text), "script-alert", - G_CALLBACK(debug_window_alert_cb), win); - clear_cb(NULL, win); gtk_widget_show_all(win->window);
--- a/pidgin/gtkdebug.html Mon May 06 17:09:25 2013 -0400 +++ b/pidgin/gtkdebug.html Tue May 07 05:04:46 2013 -0400 @@ -21,6 +21,168 @@ span.regex{background-color:#ffafaf;font-weight:bold;} </style> <script> + regex = { + clear: function () { + var list, i; + /* Remove highlighting SPANs */ + list = document.getElementsByClassName('regex'); + i = list.length; + while (i--) { + var span = list[i]; + var parent = span.parentNode; + var content = span.textContent; + var text = document.createTextNode(content); + parent.replaceChild(text, span); + } + + /* Remove hidden DIVs */ + list = document.getElementsByClassName('hide'); + i = list.length; + while (i--) { + list[i].classList.remove('hide'); + } + + this.enabled = false; + }, + + highlightTextNodes: function (div, start_pos, end_pos) { + var data = [], node, range, span, contents; + var ind, end_ind + var this_start, this_end; + + ind = 0; + div.normalize(); + node = div; + + /* First, find the container nodes and offsets to apply highlighting. */ + do { + if (node.nodeType === Node.TEXT_NODE) { + end_ind = ind + node.length; + + if (start_pos <= ind) + this_start = 0; + else if (start_pos < end_ind) + this_start = start_pos - ind; + else + this_start = -1; + + if (end_pos < end_ind) + this_end = end_pos - ind; + else + this_end = end_ind - ind; + + if (this_start != -1 && this_start < this_end) { + data.push(this_end, this_start, node); + } + + ind = end_ind; + } + + if (node.hasChildNodes()) { + node = node.firstChild; + } else { + while (node != div) { + var next = node.nextSibling; + if (next) { + node = next; + break; + } else { + node = node.parentNode; + } + } + } + } while (node != div); + + /* Second, apply highlighting to saved sections. Changing the DOM is + automatically reflected in all WebKit API, so we have to do this after + finding the offsets, or things could get complicated. */ + while (data.length) { + node = data.pop(); + this_start = data.pop(); + this_end = data.pop(); + + range = document.createRange(); + range.setStart(node, this_start); + range.setEnd(node, this_end); + + span = document.createElement('span'); + span.className = 'regex'; + + contents = range.extractContents(); + range.insertNode(span); + span.appendChild(contents); + } + }, + + match: function (div) { + var text, match_info; + var m, count, start_pos, end_pos; + + text = div.textContent; + if (!text) + return; + + /* We do a first pass to see if it matches at all. If it does we work out + * the offsets to highlight. + */ + this.regex.lastIndex = 0; + var match_info = this.regex.exec(text); + if ((match_info != null) != this.invert) { + /* If we're not highlighting or the expression is inverted, we're + * done and move on. + */ + if (!this.highlight || this.invert) + return; + + do { + if (match_info === null) + break; + + count = match_info.length; + if (count === 1) + m = 0; + else + m = 1; + + /* We do this because JS doesn't provide a sufficient means to + determine the indices of matched groups. So we're just going + to highlight the entire match instead. */ + m = 0; count = 1; + + for (; m < count; m++) { + start_pos = match_info.index; + end_pos = this.regex.lastIndex; + if (end_pos == -1) + break; + + this.highlightTextNodes(div, start_pos, end_pos); + } + + /* Workaround broken API for empty matches */ + if (match_info.index == this.regex.lastIndex) + this.regex.lastIndex++; + } while (match_info = this.regex.exec(text)); + } else { + div.classList.add('hide'); + } + }, + + filterAll: function (str, inv, high) { + this.regex = new RegExp(str, 'gi'); + this.invert = inv; + this.highlight = high; + this.enabled = true; + var list = document.getElementsByTagName('div'); + for (var i = 0; i < list.length; i++) + this.match(list[i]); + }, + + highlight: false, + invert: false, + enabled: false, + regex: undefined + } + function append(level, time, cat, msg) { var div = document.createElement('div'); div.className = 'l' + level; @@ -32,8 +194,9 @@ div.appendChild(document.createTextNode(' ')); } div.appendChild(document.createTextNode(msg)); + if (regex.enabled) + regex.match(div); document.body.appendChild(div); - alert('appended'); } function clear() {