<commit_before>/*
 * Copyright (c) 2008-2013 <NAME>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundaftion; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty off
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/* CREDITS:
 *  - Big thanks belong to <NAME> for his article and sample code on
 *    http://www.codeproject.com/KB/COM/cwebpage.aspx
 */

/* TODO:
 *  - Notifications for context menus so app could customize the menu
 *    or provide its own. (App may want provide different menus depending
 *    on the object clicked. E.g. normal link versus image etc.).
 */

#include "html.h"
#include "theme.h"

#include <exdisp.h>    /* IWebBrowser2 */
#include <exdispid.h>  /* DISP_xxxx constants */
#include <mshtml.h>    /* IHTMLDocument3, IHTMLElement */
#include <mshtmhst.h>  /* IDocHostUIHandler */


/* Uncomment this to have more verbose traces about MC_HTML control. */
/*#define HTML_DEBUG     1*/


#ifdef HTML_DEBUG
    #define HTML_TRACE        MC_TRACE
    #define HTML_TRACE_GUID   MC_TRACE_GUID
#else
    #define HTML_TRACE(...)        do {} while(0)
    #define HTML_TRACE_GUID(...)   do {} while(0)
#endif


/* Window class name */
static const TCHAR html_wc[] = MC_WC_HTML;


/* We use 'about:blank' as a default URL */
static const WCHAR url_blank_data[] = L"\x16\x00about:blank";
static BSTR url_blank = (BSTR) &url_blank_data[2];


static TCHAR ie_prop[] = _T("mctrl.html.handle");


/* Main control structure */
typedef struct html_tag html_t;
struct html_tag {
    HWND win;
    HWND ie_win;
    HWND notify_win;
    WNDPROC ie_proc;
    DWORD style                 : 28;
    DWORD ole_initialized       :  1;
    DWORD unicode_notifications :  1;
    DWORD can_back              :  1;
    DWORD can_forward           :  1;
    mc_ref_t refs;

    /* Pointer to the COM-object representing the embedded Internet Explorer */
    IOleObject* browser_obj;

    /* This structure is also COM-object with these interfaces, for wiring
     * MC_HTML control to the embedded Internet Explorer COM-object */
    IDispatch dispatch;
    IOleClientSite client_site;
    IOleInPlaceSiteEx inplace_site_ex;
    IOleInPlaceFrame inplace_frame;
    IDocHostUIHandler ui_handler;
};


/* Helper macros for retrieving the complete structure inside methods
 * of particular interfaces */
#define MC_HTML_FROM_DISPTACH(ptr_dispatch)                                   \
    MC_CONTAINEROF(ptr_dispatch, html_t, dispatch)
#define MC_HTML_FROM_CLIENT_SITE(ptr_client_site)                             \
    MC_CONTAINEROF(ptr_client_site, html_t, client_site)
#define MC_HTML_FROM_INPLACE_SITE_EX(ptr_inplace_site_ex)                     \
    MC_CONTAINEROF(ptr_inplace_site_ex, html_t, inplace_site_ex)
#define MC_HTML_FROM_INPLACE_FRAME(ptr_inplace_frame)                         \
    MC_CONTAINEROF(ptr_inplace_frame, html_t, inplace_frame)
#define MC_HTML_FROM_UI_HANDLER(ptr_ui_handler)                               \
    MC_CONTAINEROF(ptr_ui_handler, html_t, ui_handler)


static ULONG
html_AddRef(html_t* html)
{
    HTML_TRACE("html_AddRef(%d -> %d)", (int) html->refs, (int) html->refs+1);
    return mc_ref(&html->refs);
}

static ULONG
html_Release(html_t* html)
{
    ULONG refs;

    HTML_TRACE("html_Release(%d -> %d)", (int) html->refs, (int) html->refs-1);
    refs = mc_unref(&html->refs);
    if(refs == 0) {
        HTML_TRACE("html_Release: Freeing the HTML object.");

        if(html->ole_initialized)
            OleUninitialize();

        free(html);
    }
    return refs;
}

