OS由来メッセージのディスパッチのされ方

2007.11.18追記

大雑把に書いたら

nsThread::ThreadFunc() {
    while (!self->ShuttingDown()) {
        event = eventQueue.GetEvent();
        event->run();

        if ( sGlobalObserver )
            sGlobalObserver->OnProcessNextEvent();
    }
}

nsBaseAppShell::OnProcessNextEvent() {
    ::PeekMessageW(&msg);
    ::TranslateMessage(&msg);
    ::DispatchMessageW(&msg);
}

こんなかんじ。

2008.1.11追記

OSXの場合はOSネイティブのrun loopをメインにしてまわる。CFRunLoopAddSourceでfirefox由来のイベントが発生したときは
ProcessGeckoEventsが呼ばれるようにしている。
firefox由来のイベントが起こることに
nsAppShell::ScheduleNativeEventCallbackを呼び出して

  ::CFRunLoopSourceSignal(mCFRunLoopSource);
  ::CFRunLoopWakeUp(mCFRunLoop);

でdispatchしてもらう。



だれがどう実行してるかがようやくわかったので、後はどの段階でどうやって投入するかが問題。


関係ないけど。

~/mozilla/content/base/src/nsDOMParser.cpp

NS_IMETHODIMP
nsDOMParser::ParseFromStream(nsIInputStream *stream,
                             const char *charset,
                             PRInt32 contentLength,
                             const char *contentType,
                             nsIDOMDocument **aResult)
{
.... snip ....
  rv = listener->OnStopRequest(parserChannel, nsnull, status);
  // Failure returned from OnStopRequest does not affect the final status of
  // the channel, so we do not need to call Cancel(rv) as we do above.

  if (NS_FAILED(rv)) {
    return NS_ERROR_FAILURE;
  }

  // Process events until we receive a load, abort, or error event for the
  // document object.  That event may have already fired.

  nsIThread *thread = NS_GetCurrentThread();
  while (mLoopingForSyncLoad) {
    if (!NS_ProcessNextEvent(thread))
      break;
  }

  domDocument.swap(*aResult);

  return NS_OK;
}

nsRunnableのサブクラスを探してたらこんなの発見。UIスレッドのイベントキューを管理しているsingletonで
各platformのベースクラス。

~/mozilla/widget/src/xpwidgets/nsBaseAppShell.h

/**
 * A singleton that manages the UI thread's event queue.  Subclass this class
 * to enable platform-specific event queue support.
 */
class nsBaseAppShell : public nsIAppShell, public nsIThreadObserver,
                       public nsIObserver
{

~/mozilla/widget/src/windows/nsAppShell.h

/**
 * Native Win32 Application shell wrapper
 */
class nsAppShell : public nsBaseAppShell
{
public:
  nsAppShell() : mEventWnd(NULL) {}

  nsresult Init();

そのwin32版。nsBaseAppShellを継承してる。

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

PRBool
nsAppShell::ProcessNextNativeEvent(PRBool mayWait)
{
  PRBool gotMessage = PR_FALSE;

  do {
    MSG msg;
    // Give priority to system messages (in particular keyboard, mouse, timer,
    // and paint messages).
    if (PeekKeyAndIMEMessage(&msg, NULL) ||
        ::PeekMessageW(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) ||
        ::PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
      gotMessage = PR_TRUE;
      if (msg.message == WM_QUIT) {
        Exit();
      } else {
        ::TranslateMessage(&msg);
        ::DispatchMessageW(&msg);
      }
    } else if (mayWait) {
      // Block and wait for any posted application message
      ::WaitMessage();
    }
  } while (!gotMessage && mayWait);

  return gotMessage;
}

コードを見ると ProcessNextNativeEvent で DispatchMessageW してる。
MFCでしか書いたことがないので知らなかったよー....
WindowsAPI Programming  第1章 〜スケルトンプログラム〜 その2
によるとDispatchMessageWでWindowProcが呼ばれてディスパッチされるとのこと。
OSからのメッセージはプロセスのメッセージキューにたまって、DispatchMessageで取り出してWindowProcに渡してるってことだ。考えたらあたり舞うだけどそこがわかんなかった。

こいつがどこから呼ばれてるかを確かめれば完了。

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

PRBool
nsBaseAppShell::DoProcessNextNativeEvent(PRBool mayWait)
{
  // The next native event to be processed may trigger our NativeEventCallback,
  // in which case we do not want it to process any thread events since we'll
  // do that when this function returns.
  //
  // If the next native event is not our NativeEventCallback, then we may end
  // up recursing into this function.
  //
  // However, if the next native event is not our NativeEventCallback, but it
  // results in another native event loop, then our NativeEventCallback could
  // fire and it will see mProcessNextNativeEvent as true.
  //
  PRBool prevVal = mProcessingNextNativeEvent;

  mProcessingNextNativeEvent = PR_TRUE;
  PRBool result = ProcessNextNativeEvent(mayWait);
  mProcessingNextNativeEvent = prevVal;
  return result;
}

DoProcessNextNativeEventがどこから呼ばれてるかを探す。

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

// Called from the main thread
NS_IMETHODIMP
nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, PRBool mayWait,
                                   PRUint32 recursionDepth)
{
  PRIntervalTime start = PR_IntervalNow();
  PRIntervalTime limit = THREAD_EVENT_STARVATION_LIMIT;

  if (mFavorPerf <= 0 && start > mSwitchTime + mStarvationDelay) {
    // Favor pending native events
    PRIntervalTime now = start;
    PRBool keepGoing;
    do {
      mLastNativeEventTime = now;
      keepGoing = DoProcessNextNativeEvent(PR_FALSE);
    } while (keepGoing && ((now = PR_IntervalNow()) - start) < limit);
  } else {
    // Avoid starving native events completely when in performance mode
    if (start - mLastNativeEventTime > limit) {
      mLastNativeEventTime = start;
      DoProcessNextNativeEvent(PR_FALSE);
    }
  }

  // When mayWait is true, we need to make sure that there is an event in the
  // thread's event queue before we return.  Otherwise, the thread will block
  // on its event queue waiting for an event.
  PRBool needEvent = mayWait;

  while (!NS_HasPendingEvents(thr)) {
    // If we have been asked to exit from Run, then we should not wait for
    // events to process.
    if (mExiting && mayWait)
      mayWait = PR_FALSE;

    mLastNativeEventTime = PR_IntervalNow();
    if (!DoProcessNextNativeEvent(mayWait) || !mayWait)
      break;
  }

  // Make sure that the thread event queue does not block on its monitor, as
  // it normally would do if it did not have any pending events.  To avoid
  // that, we simply insert a dummy event into its queue during shutdown.
  if (needEvent && !NS_HasPendingEvents(thr)) {
    if (!mDummyEvent)
      mDummyEvent = new nsRunnable();
    thr->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL);
  }

  return NS_OK;
}

OnProcessNextEventは::RunでNS_ProcessNextEventが呼ばれるたびにコールバックで呼ばれる関数。

RunのループでFirefox独自のイベントを処理しつつ、そのとき同時にOSからきてるメッセージも処理してるというわけでした。

Firefoxのイベントキュー処理とOSからのメッセージ処理をどうやって同じスレッドで両立させているのかがずっとわかんなかったけどわかってみればきわめて単純。