DOMイベントハンドリング

古いやつ途中でたどってるところが間違ってた。

~/mozilla/widget/src/windows/nsWindow.cpp

PRBool nsWindow::ProcessMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *aRetValue)
.......
      else if (!sIMEIsComposing) {
        result = OnKeyDown(wParam, (HIWORD(lParam)), lParam);
      }
      else
        result = PR_FALSE;
BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData)
{
....
  gKbdLayout.OnKeyDown (aVirtualKeyCode);

  // Use only DOMKeyCode for XP processing.
  // Use aVirtualKeyCode for gKbdLayout and native processing.
  UINT DOMKeyCode = sIMEIsComposing ?
                      aVirtualKeyCode : MapFromNativeToDOM(aVirtualKeyCode);

#ifdef DEBUG
  //printf("In OnKeyDown virt: %d  scan: %d\n", DOMKeyCode, aScanCode);
#endif

  BOOL noDefault = DispatchKeyEvent(NS_KEY_DOWN, 0, DOMKeyCode, aKeyData);
PRBool nsWindow::DispatchKeyEvent(PRUint32 aEventType, WORD aCharCode, UINT aVirtualCharCode,
                                  LPARAM aKeyData, PRUint32 aFlags)
{
  nsKeyEvent event(PR_TRUE, aEventType, this);
  nsPoint point(0, 0);

  InitEvent(event, &point); // this add ref's event.widget

  event.flags |= aFlags;
  event.charCode = aCharCode;
  event.keyCode  = aVirtualCharCode;

.......


  event.isShift   = mIsShiftDown;
  event.isControl = mIsControlDown;
  event.isMeta    = PR_FALSE;
  event.isAlt     = mIsAltDown;

  nsPluginEvent pluginEvent;

  switch (aEventType)
  {
    case NS_KEY_UP:
      pluginEvent.event = WM_KEYUP;
      break;
    case NS_KEY_DOWN:
      pluginEvent.event = WM_KEYDOWN;
      break;
    default:
      break;
  }

  pluginEvent.wParam = aVirtualCharCode;
  pluginEvent.lParam = aKeyData;

  event.nativeMsg = (void *)&pluginEvent;

  PRBool result = DispatchWindowEvent(&event);
  NS_RELEASE(event.widget);

  return result;
}
PRBool nsWindow::DispatchWindowEvent(nsGUIEvent* event)
{
  nsEventStatus status;
  DispatchEvent(event, status);
  return ConvertStatus(status);
}
NS_IMETHODIMP nsWindow::DispatchEvent(nsGUIEvent* event, nsEventStatus & aStatus)
{
........
  if (nsnull != mEventCallback) {
    aStatus = (*mEventCallback)(event);
  }
 
  // Dispatch to event listener if event was not consumed
  if ((aStatus != nsEventStatus_eIgnore) && (nsnull != mEventListener)) {
    aStatus = mEventListener->ProcessEvent(*event);
  }


まずmEventCallbackが何者なのか確認。

xpはcorss-platform.コメントにも

mozilla/widget/src/xpwidgets/nsBaseWidget.h

/**
 * Common widget implementation used as base class for native
 * or crossplatform implementations of Widgets. 
 * All cross-platform behavior that all widgets need to implement 
 * should be placed in this class. 
 * (Note: widget implementations are not required to use this
 * class, but it gives them a head start.)
 */

って書いてあった。

mozilla/widget/src/xpwidgets/nsBaseWidget.cpp

void nsBaseWidget::BaseCreate(nsIWidget *aParent,
                              const nsRect &aRect,
                              EVENT_CALLBACK aHandleEventFunction,
                              nsIDeviceContext *aContext,
                              nsIAppShell *aAppShell,
                              nsIToolkit *aToolkit,
                              nsWidgetInitData *aInitData)
{
........
  // save the event callback function
  mEventCallback = aHandleEventFunction;

<||

**~/mozilla/widget/src/windows/nsWindow.cpp
>|cpp|
//WINOLEAPI oleStatus;
//-------------------------------------------------------------------------
//
// Utility method for implementing both Create(nsIWidget ...) and
// Create(nsNativeWidget...)
//-------------------------------------------------------------------------

nsresult
nsWindow::StandardWindowCreate(nsIWidget *aParent,
                               const nsRect &aRect,
                               EVENT_CALLBACK aHandleEventFunction,
                               nsIDeviceContext *aContext,
                               nsIAppShell *aAppShell,
                               nsIToolkit *aToolkit,
                               nsWidgetInitData *aInitData,
                               nsNativeWidget aNativeParent)
{
.......
  BaseCreate(baseParent, aRect, aHandleEventFunction, aContext,
             aAppShell, aToolkit, aInitData);

EVENT_CALLBACK aHandleEventFunction
これ。

~/mozilla/embedding/browser/webBrowser/nsWebBrowser.cpp

NS_IMETHODIMP nsWebBrowser::Create()
{
   NS_ENSURE_STATE(!mDocShell && (mParentNativeWindow || mParentWidget));

   NS_ENSURE_SUCCESS(EnsureDocShellTreeOwner(), NS_ERROR_FAILURE);

   nsCOMPtr<nsIWidget> docShellParentWidget(mParentWidget);
   if(!mParentWidget) // We need to create a widget
      {
      // Create the widget
      NS_ENSURE_TRUE(mInternalWidget = do_CreateInstance(kChildCID), NS_ERROR_FAILURE);

      docShellParentWidget = mInternalWidget;
      nsWidgetInitData  widgetInit;

      widgetInit.clipChildren = PR_TRUE;
      widgetInit.mContentType = (mContentType == typeChrome ||
        mContentType == typeChromeWrapper)? eContentTypeUI: eContentTypeContent;

      widgetInit.mWindowType = eWindowType_child;
      nsRect bounds(mInitInfo->x, mInitInfo->y, mInitInfo->cx, mInitInfo->cy);

      mInternalWidget->SetClientData(static_cast<nsWebBrowser *>(this));
      mInternalWidget->Create(mParentNativeWindow, bounds, nsWebBrowser::HandleEvent,
                              nsnull, nsnull, nsnull, &widgetInit);
      }

なんかNS_PAINTだけがハンドルされているだけだった....なんだこれ。

上に戻って今度はmEventListener.

~/mozilla/widget/src/xpwidgets/nsBaseWidget.cpp

NS_METHOD nsBaseWidget::AddEventListener(nsIEventListener * aListener)
{
  NS_PRECONDITION(mEventListener == nsnull, "Null mouse listener");
  NS_IF_RELEASE(mEventListener);
  NS_ADDREF(aListener);
  mEventListener = aListener;
  return NS_OK;
}

だめだわかんない。だれもaddしてない。


以下関係ないところ。








NS_IMETHODIMP
nsGlobalWindow::DispatchEvent(nsIDOMEvent* aEvent, PRBool* _retval)
{
  FORWARD_TO_INNER(DispatchEvent, (aEvent, _retval), NS_OK);

  if (!mDoc) {
    return NS_ERROR_FAILURE;
  }

  // Obtain a presentation shell
  nsIPresShell *shell = mDoc->GetPrimaryShell();
  nsCOMPtr<nsPresContext> presContext;
  if (shell) {
    // Retrieve the context
    presContext = shell->GetPresContext();
  }

  nsEventStatus status = nsEventStatus_eIgnore;
  nsresult rv =
    nsEventDispatcher::DispatchDOMEvent(GetOuterWindow(), nsnull, aEvent,
                                        presContext, &status);

  *_retval = (status != nsEventStatus_eConsumeNoDefault);
  return rv;
}
nsresult
nsGlobalWindow::DispatchDOMEvent(nsEvent* aEvent,
                                 nsIDOMEvent* aDOMEvent,
                                 nsPresContext* aPresContext,
                                 nsEventStatus* aEventStatus)
{
  return
    nsEventDispatcher::DispatchDOMEvent(static_cast<nsPIDOMWindow*>(this),
                                       aEvent, aDOMEvent, aPresContext,
                                       aEventStatus);
}

~/mozilla/content/events/src/nsEventDispatcher.cpp

/* static */ nsresult
nsEventDispatcher::DispatchDOMEvent(nsISupports* aTarget,
                                    nsEvent* aEvent,
                                    nsIDOMEvent* aDOMEvent,
                                    nsPresContext* aPresContext,
                                    nsEventStatus* aEventStatus)
{
  if (aDOMEvent) {
    nsCOMPtr<nsIPrivateDOMEvent> privEvt(do_QueryInterface(aDOMEvent));
    if (privEvt) {
      nsEvent* innerEvent = nsnull;
      privEvt->GetInternalNSEvent(&innerEvent);
      NS_ENSURE_TRUE(innerEvent, NS_ERROR_ILLEGAL_VALUE);

      PRBool trusted;
      nsCOMPtr<nsIDOMNSEvent> nsevent(do_QueryInterface(privEvt));

      nsevent->GetIsTrusted(&trusted);

      if (!trusted) {
        //Check security state to determine if dispatcher is trusted
        privEvt->SetTrusted(nsContentUtils::IsCallerTrustedForWrite());
      }

      return nsEventDispatcher::Dispatch(aTarget, aPresContext, innerEvent,
                                         aDOMEvent, aEventStatus);
    }
  } else if (aEvent) {
    return nsEventDispatcher::Dispatch(aTarget, aPresContext, aEvent,
                                       aDOMEvent, aEventStatus);
  }
  return NS_ERROR_ILLEGAL_VALUE;
}
/* static */ nsresult
nsEventDispatcher::Dispatch(nsISupports* aTarget,
                            nsPresContext* aPresContext,
                            nsEvent* aEvent,
                            nsIDOMEvent* aDOMEvent,
                            nsEventStatus* aEventStatus,
                            nsDispatchingCallback* aCallback)
{
.......
    if (NS_SUCCEEDED(rv)) {
      // Event target chain is created. Handle the chain.
      nsEventChainPostVisitor postVisitor(preVisitor);
      rv = topEtci->HandleEventTargetChain(postVisitor,
                                           NS_EVENT_FLAG_BUBBLE |
                                           NS_EVENT_FLAG_CAPTURE,
                                           aCallback);

nsresult
nsEventTargetChainItem::HandleEventTargetChain(nsEventChainPostVisitor& aVisitor, PRUint32 aFlags,
                                               nsDispatchingCallback* aCallback)
{
  // Save the target so that it can be restored later.
  nsCOMPtr<nsISupports> firstTarget = aVisitor.mEvent->target;

  // Capture
  nsEventTargetChainItem* item = this;
  aVisitor.mEvent->flags |= NS_EVENT_FLAG_CAPTURE;
  aVisitor.mEvent->flags &= ~NS_EVENT_FLAG_BUBBLE;
  while (item->mChild) {
    if ((!(aVisitor.mEvent->flags & NS_EVENT_FLAG_NO_CONTENT_DISPATCH) ||
         item->ForceContentDispatch()) &&
        !(aVisitor.mEvent->flags & NS_EVENT_FLAG_STOP_DISPATCH)) {
      item->HandleEvent(aVisitor, aFlags & NS_EVENT_CAPTURE_MASK);
    }
    
......

    item = item->mChild;
  }
  // Target
  aVisitor.mEvent->flags |= NS_EVENT_FLAG_BUBBLE;
  if (!(aVisitor.mEvent->flags & NS_EVENT_FLAG_STOP_DISPATCH) &&
      (!(aVisitor.mEvent->flags & NS_EVENT_FLAG_NO_CONTENT_DISPATCH) ||
       item->ForceContentDispatch())) {
    // FIXME Should use aFlags & NS_EVENT_BUBBLE_MASK because capture phase
    //       event listeners should not be fired. But it breaks at least
    //       <xul:dialog>'s buttons. Bug 235441.
    item->HandleEvent(aVisitor, aFlags);
  }
  if (aFlags & NS_EVENT_FLAG_SYSTEM_EVENT) {
    item->PostHandleEvent(aVisitor);
  }

表側とつながった。子へと伝達してるところだからここはキャプチャか。TargetのHandleEventyしたあとちゃんとバブルのループもある。

nsresult
nsEventTargetChainItem::HandleEvent(nsEventChainPostVisitor& aVisitor,
                                    PRUint32 aFlags)
{
  nsCOMPtr<nsIEventListenerManager> lm;
  mTarget->GetListenerManager(PR_FALSE, getter_AddRefs(lm));
  aVisitor.mEvent->currentTarget = CurrentTarget()->GetTargetForDOMEvent();
  if (lm && aVisitor.mEvent->currentTarget) {
    lm->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent, &aVisitor.mDOMEvent,
                    aVisitor.mEvent->currentTarget, aFlags,
                    &aVisitor.mEventStatus);
    aVisitor.mEvent->currentTarget = nsnull;
  }
  return NS_OK;
}

最後は結局nsIEventListenerManager経由。

mozilla/content/events/public/nsIEventListenerManager.h
っていうのがあってnsGlobalWindowが実装してる。

~/mozilla/widget/src/windows/nsWindow.h のcontributorsにふたりの日本人名を発見。

~/mozilla/content/events/src/nsEventListenerManager.cpp

/**
* Causes a check for event listeners and processing by them if they exist.
* @param an event listener
*/

nsresult
nsEventListenerManager::HandleEvent(nsPresContext* aPresContext,
                                    nsEvent* aEvent, nsIDOMEvent** aDOMEvent,
                                    nsISupports* aCurrentTarget,
                                    PRUint32 aFlags,
                                    nsEventStatus* aEventStatus)
{
......
          if (*aDOMEvent) {
            if (useTypeInterface) {
              DispatchToInterface(*aDOMEvent, ls->mListener,
                                  dispData->method, *typeData->iid);
            } else if (useGenericInterface) {
              HandleEventSubType(ls, ls->mListener, *aDOMEvent,
                                 aCurrentTarget, aFlags);
            }
          }
......

UI系のイベントでなければぜんぶDOMEventになるっぽい。

nsresult
nsEventListenerManager::HandleEventSubType(nsListenerStruct* aListenerStruct,
                                           nsIDOMEventListener* aListener,
                                           nsIDOMEvent* aDOMEvent,
                                           nsISupports* aCurrentTarget,
                                           PRUint32 aPhaseFlags)
{
  nsresult result = NS_OK;

  // If this is a script handler and we haven't yet
  // compiled the event handler itself
  if ((aListenerStruct->mFlags & NS_PRIV_EVENT_FLAG_SCRIPT) &&
      aListenerStruct->mHandlerIsString) {
    nsCOMPtr<nsIJSEventListener> jslistener = do_QueryInterface(aListener);
    if (jslistener) {
      nsAutoString eventString;
      if (NS_SUCCEEDED(aDOMEvent->GetType(eventString))) {
        nsCOMPtr<nsIAtom> atom = do_GetAtom(NS_LITERAL_STRING("on") + eventString);

        result = CompileEventHandlerInternal(jslistener->GetEventContext(),
                                             jslistener->GetEventScope(),
                                             jslistener->GetEventTarget(),
                                             atom, aListenerStruct,
                                             aCurrentTarget);
      }
    }
  }

  // nsCxPusher will automatically push and pop the current cx onto the
  // context stack
  nsCxPusher pusher(aCurrentTarget);

  if (NS_SUCCEEDED(result)) {
    // nsIDOMEvent::currentTarget is set in nsEventDispatcher.
    result = aListener->HandleEvent(aDOMEvent);
  }

  return result;
}

あれ、けっきょくnsIJSEventListener::HandleEventになるってことは、OSからメッセージうけとったスレッドがjs実行することになってたきが....

~/mozilla/dom/src/events/nsJSEventListener.cpp

nsresult
nsJSEventListener::HandleEvent(nsIDOMEvent* aEvent)
{
........
  nsScriptObjectHolder funcval(mContext);
  rv = mContext->GetBoundEventHandler(mTarget, mScopeObject, atomName,
                                      funcval);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!funcval)
    return NS_OK;

  PRBool handledScriptError = PR_FALSE;
  if (eventString.EqualsLiteral("onerror")) {
    nsCOMPtr<nsIPrivateDOMEvent> priv(do_QueryInterface(aEvent));
    NS_ENSURE_TRUE(priv, NS_ERROR_UNEXPECTED);
........
  nsCOMPtr<nsIVariant> vrv;
  rv = mContext->CallEventHandler(mTarget, mScopeObject, funcval, iargv,
                                  getter_AddRefs(vrv));

関係ないけどこれ気になる....
extensions/python/dom/src/nsPyContext.cpp
Breaking the grip JS has on the DOM - MozillaWiki
これかな。

nsScriptObjectHolderの実体を定義してるクラスが見つかんない。
でもCallEventHandlerを持ってるクラスがnsJSEnvironmentのほかにないっぽい。

~/mozilla/dom/src/base/nsJSEnvironment.cpp

nsresult
nsJSContext::CallEventHandler(nsISupports* aTarget, void *aScope, void *aHandler,
                              nsIArray *aargv, nsIVariant **arv)
{
.....
    jsval funval = OBJECT_TO_JSVAL(aHandler);
    JSAutoRequest ar(mContext);
    PRBool ok = ::JS_CallFunctionValue(mContext, target,
                                       funval, argc, argv, &rval);

けっきょく最後に直接呼んじゃう。
OSからメッセージを受け取ったスレッドが直接呼んでるってことは

  1. 一番はじめの::Runの中からイベントがわたってる
  2. そもそもなんか勘違いしてる

どっちか。
はじめにもどって検証。


nsWindow::ProcessMessage
より一つ前があった。

//-------------------------------------------------------------------------
//
// the nsWindow procedure for all nsWindows in this toolkit
//
//-------------------------------------------------------------------------
LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
......

  if (nsnull != someWindow) {
    LRESULT retValue;
    if (PR_TRUE == someWindow->ProcessMessage(msg, wParam, lParam, &retValue)) {
      return retValue;
    }
  }

ということはOSからメッセージを受け取るスレッドと、ContentSinkのスレッドは別ってことか。
でもやっぱりそれってどうやって同期取ってるのかわかんない。

つまったところで休憩。









function test() {
  window.str = undefined;

  var req = new XMLHttpRequest();
  req.open("GET", "/", true);
  req.send("");

  req.onreadystatechange = function () {
    if (req.readyState == 4) {
      // only if "OK"
      console.log(req.statusText);
    }
  };

  window.str = prompt('hoge?');
}

こういうのもちゃんとpromptを抜ける。

ProxyObjectつかってるところはほとんどない。