text: /*
   * Copyright 2007-2008 Jacek Caban for CodeWeavers
   *
   * 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 Foundation; 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 of
   * 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 St, Fifth Floor, Boston, MA 02110-1301, USA
   */
  
  #include "config.h"
  
  #include <stdarg.h>
  
  #define COBJMACROS
  
  #include "windef.h"
  #include "winbase.h"
  #include "winuser.h"
  #include "ole2.h"
  #include "mshtmcid.h"
  #include "shlguid.h"
  
  #include "wine/debug.h"
  
  #include "mshtml_private.h"
  #include "htmlscript.h"
  #include "htmlevent.h"
  #include "binding.h"
  #include "resource.h"
  
  WINE_DEFAULT_DEBUG_CHANNEL(mshtml);
  
  static const PRUnichar blurW[]      = {'b','l','u','r',0};
  static const PRUnichar focusW[]     = {'f','o','c','u','s',0};
  static const PRUnichar keypressW[]  = {'k','e','y','p','r','e','s','s',0};
  static const PRUnichar loadW[]      = {'l','o','a','d',0};
  
  typedef struct {
      nsIDOMEventListener nsIDOMEventListener_iface;
      nsDocumentEventListener *This;
  } nsEventListener;
  
  struct nsDocumentEventListener {
      nsEventListener blur_listener;
      nsEventListener focus_listener;
      nsEventListener keypress_listener;
      nsEventListener load_listener;
      nsEventListener htmlevent_listener;
  
      LONG ref;
  
      HTMLDocumentNode *doc;
  };
  
  static LONG release_listener(nsDocumentEventListener *This)
  {
      LONG ref = InterlockedDecrement(&This->ref);
  
      TRACE("(%p) ref=%d\n", This, ref);
  
      if(!ref)
          heap_free(This);
  
      return ref;
  }
  
  static inline nsEventListener *impl_from_nsIDOMEventListener(nsIDOMEventListener *iface)
  {
      return CONTAINING_RECORD(iface, nsEventListener, nsIDOMEventListener_iface);
  }
  
  static nsresult NSAPI nsDOMEventListener_QueryInterface(nsIDOMEventListener *iface,
          nsIIDRef riid, void **result)
  {
      nsEventListener *This = impl_from_nsIDOMEventListener(iface);
  
      *result = NULL;
  
      if(IsEqualGUID(&IID_nsISupports, riid)) {
          TRACE("(%p)->(IID_nsISupports, %p)\n", This, result);
          *result = &This->nsIDOMEventListener_iface;
      }else if(IsEqualGUID(&IID_nsIDOMEventListener, riid)) {
          TRACE("(%p)->(IID_nsIDOMEventListener %p)\n", This, result);
          *result = &This->nsIDOMEventListener_iface;
      }
  
      if(*result) {
          nsIDOMEventListener_AddRef(&This->nsIDOMEventListener_iface);
          return NS_OK;
      }
  
      TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), result);
      return NS_NOINTERFACE;
  }
  
  static nsrefcnt NSAPI nsDOMEventListener_AddRef(nsIDOMEventListener *iface)
  {
      nsEventListener *This = impl_from_nsIDOMEventListener(iface);
      LONG ref = InterlockedIncrement(&This->This->ref);
  
      TRACE("(%p) ref=%d\n", This->This, ref);
  
      return ref;
  }
  
  static nsrefcnt NSAPI nsDOMEventListener_Release(nsIDOMEventListener *iface)
  {
      nsEventListener *This = impl_from_nsIDOMEventListener(iface);
  
      return release_listener(This->This);
  }
  
  static BOOL is_doc_child_focus(NSContainer *nscontainer)
  {
      HWND hwnd;
  
      for(hwnd = GetFocus(); hwnd && hwnd != nscontainer->hwnd; hwnd = GetParent(hwnd));
  
      return hwnd != NULL;
  }
  
  static nsresult NSAPI handle_blur(nsIDOMEventListener *iface, nsIDOMEvent *event)
  {
      nsEventListener *This = impl_from_nsIDOMEventListener(iface);
      HTMLDocumentNode *doc = This->This->doc;
      HTMLDocumentObj *doc_obj;
  
      TRACE("(%p)\n", doc);
  
      if(!doc || !doc->basedoc.doc_obj)
          return NS_ERROR_FAILURE;
      doc_obj = doc->basedoc.doc_obj;
  
      if(doc_obj->focus && !is_doc_child_focus(doc_obj->nscontainer)) {
          doc_obj->focus = FALSE;
          notif_focus(doc_obj);
      }
  
      return NS_OK;
  }
  
  static nsresult NSAPI handle_focus(nsIDOMEventListener *iface, nsIDOMEvent *event)
  {
      nsEventListener *This = impl_from_nsIDOMEventListener(iface);
      HTMLDocumentNode *doc = This->This->doc;
      HTMLDocumentObj *doc_obj;
  
      TRACE("(%p)\n", doc);
  
      if(!doc)
          return NS_ERROR_FAILURE;
      doc_obj = doc->basedoc.doc_obj;
  
      if(!doc_obj->focus) {
          doc_obj->focus = TRUE;
          notif_focus(doc_obj);
      }
  
      return NS_OK;
  }
  
  static nsresult NSAPI handle_keypress(nsIDOMEventListener *iface,
          nsIDOMEvent *event)
  {
      nsEventListener *This = impl_from_nsIDOMEventListener(iface);
      HTMLDocumentNode *doc = This->This->doc;
      HTMLDocumentObj *doc_obj;
  
      if(!doc)
          return NS_ERROR_FAILURE;
      doc_obj = doc->basedoc.doc_obj;
  
      TRACE("(%p)->(%p)\n", doc, event);
  
      update_doc(&doc_obj->basedoc, UPDATE_UI);
      if(doc_obj->usermode == EDITMODE)
          handle_edit_event(&doc_obj->basedoc, event);
  
      return NS_OK;
  }
  
  static void handle_docobj_load(HTMLDocumentObj *doc)
  {
      IOleCommandTarget *olecmd = NULL;
      HRESULT hres;
  
      if(doc->nscontainer->editor_controller) {
          nsIController_Release(doc->nscontainer->editor_controller);
          doc->nscontainer->editor_controller = NULL;
      }
  
      if(doc->usermode == EDITMODE)
          handle_edit_load(&doc->basedoc);
  
      if(doc->client) {
          hres = IOleClientSite_QueryInterface(doc->client, &IID_IOleCommandTarget, (void**)&olecmd);
          if(FAILED(hres))
              olecmd = NULL;
      }
  
      if(doc->download_state) {
          if(olecmd) {
              VARIANT progress;
  
              V_VT(&progress) = VT_I4;
              V_I4(&progress) = 0;
              IOleCommandTarget_Exec(olecmd, NULL, OLECMDID_SETPROGRESSPOS,
                      OLECMDEXECOPT_DONTPROMPTUSER, &progress, NULL);
          }
  
          set_download_state(doc, 0);
      }
  
      if(olecmd) {
          IOleCommandTarget_Exec(olecmd, &CGID_ShellDocView, 103, 0, NULL, NULL);
          IOleCommandTarget_Exec(olecmd, &CGID_MSHTML, IDM_PARSECOMPLETE, 0, NULL, NULL);
          IOleCommandTarget_Exec(olecmd, NULL, OLECMDID_HTTPEQUIV_DONE, 0, NULL, NULL);
  
          IOleCommandTarget_Release(olecmd);
      }
  }
  
  static nsresult NSAPI handle_load(nsIDOMEventListener *iface, nsIDOMEvent *event)
  {
      nsEventListener *This = impl_from_nsIDOMEventListener(iface);
      HTMLDocumentNode *doc = This->This->doc;
      HTMLDocumentObj *doc_obj = NULL;
      nsresult nsres = NS_OK;
  
      TRACE("(%p)\n", doc);
  
      if(!doc || !doc->basedoc.window)
          return NS_ERROR_FAILURE;
      if(doc->basedoc.doc_obj && doc->basedoc.doc_obj->basedoc.doc_node == doc)
          doc_obj = doc->basedoc.doc_obj;
  
      connect_scripts(doc->window);
  
      htmldoc_addref(&doc->basedoc);
  
      if(doc_obj)
          handle_docobj_load(doc_obj);
  
      set_ready_state(doc->basedoc.window, READYSTATE_COMPLETE);
  
      if(doc_obj) {
          if(doc_obj->view_sink)
              IAdviseSink_OnViewChange(doc_obj->view_sink, DVASPECT_CONTENT, -1);
  
          set_statustext(doc_obj, IDS_STATUS_DONE, NULL);
  
          update_title(doc_obj);
      }
  
      if(doc_obj && doc_obj->usermode!=EDITMODE && doc_obj->doc_object_service
         && !(doc->basedoc.window->load_flags & BINDING_REFRESH))
          IDocObjectService_FireDocumentComplete(doc_obj->doc_object_service,
                  &doc->basedoc.window->base.IHTMLWindow2_iface, 0);
  
      if(doc->nsdoc) {
          nsIDOMHTMLElement *nsbody;
  
          flush_pending_tasks(doc->basedoc.task_magic);
  
          nsres = nsIDOMHTMLDocument_GetBody(doc->nsdoc, &nsbody);
          if(NS_SUCCEEDED(nsres) && nsbody) {
              HTMLDOMNode *node;
              HRESULT hres;
  
              hres = get_node(doc, (nsIDOMNode*)nsbody, TRUE, &node);
              nsIDOMHTMLElement_Release(nsbody);
              if(SUCCEEDED(hres)) {
                  fire_event(doc, EVENTID_LOAD, TRUE, node, event,
                          (IDispatch*)&doc->window->base.IDispatchEx_iface);
                  node_release(node);
              }
          }
      }else {
          ERR("NULL nsdoc\n");
          nsres = NS_ERROR_FAILURE;
      }
  
      htmldoc_release(&doc->basedoc);
      return nsres;
  }
  
  static nsresult NSAPI handle_htmlevent(nsIDOMEventListener *iface, nsIDOMEvent *event)
  {
      nsEventListener *This = impl_from_nsIDOMEventListener(iface);
      HTMLDocumentNode *doc = This->This->doc;
      const PRUnichar *type;
      nsIDOMEventTarget *event_target;
      nsIDOMNode *nsnode;
      nsAString type_str;
      HTMLDOMNode *node;
      eventid_t eid;
      nsresult nsres;
      HRESULT hres;
  
      TRACE("%p\n", This->This);
  
      if(!doc) {
          WARN("NULL doc\n");
          return NS_OK;
      }
  
      nsAString_Init(&type_str, NULL);
      nsIDOMEvent_GetType(event, &type_str);
      nsAString_GetData(&type_str, &type);
      eid = str_to_eid(type);
      nsAString_Finish(&type_str);
  
      nsres = nsIDOMEvent_GetTarget(event, &event_target);
      if(NS_FAILED(nsres) || !event_target) {
          ERR("GetEventTarget failed: %08x\n", nsres);
          return NS_OK;
      }
  
      nsres = nsIDOMEventTarget_QueryInterface(event_target, &IID_nsIDOMNode, (void**)&nsnode);
      nsIDOMEventTarget_Release(event_target);
      if(NS_FAILED(nsres)) {
          ERR("Could not get nsIDOMNode: %08x\n", nsres);
          return NS_OK;
      }
  
      hres = get_node(doc, nsnode, TRUE, &node);
      nsIDOMNode_Release(nsnode);
      if(FAILED(hres))
          return NS_OK;
  
      /* If we fine need for more special cases here, we may consider handling it in a more generic way. */
      switch(eid) {
      case EVENTID_FOCUS:
          if(doc->event_vector[EVENTID_FOCUSIN])
              fire_event(doc, EVENTID_FOCUSIN, TRUE, node, NULL, NULL);
          break;
      case EVENTID_BLUR:
          if(doc->event_vector[EVENTID_FOCUSOUT])
              fire_event(doc, EVENTID_FOCUSOUT, TRUE, node, NULL, NULL);
          break;
      default:
          break;
      }
  
      fire_event(doc, eid, TRUE, node, event, NULL);
  
      node_release(node);
      return NS_OK;
  }
  
  #define EVENTLISTENER_VTBL(handler) \
      { \
          nsDOMEventListener_QueryInterface, \
          nsDOMEventListener_AddRef, \
          nsDOMEventListener_Release, \
          handler, \
      }
  
  static const nsIDOMEventListenerVtbl blur_vtbl =      EVENTLISTENER_VTBL(handle_blur);
  static const nsIDOMEventListenerVtbl focus_vtbl =     EVENTLISTENER_VTBL(handle_focus);
  static const nsIDOMEventListenerVtbl keypress_vtbl =  EVENTLISTENER_VTBL(handle_keypress);
  static const nsIDOMEventListenerVtbl load_vtbl =      EVENTLISTENER_VTBL(handle_load);
  static const nsIDOMEventListenerVtbl htmlevent_vtbl = EVENTLISTENER_VTBL(handle_htmlevent);
  
  static void init_event(nsIDOMEventTarget *target, const PRUnichar *type,
          nsIDOMEventListener *listener, BOOL capture)
  {
      nsAString type_str;
      nsresult nsres;
  
      nsAString_InitDepend(&type_str, type);
      nsres = nsIDOMEventTarget_AddEventListener(target, &type_str, listener, capture, FALSE, 1);
      nsAString_Finish(&type_str);
      if(NS_FAILED(nsres))
          ERR("AddEventTarget failed: %08x\n", nsres);
  
  }
  
  static void init_listener(nsEventListener *This, nsDocumentEventListener *listener,
          const nsIDOMEventListenerVtbl *vtbl)
  {
      This->nsIDOMEventListener_iface.lpVtbl = vtbl;
      This->This = listener;
  }
  
  void add_nsevent_listener(HTMLDocumentNode *doc, nsIDOMNode *nsnode, LPCWSTR type)
  {
      nsIDOMEventTarget *target;
      nsresult nsres;
  
      if(nsnode)
          nsres = nsIDOMNode_QueryInterface(nsnode, &IID_nsIDOMEventTarget, (void**)&target);
      else
          nsres = nsIDOMWindow_QueryInterface(doc->basedoc.window->nswindow, &IID_nsIDOMEventTarget, (void**)&target);
      if(NS_FAILED(nsres)) {
          ERR("Could not get nsIDOMEventTarget interface: %08x\n", nsres);
          return;
      }
  
      init_event(target, type, &doc->nsevent_listener->htmlevent_listener.nsIDOMEventListener_iface,
              TRUE);
      nsIDOMEventTarget_Release(target);
  }
  
  static void detach_nslistener(HTMLDocumentNode *doc, const WCHAR *type, nsEventListener *listener, cpp_bool is_capture)
  {
      nsIDOMEventTarget *target;
      nsAString type_str;
      nsresult nsres;
  
      if(!doc->basedoc.window)
          return;
  
      nsres = nsIDOMWindow_QueryInterface(doc->basedoc.window->nswindow, &IID_nsIDOMEventTarget, (void**)&target);
      if(NS_FAILED(nsres)) {
          ERR("Could not get nsIDOMEventTarget interface: %08x\n", nsres);
          return;
      }
  
      nsAString_InitDepend(&type_str, type);
      nsres = nsIDOMEventTarget_RemoveEventListener(target, &type_str,
              &listener->nsIDOMEventListener_iface, is_capture);
      nsAString_Finish(&type_str);
      nsIDOMEventTarget_Release(target);
      if(NS_FAILED(nsres))
          ERR("RemoveEventTarget failed: %08x\n", nsres);
  }
  
  void detach_nsevent(HTMLDocumentNode *doc, const WCHAR *type)
  {
      detach_nslistener(doc, type, &doc->nsevent_listener->htmlevent_listener, TRUE);
  }
  
  void release_nsevents(HTMLDocumentNode *doc)
  {
      nsDocumentEventListener *listener = doc->nsevent_listener;
  
      TRACE("%p %p\n", doc, doc->nsevent_listener);
  
      if(!listener)
          return;
  
      detach_nslistener(doc, blurW,     &listener->blur_listener,     TRUE);
      detach_nslistener(doc, focusW,    &listener->focus_listener,    TRUE);
      detach_nslistener(doc, keypressW, &listener->keypress_listener, FALSE);
      detach_nslistener(doc, loadW,     &listener->load_listener,     TRUE);
  
      listener->doc = NULL;
      release_listener(listener);
      doc->nsevent_listener = NULL;
  }
  
  void init_nsevents(HTMLDocumentNode *doc)
  {
      nsDocumentEventListener *listener;
      nsIDOMEventTarget *target;
      nsresult nsres;
  
      listener = heap_alloc(sizeof(nsDocumentEventListener));
      if(!listener)
          return;
  
      TRACE("%p %p\n", doc, listener);
  
      listener->ref = 1;
      listener->doc = doc;
  
      init_listener(&listener->blur_listener,        listener, &blur_vtbl);
      init_listener(&listener->focus_listener,       listener, &focus_vtbl);
      init_listener(&listener->keypress_listener,    listener, &keypress_vtbl);
      init_listener(&listener->load_listener,        listener, &load_vtbl);
      init_listener(&listener->htmlevent_listener,   listener, &htmlevent_vtbl);
  
      doc->nsevent_listener = listener;
  
      nsres = nsIDOMWindow_QueryInterface(doc->basedoc.window->nswindow, &IID_nsIDOMEventTarget, (void**)&target);
      if(NS_FAILED(nsres)) {
          ERR("Could not get nsIDOMEventTarget interface: %08x\n", nsres);
          return;
      }
  
      init_event(target, blurW,     &listener->blur_listener.nsIDOMEventListener_iface,     TRUE);
      init_event(target, focusW,    &listener->focus_listener.nsIDOMEventListener_iface,    TRUE);
      init_event(target, keypressW, &listener->keypress_listener.nsIDOMEventListener_iface, FALSE);
      init_event(target, loadW,     &listener->load_listener.nsIDOMEventListener_iface,     TRUE);
  
      nsIDOMEventTarget_Release(target);
  }
  