static HRESULT
html_QueryInterface(html_t* html, REFIID riid, void** obj)
{
    if(InlineIsEqualGUID(riid, &IID_IUnknown)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IUnknown)", riid);
        *obj = (void*)&html->dispatch;
    } else if(InlineIsEqualGUID(riid, &IID_IDispatch)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IDispatch)", riid);
        *obj = (void*)&html->dispatch;
    } else if(InlineIsEqualGUID(riid, &IID_IOleClientSite)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IOleClientSite)", riid);
        *obj = (void*)&html->client_site;
    } else if(InlineIsEqualGUID(riid, &IID_IOleWindow) ||
              InlineIsEqualGUID(riid, &IID_IOleInPlaceSite) ||
              InlineIsEqualGUID(riid, &IID_IOleInPlaceSiteEx)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IOleInPlaceSiteEx)", riid);
        *obj = (void*)&html->inplace_site_ex;
    } else if(InlineIsEqualGUID(riid, &IID_IOleInPlaceFrame)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IOleInPlaceFrame)", riid);
        *obj = (void*)&html->inplace_frame;
    } else if(InlineIsEqualGUID(riid, &IID_IDocHostUIHandler)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IDocHostUIHandler)", riid);
        *obj = (void*)&html->ui_handler;
    } else {
        HTML_TRACE_GUID("html_QueryInterface: unsupported GUID", riid);
        *obj = NULL;
        return E_NOINTERFACE;
    }

    html_AddRef(html);
    return S_OK;
}


/********************************
 *** IDispatch implementation ***
 ********************************/

static HRESULT STDMETHODCALLTYPE
dispatch_QueryInterface(IDispatch* self, REFIID riid, void** obj)
{
    return html_QueryInterface(MC_HTML_FROM_DISPTACH(self), riid, obj);
}

static ULONG STDMETHODCALLTYPE
dispatch_AddRef(IDispatch* self)
{
    HTML_TRACE("dispatch_AddRef");
    return html_AddRef(MC_HTML_FROM_DISPTACH(self));
}

static ULONG STDMETHODCALLTYPE
dispatch_Release(IDispatch* self)
{
    HTML_TRACE("dispatch_Release");
    return html_Release(MC_HTML_FROM_DISPTACH(self));
}

static HRESULT STDMETHODCALLTYPE
dispatch_GetTypeInfoCount(IDispatch* self, UINT* count)
{
    HTML_TRACE("dispatch_GetTypeInfoCount: [S_OK]");
    *count = 0;
    return S_OK;
}

static HRESULT STDMETHODCALLTYPE
dispatch_GetTypeInfo(IDispatch* self, UINT index, LCID lcid, ITypeInfo** type_info)
{
    HTML_TRACE("dispatch_GetTypeInfo: Stub [TYPE_E_ELEMENTNOTFOUND]");
    *type_info = NULL;
    return TYPE_E_ELEMENTNOTFOUND;
}

static HRESULT STDMETHODCALLTYPE
dispatch_GetIDsOfNames(IDispatch* self, REFIID riid, LPOLESTR* names,
                       UINT names_count, LCID lcid, DISPID* disp_id)
{
    HTML_TRACE("dispatch_GetIDsOfNames('%S'): Stub [E_NOTIMPL]", names_count > 0 ? names[0] : L"");
    *disp_id = DISPID_UNKNOWN;
    return E_NOTIMPL;
}

/* Forward declarations */
static LRESULT html_notify_text(html_t* html, UINT code, const WCHAR* url);
static int html_goto_url(html_t* html, const void* url, BOOL unicode);


