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からメッセージを受け取ったスレッドが直接呼んでるってことは
- 一番はじめの::Runの中からイベントがわたってる
- そもそもなんか勘違いしてる
どっちか。
はじめにもどって検証。
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つかってるところはほとんどない。