static HRESULT STDMETHODCALLTYPE
dispatch_Invoke(IDispatch* self, DISPID disp_id, REFIID riid, LCID lcid,
                WORD flags, DISPPARAMS* params, VARIANT* var_res,
                EXCEPINFO* except, UINT* arg_err)
{
    html_t* html = MC_HTML_FROM_DISPTACH(self);

    switch(disp_id) {
        case DISPID_BEFORENAVIGATE2:
        {
            BSTR url = V_BSTR(V_VARIANTREF(&params->rgvarg[5]));
            VARIANT_BOOL* cancel = V_BOOLREF(&params->rgvarg[0]);

            HTML_TRACE("dispatch_Invoke: DISPID_BEFORENAVIGATE2(%S)", url);

            if(wcsncmp(url, L"app:", 4) == 0) {
                html_notify_text(html, MC_HN_APPLINK, url);
                *cancel = VARIANT_TRUE;
            }
            break;
        }

#if 0
        /* Unfortunately, IE does not send DISPID_DOCUMENTCOMPLETE
         * when refreshing the page (e.g. from context menu). So we workaround
         * via DISPID_PROGRESSCHANGE below. */
        case DISPID_DOCUMENTCOMPLETE:
        {
            BSTR url = V_BSTR(V_VARIANTREF(&params->rgvarg[0]));
            HTML_TRACE("dispatch_Invoke: DISPID_DOCUMENTCOMPLETE(%S)", url);

            html_notify_text(html, MC_HN_DOCUMENTCOMPLETE, url);
            break;
        }
#endif

        case DISPID_PROGRESSCHANGE:
        {
            LONG progress_max = V_I4(&params->rgvarg[0]);
            LONG progress = V_I4(&params->rgvarg[1]);
            MC_NMHTMLPROGRESS notify;
            HTML_TRACE("dispatch_Invoke: DISPID_PROGRESSCHANGE(%ld, %ld)",
                       progress, progress_max);

            /* Send also notification about the progress */
            notify.hdr.hwndFrom = html->win;
            notify.hdr.idFrom = GetDlgCtrlID(html->win);
            notify.hdr.code = MC_HN_PROGRESS;
            notify.lProgress = progress;
            notify.lProgressMax = progress_max;
            MC_SEND(html->notify_win, WM_NOTIFY, notify.hdr.idFrom, &notify);

            /* This replaces DISPID_DOCUMENTCOMPLETE above */
            if(progress < 0  ||  progress_max < 0) {
                IWebBrowser2* browser_iface;
                HRESULT hr;

                hr = html->browser_obj->lpVtbl->QueryInterface(html->browser_obj,
                        &IID_IWebBrowser2, (void**)&browser_iface);
                if(MC_ERR(hr != S_OK  ||  browser_iface == NULL)) {
                    MC_TRACE("dispatch_Invoke(DISPID_PROGRESSCHANGE): "
                             "QueryInterface(IID_IWebBrowser2) failed [0x%lx]", hr);
                } else {
                    BSTR url = NULL;
                    hr = browser_iface->lpVtbl->get_LocationURL(browser_iface, &url);
                    if(hr == S_OK && url != NULL) {
                        html_notify_text(html, MC_HN_DOCUMENTCOMPLETE, url);
                        SysFreeString(url);
                    }
                    browser_iface->lpVtbl->Release(browser_iface);
                }
            }
            break;
        }

        case DISPID_STATUSTEXTCHANGE:
            html_notify_text(html, MC_HN_STATUSTEXT, V_BSTR(&params->rgvarg[0]));
            break;

        case DISPID_TITLECHANGE:
            html_notify_text(html, MC_HN_TITLETEXT, V_BSTR(&params->rgvarg[0]));
            break;

        case DISPID_COMMANDSTATECHANGE:
        {
            VARIANT_BOOL enabled = V_BOOL(&params->rgvarg[0]);
            LONG cmd = V_I4(&params->rgvarg[0]);
            MC_NMHTMLHISTORY notify;

            if(cmd == CSC_NAVIGATEBACK  ||  cmd == CSC_NAVIGATEFORWARD) {
                if(cmd == CSC_NAVIGATEBACK)
                    html->can_back = enabled;
                else
                    html->can_forward = enabled;

                notify.hdr.hwndFrom = html->win;
                notify.hdr.idFrom = GetDlgCtrlID(html->win);
                notify.hdr.code = MC_HN_HISTORY;
                notify.bCanBack = html->can_back;
                notify.bCanForward = html->can_forward;
                MC_SEND(html->notify_win, WM_NOTIFY, notify.hdr.idFrom, &notify);<commit_msg>[HTML] Fix using 2nd param of DISPID_COMMANDSTATECHANGE.<commit_after>/*
 * Copyright (c) 2008-2013 <NAME>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundaftion; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty off
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/* CREDITS:
 *  - Big thanks belong to <NAME> for his article and sample code on
 *    http://www.codeproject.com/KB/COM/cwebpage.aspx
 */

/* TODO:
 *  - Notifications for context menus so app could customize the menu
 *    or provide its own. (App may want provide different menus depending
 *    on the object clicked. E.g. normal link versus image etc.).
 */

#include "html.h"
#include "theme.h"

#include <exdisp.h>    /* IWebBrowser2 */
#include <exdispid.h>  /* DISP_xxxx constants */
#include <mshtml.h>    /* IHTMLDocument3, IHTMLElement */
#include <mshtmhst.h>  /* IDocHostUIHandler */


/* Uncomment this to have more verbose traces about MC_HTML control. */
/*#define HTML_DEBUG     1*/


#ifdef HTML_DEBUG
    #define HTML_TRACE        MC_TRACE
    #define HTML_TRACE_GUID   MC_TRACE_GUID
#else
    #define HTML_TRACE(...)        do {} while(0)
    #define HTML_TRACE_GUID(...)   do {} while(0)
#endif


/* Window class name */
static const TCHAR html_wc[] = MC_WC_HTML;


/* We use 'about:blank' as a default URL */
static const WCHAR url_blank_data[] = L"\x16\x00about:blank";
static BSTR url_blank = (BSTR) &url_blank_data[2];


static TCHAR ie_prop[] = _T("mctrl.html.handle");


/* Main control structure */
typedef struct html_tag html_t;
struct html_tag {
    HWND win;
    HWND ie_win;
    HWND notify_win;
    WNDPROC ie_proc;
    DWORD style                 : 28;
    DWORD ole_initialized       :  1;
    DWORD unicode_notifications :  1;
    DWORD can_back              :  1;
    DWORD can_forward           :  1;
    mc_ref_t refs;

    /* Pointer to the COM-object representing the embedded Internet Explorer */
    IOleObject* browser_obj;

    /* This structure is also COM-object with these interfaces, for wiring
     * MC_HTML control to the embedded Internet Explorer COM-object */
    IDispatch dispatch;
    IOleClientSite client_site;
    IOleInPlaceSiteEx inplace_site_ex;
    IOleInPlaceFrame inplace_frame;
    IDocHostUIHandler ui_handler;
};


/* Helper macros for retrieving the complete structure inside methods
 * of particular interfaces */
#define MC_HTML_FROM_DISPTACH(ptr_dispatch)                                   \
    MC_CONTAINEROF(ptr_dispatch, html_t, dispatch)
#define MC_HTML_FROM_CLIENT_SITE(ptr_client_site)                             \
    MC_CONTAINEROF(ptr_client_site, html_t, client_site)
#define MC_HTML_FROM_INPLACE_SITE_EX(ptr_inplace_site_ex)                     \
    MC_CONTAINEROF(ptr_inplace_site_ex, html_t, inplace_site_ex)
#define MC_HTML_FROM_INPLACE_FRAME(ptr_inplace_frame)                         \
    MC_CONTAINEROF(ptr_inplace_frame, html_t, inplace_frame)
#define MC_HTML_FROM_UI_HANDLER(ptr_ui_handler)                               \
    MC_CONTAINEROF(ptr_ui_handler, html_t, ui_handler)


static ULONG
html_AddRef(html_t* html)
{
    HTML_TRACE("html_AddRef(%d -> %d)", (int) html->refs, (int) html->refs+1);
    return mc_ref(&html->refs);
}

static ULONG
html_Release(html_t* html)
{
    ULONG refs;

    HTML_TRACE("html_Release(%d -> %d)", (int) html->refs, (int) html->refs-1);
    refs = mc_unref(&html->refs);
    if(refs == 0) {
        HTML_TRACE("html_Release: Freeing the HTML object.");

        if(html->ole_initialized)
            OleUninitialize();

        free(html);
    }
    return refs;
}

static HRESULT
html_QueryInterface(html_t* html, REFIID riid, void** obj)
{
    if(InlineIsEqualGUID(riid, &IID_IUnknown)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IUnknown)", riid);
        *obj = (void*)&html->dispatch;
    } else if(InlineIsEqualGUID(riid, &IID_IDispatch)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IDispatch)", riid);
        *obj = (void*)&html->dispatch;
    } else if(InlineIsEqualGUID(riid, &IID_IOleClientSite)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IOleClientSite)", riid);
        *obj = (void*)&html->client_site;
    } else if(InlineIsEqualGUID(riid, &IID_IOleWindow) ||
              InlineIsEqualGUID(riid, &IID_IOleInPlaceSite) ||
              InlineIsEqualGUID(riid, &IID_IOleInPlaceSiteEx)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IOleInPlaceSiteEx)", riid);
        *obj = (void*)&html->inplace_site_ex;
    } else if(InlineIsEqualGUID(riid, &IID_IOleInPlaceFrame)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IOleInPlaceFrame)", riid);
        *obj = (void*)&html->inplace_frame;
    } else if(InlineIsEqualGUID(riid, &IID_IDocHostUIHandler)) {
        HTML_TRACE_GUID("html_QueryInterface(IID_IDocHostUIHandler)", riid);
        *obj = (void*)&html->ui_handler;
    } else {
        HTML_TRACE_GUID("html_QueryInterface: unsupported GUID", riid);
        *obj = NULL;
        return E_NOINTERFACE;
    }

    html_AddRef(html);
    return S_OK;
}


/********************************
 *** IDispatch implementation ***
 ********************************/

static HRESULT STDMETHODCALLTYPE
dispatch_QueryInterface(IDispatch* self, REFIID riid, void** obj)
{
    return html_QueryInterface(MC_HTML_FROM_DISPTACH(self), riid, obj);
}

static ULONG STDMETHODCALLTYPE
dispatch_AddRef(IDispatch* self)
{
    HTML_TRACE("dispatch_AddRef");
    return html_AddRef(MC_HTML_FROM_DISPTACH(self));
}

static ULONG STDMETHODCALLTYPE
dispatch_Release(IDispatch* self)
{
    HTML_TRACE("dispatch_Release");
    return html_Release(MC_HTML_FROM_DISPTACH(self));
}

static HRESULT STDMETHODCALLTYPE
dispatch_GetTypeInfoCount(IDispatch* self, UINT* count)
{
    HTML_TRACE("dispatch_GetTypeInfoCount: [S_OK]");
    *count = 0;
    return S_OK;
}

static HRESULT STDMETHODCALLTYPE
dispatch_GetTypeInfo(IDispatch* self, UINT index, LCID lcid, ITypeInfo** type_info)
{
    HTML_TRACE("dispatch_GetTypeInfo: Stub [TYPE_E_ELEMENTNOTFOUND]");
    *type_info = NULL;
    return TYPE_E_ELEMENTNOTFOUND;
}

static HRESULT STDMETHODCALLTYPE
dispatch_GetIDsOfNames(IDispatch* self, REFIID riid, LPOLESTR* names,
                       UINT names_count, LCID lcid, DISPID* disp_id)
{
    HTML_TRACE("dispatch_GetIDsOfNames('%S'): Stub [E_NOTIMPL]", names_count > 0 ? names[0] : L"");
    *disp_id = DISPID_UNKNOWN;
    return E_NOTIMPL;
}

/* Forward declarations */
static LRESULT html_notify_text(html_t* html, UINT code, const WCHAR* url);
static int html_goto_url(html_t* html, const void* url, BOOL unicode);


static HRESULT STDMETHODCALLTYPE
dispatch_Invoke(IDispatch* self, DISPID disp_id, REFIID riid, LCID lcid,
                WORD flags, DISPPARAMS* params, VARIANT* var_res,
                EXCEPINFO* except, UINT* arg_err)
{
    html_t* html = MC_HTML_FROM_DISPTACH(self);

    switch(disp_id) {
        case DISPID_BEFORENAVIGATE2:
        {
            BSTR url = V_BSTR(V_VARIANTREF(&params->rgvarg[5]));
            VARIANT_BOOL* cancel = V_BOOLREF(&params->rgvarg[0]);

            HTML_TRACE("dispatch_Invoke: DISPID_BEFORENAVIGATE2(%S)", url);

            if(wcsncmp(url, L"app:", 4) == 0) {
                html_notify_text(html, MC_HN_APPLINK, url);
                *cancel = VARIANT_TRUE;
            }
            break;
        }

#if 0
        /* Unfortunately, IE does not send DISPID_DOCUMENTCOMPLETE
         * when refreshing the page (e.g. from context menu). So we workaround
         * via DISPID_PROGRESSCHANGE below. */
        case DISPID_DOCUMENTCOMPLETE:
        {
            BSTR url = V_BSTR(V_VARIANTREF(&params->rgvarg[0]));
            HTML_TRACE("dispatch_Invoke: DISPID_DOCUMENTCOMPLETE(%S)", url);

            html_notify_text(html, MC_HN_DOCUMENTCOMPLETE, url);
            break;
        }
#endif

        case DISPID_PROGRESSCHANGE:
        {
            LONG progress_max = V_I4(&params->rgvarg[0]);
            LONG progress = V_I4(&params->rgvarg[1]);
            MC_NMHTMLPROGRESS notify;
            HTML_TRACE("dispatch_Invoke: DISPID_PROGRESSCHANGE(%ld, %ld)",
                       progress, progress_max);

            /* Send also notification about the progress */
            notify.hdr.hwndFrom = html->win;
            notify.hdr.idFrom = GetDlgCtrlID(html->win);
            notify.hdr.code = MC_HN_PROGRESS;
            notify.lProgress = progress;
            notify.lProgressMax = progress_max;
            MC_SEND(html->notify_win, WM_NOTIFY, notify.hdr.idFrom, &notify);

            /* This replaces DISPID_DOCUMENTCOMPLETE above */
            if(progress < 0  ||  progress_max < 0) {
                IWebBrowser2* browser_iface;
                HRESULT hr;

                hr = html->browser_obj->lpVtbl->QueryInterface(html->browser_obj,
                        &IID_IWebBrowser2, (void**)&browser_iface);
                if(MC_ERR(hr != S_OK  ||  browser_iface == NULL)) {
                    MC_TRACE("dispatch_Invoke(DISPID_PROGRESSCHANGE): "
                             "QueryInterface(IID_IWebBrowser2) failed [0x%lx]", hr);
                } else {
                    BSTR url = NULL;
                    hr = browser_iface->lpVtbl->get_LocationURL(browser_iface, &url);
                    if(hr == S_OK && url != NULL) {
                        html_notify_text(html, MC_HN_DOCUMENTCOMPLETE, url);
                        SysFreeString(url);
                    }
                    browser_iface->lpVtbl->Release(browser_iface);
                }
            }
            break;
        }

        case DISPID_STATUSTEXTCHANGE:
            html_notify_text(html, MC_HN_STATUSTEXT, V_BSTR(&params->rgvarg[0]));
            break;

        case DISPID_TITLECHANGE:
            html_notify_text(html, MC_HN_TITLETEXT, V_BSTR(&params->rgvarg[0]));
            break;

        case DISPID_COMMANDSTATECHANGE:
        {
            LONG cmd = V_I4(&params->rgvarg[0]);
            VARIANT_BOOL enabled = V_BOOL(&params->rgvarg[1]);
            MC_NMHTMLHISTORY notify;

            if(cmd == CSC_NAVIGATEBACK  ||  cmd == CSC_NAVIGATEFORWARD) {
                if(cmd == CSC_NAVIGATEBACK)
                    html->can_back = enabled;
                else
                    html->can_forward = enabled;

                notify.hdr.hwndFrom = html->win;
                notify.hdr.idFrom = GetDlgCtrlID(html->win);
                notify.hdr.code = MC_HN_HISTORY;
                notify.bCanBack = html->can_back;
                notify.bCanForward = html->can_forward;
                MC_SEND(html->notify_win, WM_NOTIFY, notify.hdr.idFrom, &notify);
{'fraction_non_alphanumeric': 0.10738827756568606, 'fraction_numerical': 0.005973501010554682, 'mean_word_length': 2.718436873747495, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 101, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1812190', 'n_tokens_mistral': 7379, 'n_tokens_neox': 7153, 'n_words': 1547}