diff options
Diffstat (limited to 'dom/events/Event.cpp')
-rw-r--r-- | dom/events/Event.cpp | 1305 |
1 files changed, 1305 insertions, 0 deletions
diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp new file mode 100644 index 0000000000..a85a0d66b2 --- /dev/null +++ b/dom/events/Event.cpp @@ -0,0 +1,1305 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AccessCheck.h" +#include "base/basictypes.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/ShadowRoot.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/InternalMutationEvent.h" +#include "mozilla/dom/Performance.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TouchEvents.h" +#include "nsContentUtils.h" +#include "nsCOMPtr.h" +#include "nsDeviceContext.h" +#include "nsError.h" +#include "nsGlobalWindow.h" +#include "nsIFrame.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIPresShell.h" +#include "nsIScrollableFrame.h" +#include "nsJSEnvironment.h" +#include "nsLayoutUtils.h" +#include "nsPIWindowRoot.h" +#include "WorkerPrivate.h" + +namespace mozilla { +namespace dom { + +namespace workers { +extern bool IsCurrentThreadRunningChromeWorker(); +} // namespace workers + +static char *sPopupAllowedEvents; + +static bool sReturnHighResTimeStamp = false; +static bool sReturnHighResTimeStampIsSet = false; + +Event::Event(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) +{ + ConstructorInit(aOwner, aPresContext, aEvent); +} + +Event::Event(nsPIDOMWindowInner* aParent) +{ + ConstructorInit(nsGlobalWindow::Cast(aParent), nullptr, nullptr); +} + +void +Event::ConstructorInit(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) +{ + SetOwner(aOwner); + mIsMainThreadEvent = NS_IsMainThread(); + + if (mIsMainThreadEvent && !sReturnHighResTimeStampIsSet) { + Preferences::AddBoolVarCache(&sReturnHighResTimeStamp, + "dom.event.highrestimestamp.enabled", + sReturnHighResTimeStamp); + sReturnHighResTimeStampIsSet = true; + } + + mPrivateDataDuplicated = false; + mWantsPopupControlCheck = false; + + if (aEvent) { + mEvent = aEvent; + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + /* + A derived class might want to allocate its own type of aEvent + (derived from WidgetEvent). To do this, it should take care to pass + a non-nullptr aEvent to this ctor, e.g.: + + FooEvent::FooEvent(..., WidgetEvent* aEvent) + : Event(..., aEvent ? aEvent : new WidgetEvent()) + + Then, to override the mEventIsInternal assignments done by the + base ctor, it should do this in its own ctor: + + FooEvent::FooEvent(..., WidgetEvent* aEvent) + ... + { + ... + if (aEvent) { + mEventIsInternal = false; + } + else { + mEventIsInternal = true; + } + ... + } + */ + mEvent = new WidgetEvent(false, eVoidEvent); + mEvent->mTime = PR_Now(); + } + + InitPresContextData(aPresContext); +} + +void +Event::InitPresContextData(nsPresContext* aPresContext) +{ + mPresContext = aPresContext; + // Get the explicit original target (if it's anonymous make it null) + { + nsCOMPtr<nsIContent> content = GetTargetFromFrame(); + mExplicitOriginalTarget = content; + if (content && content->IsInAnonymousSubtree()) { + mExplicitOriginalTarget = nullptr; + } + } +} + +Event::~Event() +{ + NS_ASSERT_OWNINGTHREAD(Event); + + if (mEventIsInternal && mEvent) { + delete mEvent; + } +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Event) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIDOMEvent) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Event) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Event) + +NS_IMPL_CYCLE_COLLECTION_CLASS(Event) + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Event) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Event) + if (tmp->mEventIsInternal) { + tmp->mEvent->mTarget = nullptr; + tmp->mEvent->mCurrentTarget = nullptr; + tmp->mEvent->mOriginalTarget = nullptr; + switch (tmp->mEvent->mClass) { + case eMouseEventClass: + case eMouseScrollEventClass: + case eWheelEventClass: + case eSimpleGestureEventClass: + case ePointerEventClass: + tmp->mEvent->AsMouseEventBase()->relatedTarget = nullptr; + break; + case eDragEventClass: { + WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent(); + dragEvent->mDataTransfer = nullptr; + dragEvent->relatedTarget = nullptr; + break; + } + case eClipboardEventClass: + tmp->mEvent->AsClipboardEvent()->mClipboardData = nullptr; + break; + case eMutationEventClass: + tmp->mEvent->AsMutationEvent()->mRelatedNode = nullptr; + break; + case eFocusEventClass: + tmp->mEvent->AsFocusEvent()->mRelatedTarget = nullptr; + break; + default: + break; + } + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPresContext); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mExplicitOriginalTarget); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner); + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Event) + if (tmp->mEventIsInternal) { + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mCurrentTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mOriginalTarget) + switch (tmp->mEvent->mClass) { + case eMouseEventClass: + case eMouseScrollEventClass: + case eWheelEventClass: + case eSimpleGestureEventClass: + case ePointerEventClass: + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget"); + cb.NoteXPCOMChild(tmp->mEvent->AsMouseEventBase()->relatedTarget); + break; + case eDragEventClass: { + WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent(); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mDataTransfer"); + cb.NoteXPCOMChild(dragEvent->mDataTransfer); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget"); + cb.NoteXPCOMChild(dragEvent->relatedTarget); + break; + } + case eClipboardEventClass: + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mClipboardData"); + cb.NoteXPCOMChild(tmp->mEvent->AsClipboardEvent()->mClipboardData); + break; + case eMutationEventClass: + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mRelatedNode"); + cb.NoteXPCOMChild(tmp->mEvent->AsMutationEvent()->mRelatedNode); + break; + case eFocusEventClass: + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mRelatedTarget"); + cb.NoteXPCOMChild(tmp->mEvent->AsFocusEvent()->mRelatedTarget); + break; + default: + break; + } + } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExplicitOriginalTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + + +JSObject* +Event::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return WrapObjectInternal(aCx, aGivenProto); +} + +JSObject* +Event::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return EventBinding::Wrap(aCx, this, aGivenProto); +} + +bool +Event::IsChrome(JSContext* aCx) const +{ + return mIsMainThreadEvent ? + xpc::AccessCheck::isChrome(js::GetContextCompartment(aCx)) : + mozilla::dom::workers::IsCurrentThreadRunningChromeWorker(); +} + +// nsIDOMEventInterface +NS_IMETHODIMP +Event::GetType(nsAString& aType) +{ + if (!mIsMainThreadEvent || !mEvent->mSpecifiedEventTypeString.IsEmpty()) { + aType = mEvent->mSpecifiedEventTypeString; + return NS_OK; + } + const char* name = GetEventName(mEvent->mMessage); + + if (name) { + CopyASCIItoUTF16(name, aType); + return NS_OK; + } else if (mEvent->mMessage == eUnidentifiedEvent && + mEvent->mSpecifiedEventType) { + // Remove "on" + aType = Substring(nsDependentAtomString(mEvent->mSpecifiedEventType), 2); + mEvent->mSpecifiedEventTypeString = aType; + return NS_OK; + } + + aType.Truncate(); + return NS_OK; +} + +static EventTarget* +GetDOMEventTarget(nsIDOMEventTarget* aTarget) +{ + return aTarget ? aTarget->GetTargetForDOMEvent() : nullptr; +} + +EventTarget* +Event::GetTarget() const +{ + return GetDOMEventTarget(mEvent->mTarget); +} + +NS_IMETHODIMP +Event::GetTarget(nsIDOMEventTarget** aTarget) +{ + NS_IF_ADDREF(*aTarget = GetTarget()); + return NS_OK; +} + +EventTarget* +Event::GetCurrentTarget() const +{ + return GetDOMEventTarget(mEvent->mCurrentTarget); +} + +NS_IMETHODIMP +Event::GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget) +{ + NS_IF_ADDREF(*aCurrentTarget = GetCurrentTarget()); + return NS_OK; +} + +// +// Get the actual event target node (may have been retargeted for mouse events) +// +already_AddRefed<nsIContent> +Event::GetTargetFromFrame() +{ + if (!mPresContext) { return nullptr; } + + // Get the mTarget frame (have to get the ESM first) + nsIFrame* targetFrame = mPresContext->EventStateManager()->GetEventTarget(); + if (!targetFrame) { return nullptr; } + + // get the real content + nsCOMPtr<nsIContent> realEventContent; + targetFrame->GetContentForEvent(mEvent, getter_AddRefs(realEventContent)); + return realEventContent.forget(); +} + +EventTarget* +Event::GetExplicitOriginalTarget() const +{ + if (mExplicitOriginalTarget) { + return mExplicitOriginalTarget; + } + return GetTarget(); +} + +NS_IMETHODIMP +Event::GetExplicitOriginalTarget(nsIDOMEventTarget** aRealEventTarget) +{ + NS_IF_ADDREF(*aRealEventTarget = GetExplicitOriginalTarget()); + return NS_OK; +} + +EventTarget* +Event::GetOriginalTarget() const +{ + if (mEvent->mOriginalTarget) { + return GetDOMEventTarget(mEvent->mOriginalTarget); + } + + return GetTarget(); +} + +NS_IMETHODIMP +Event::GetOriginalTarget(nsIDOMEventTarget** aOriginalTarget) +{ + NS_IF_ADDREF(*aOriginalTarget = GetOriginalTarget()); + return NS_OK; +} + +EventTarget* +Event::GetComposedTarget() const +{ + EventTarget* et = GetOriginalTarget(); + nsCOMPtr<nsIContent> content = do_QueryInterface(et); + if (!content) { + return et; + } + nsIContent* nonChrome = content->FindFirstNonChromeOnlyAccessContent(); + return nonChrome ? + static_cast<EventTarget*>(nonChrome) : + static_cast<EventTarget*>(content->GetComposedDoc()); +} + +NS_IMETHODIMP_(void) +Event::SetTrusted(bool aTrusted) +{ + mEvent->mFlags.mIsTrusted = aTrusted; +} + +bool +Event::Init(mozilla::dom::EventTarget* aGlobal) +{ + if (!mIsMainThreadEvent) { + return nsContentUtils::ThreadsafeIsCallerChrome(); + } + bool trusted = false; + nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(aGlobal); + if (w) { + nsCOMPtr<nsIDocument> d = w->GetExtantDoc(); + if (d) { + trusted = nsContentUtils::IsChromeDoc(d); + nsIPresShell* s = d->GetShell(); + if (s) { + InitPresContextData(s->GetPresContext()); + } + } + } + return trusted; +} + +// static +already_AddRefed<Event> +Event::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const EventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr<mozilla::dom::EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<Event> e = new Event(t, nullptr, nullptr); + bool trusted = e->Init(t); + e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable); + e->SetTrusted(trusted); + e->SetComposed(aParam.mComposed); + return e.forget(); +} + +uint16_t +Event::EventPhase() const +{ + // Note, remember to check that this works also + // if or when Bug 235441 is fixed. + if ((mEvent->mCurrentTarget && + mEvent->mCurrentTarget == mEvent->mTarget) || + mEvent->mFlags.InTargetPhase()) { + return nsIDOMEvent::AT_TARGET; + } + if (mEvent->mFlags.mInCapturePhase) { + return nsIDOMEvent::CAPTURING_PHASE; + } + if (mEvent->mFlags.mInBubblingPhase) { + return nsIDOMEvent::BUBBLING_PHASE; + } + return nsIDOMEvent::NONE; +} + +NS_IMETHODIMP +Event::GetEventPhase(uint16_t* aEventPhase) +{ + *aEventPhase = EventPhase(); + return NS_OK; +} + +NS_IMETHODIMP +Event::GetBubbles(bool* aBubbles) +{ + *aBubbles = Bubbles(); + return NS_OK; +} + +NS_IMETHODIMP +Event::GetCancelable(bool* aCancelable) +{ + *aCancelable = Cancelable(); + return NS_OK; +} + +NS_IMETHODIMP +Event::GetTimeStamp(uint64_t* aTimeStamp) +{ + *aTimeStamp = mEvent->mTime; + return NS_OK; +} + +NS_IMETHODIMP +Event::StopPropagation() +{ + mEvent->StopPropagation(); + return NS_OK; +} + +NS_IMETHODIMP +Event::StopImmediatePropagation() +{ + mEvent->StopImmediatePropagation(); + return NS_OK; +} + +NS_IMETHODIMP +Event::StopCrossProcessForwarding() +{ + mEvent->StopCrossProcessForwarding(); + return NS_OK; +} + +NS_IMETHODIMP +Event::GetIsTrusted(bool* aIsTrusted) +{ + *aIsTrusted = IsTrusted(); + return NS_OK; +} + +NS_IMETHODIMP +Event::PreventDefault() +{ + // This method is called only from C++ code which must handle default action + // of this event. So, pass true always. + PreventDefaultInternal(true); + return NS_OK; +} + +void +Event::PreventDefault(JSContext* aCx) +{ + MOZ_ASSERT(aCx, "JS context must be specified"); + + // Note that at handling default action, another event may be dispatched. + // Then, JS in content mey be call preventDefault() + // even in the event is in system event group. Therefore, don't refer + // mInSystemGroup here. + PreventDefaultInternal(IsChrome(aCx)); +} + +void +Event::PreventDefaultInternal(bool aCalledByDefaultHandler) +{ + if (!mEvent->mFlags.mCancelable) { + return; + } + if (mEvent->mFlags.mInPassiveListener) { + nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(mOwner)); + if (win) { + if (nsIDocument* doc = win->GetExtantDoc()) { + nsString type; + GetType(type); + const char16_t* params[] = { type.get() }; + doc->WarnOnceAbout(nsIDocument::ePreventDefaultFromPassiveListener, + false, params, ArrayLength(params)); + } + } + return; + } + + mEvent->PreventDefault(aCalledByDefaultHandler); + + if (!IsTrusted()) { + return; + } + + WidgetDragEvent* dragEvent = mEvent->AsDragEvent(); + if (!dragEvent) { + return; + } + + nsCOMPtr<nsINode> node = do_QueryInterface(mEvent->mCurrentTarget); + if (!node) { + nsCOMPtr<nsPIDOMWindowOuter> win = + do_QueryInterface(mEvent->mCurrentTarget); + if (!win) { + return; + } + node = win->GetExtantDoc(); + } + if (!nsContentUtils::IsChromeDoc(node->OwnerDoc())) { + dragEvent->mDefaultPreventedOnContent = true; + } +} + +void +Event::SetEventType(const nsAString& aEventTypeArg) +{ + if (mIsMainThreadEvent) { + mEvent->mSpecifiedEventTypeString.Truncate(); + mEvent->mSpecifiedEventType = + nsContentUtils::GetEventMessageAndAtom(aEventTypeArg, mEvent->mClass, + &(mEvent->mMessage)); + mEvent->SetDefaultComposed(); + } else { + mEvent->mSpecifiedEventType = nullptr; + mEvent->mMessage = eUnidentifiedEvent; + mEvent->mSpecifiedEventTypeString = aEventTypeArg; + mEvent->SetComposed(aEventTypeArg); + } + mEvent->SetDefaultComposedInNativeAnonymousContent(); +} + +void +Event::InitEvent(const nsAString& aEventTypeArg, + bool aCanBubbleArg, + bool aCancelableArg) +{ + // Make sure this event isn't already being dispatched. + NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); + + if (IsTrusted()) { + // Ensure the caller is permitted to dispatch trusted DOM events. + if (!nsContentUtils::ThreadsafeIsCallerChrome()) { + SetTrusted(false); + } + } + + SetEventType(aEventTypeArg); + + mEvent->mFlags.mBubbles = aCanBubbleArg; + mEvent->mFlags.mCancelable = aCancelableArg; + + mEvent->mFlags.mDefaultPrevented = false; + mEvent->mFlags.mDefaultPreventedByContent = false; + mEvent->mFlags.mDefaultPreventedByChrome = false; + mEvent->mFlags.mPropagationStopped = false; + mEvent->mFlags.mImmediatePropagationStopped = false; + + // Clearing the old targets, so that the event is targeted correctly when + // re-dispatching it. + mEvent->mTarget = nullptr; + mEvent->mOriginalTarget = nullptr; +} + +NS_IMETHODIMP +Event::DuplicatePrivateData() +{ + NS_ASSERTION(mEvent, "No WidgetEvent for Event duplication!"); + if (mEventIsInternal) { + return NS_OK; + } + + mEvent = mEvent->Duplicate(); + mPresContext = nullptr; + mEventIsInternal = true; + mPrivateDataDuplicated = true; + + return NS_OK; +} + +NS_IMETHODIMP +Event::SetTarget(nsIDOMEventTarget* aTarget) +{ + mEvent->mTarget = do_QueryInterface(aTarget); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +Event::IsDispatchStopped() +{ + return mEvent->PropagationStopped(); +} + +NS_IMETHODIMP_(WidgetEvent*) +Event::WidgetEventPtr() +{ + return mEvent; +} + +NS_IMETHODIMP_(Event*) +Event::InternalDOMEvent() +{ + return this; +} + +// return true if eventName is contained within events, delimited by +// spaces +static bool +PopupAllowedForEvent(const char *eventName) +{ + if (!sPopupAllowedEvents) { + Event::PopupAllowedEventsChanged(); + + if (!sPopupAllowedEvents) { + return false; + } + } + + nsDependentCString events(sPopupAllowedEvents); + + nsAFlatCString::const_iterator start, end; + nsAFlatCString::const_iterator startiter(events.BeginReading(start)); + events.EndReading(end); + + while (startiter != end) { + nsAFlatCString::const_iterator enditer(end); + + if (!FindInReadable(nsDependentCString(eventName), startiter, enditer)) + return false; + + // the match is surrounded by spaces, or at a string boundary + if ((startiter == start || *--startiter == ' ') && + (enditer == end || *enditer == ' ')) { + return true; + } + + // Move on and see if there are other matches. (The delimitation + // requirement makes it pointless to begin the next search before + // the end of the invalid match just found.) + startiter = enditer; + } + + return false; +} + +// static +PopupControlState +Event::GetEventPopupControlState(WidgetEvent* aEvent, nsIDOMEvent* aDOMEvent) +{ + // generally if an event handler is running, new windows are disallowed. + // check for exceptions: + PopupControlState abuse = openAbused; + + if (aDOMEvent && aDOMEvent->InternalDOMEvent()->GetWantsPopupControlCheck()) { + nsAutoString type; + aDOMEvent->GetType(type); + if (PopupAllowedForEvent(NS_ConvertUTF16toUTF8(type).get())) { + return openAllowed; + } + } + + switch(aEvent->mClass) { + case eBasicEventClass: + // For these following events only allow popups if they're + // triggered while handling user input. See + // nsPresShell::HandleEventInternal() for details. + if (EventStateManager::IsHandlingUserInput()) { + switch(aEvent->mMessage) { + case eFormSelect: + if (PopupAllowedForEvent("select")) { + abuse = openControlled; + } + break; + case eFormChange: + if (PopupAllowedForEvent("change")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + case eEditorInputEventClass: + // For this following event only allow popups if it's triggered + // while handling user input. See + // nsPresShell::HandleEventInternal() for details. + if (EventStateManager::IsHandlingUserInput()) { + switch(aEvent->mMessage) { + case eEditorInput: + if (PopupAllowedForEvent("input")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + case eInputEventClass: + // For this following event only allow popups if it's triggered + // while handling user input. See + // nsPresShell::HandleEventInternal() for details. + if (EventStateManager::IsHandlingUserInput()) { + switch(aEvent->mMessage) { + case eFormChange: + if (PopupAllowedForEvent("change")) { + abuse = openControlled; + } + break; + case eXULCommand: + abuse = openControlled; + break; + default: + break; + } + } + break; + case eKeyboardEventClass: + if (aEvent->IsTrusted()) { + uint32_t key = aEvent->AsKeyboardEvent()->mKeyCode; + switch(aEvent->mMessage) { + case eKeyPress: + // return key on focused button. see note at eMouseClick. + if (key == NS_VK_RETURN) { + abuse = openAllowed; + } else if (PopupAllowedForEvent("keypress")) { + abuse = openControlled; + } + break; + case eKeyUp: + // space key on focused button. see note at eMouseClick. + if (key == NS_VK_SPACE) { + abuse = openAllowed; + } else if (PopupAllowedForEvent("keyup")) { + abuse = openControlled; + } + break; + case eKeyDown: + if (PopupAllowedForEvent("keydown")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + case eTouchEventClass: + if (aEvent->IsTrusted()) { + switch (aEvent->mMessage) { + case eTouchStart: + if (PopupAllowedForEvent("touchstart")) { + abuse = openControlled; + } + break; + case eTouchEnd: + if (PopupAllowedForEvent("touchend")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + case eMouseEventClass: + if (aEvent->IsTrusted() && + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) { + switch(aEvent->mMessage) { + case eMouseUp: + if (PopupAllowedForEvent("mouseup")) { + abuse = openControlled; + } + break; + case eMouseDown: + if (PopupAllowedForEvent("mousedown")) { + abuse = openControlled; + } + break; + case eMouseClick: + /* Click events get special treatment because of their + historical status as a more legitimate event handler. If + click popups are enabled in the prefs, clear the popup + status completely. */ + if (PopupAllowedForEvent("click")) { + abuse = openAllowed; + } + break; + case eMouseDoubleClick: + if (PopupAllowedForEvent("dblclick")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + case eFormEventClass: + // For these following events only allow popups if they're + // triggered while handling user input. See + // nsPresShell::HandleEventInternal() for details. + if (EventStateManager::IsHandlingUserInput()) { + switch(aEvent->mMessage) { + case eFormSubmit: + if (PopupAllowedForEvent("submit")) { + abuse = openControlled; + } + break; + case eFormReset: + if (PopupAllowedForEvent("reset")) { + abuse = openControlled; + } + break; + default: + break; + } + } + break; + default: + break; + } + + return abuse; +} + +// static +void +Event::PopupAllowedEventsChanged() +{ + if (sPopupAllowedEvents) { + free(sPopupAllowedEvents); + } + + nsAdoptingCString str = Preferences::GetCString("dom.popup_allowed_events"); + + // We'll want to do this even if str is empty to avoid looking up + // this pref all the time if it's not set. + sPopupAllowedEvents = ToNewCString(str); +} + +// static +void +Event::Shutdown() +{ + if (sPopupAllowedEvents) { + free(sPopupAllowedEvents); + } +} + +// static +CSSIntPoint +Event::GetScreenCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint) +{ + if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode() && + nsContentUtils::ResistFingerprinting()) { + // When resisting fingerprinting, return client coordinates instead. + return GetClientCoords(aPresContext, aEvent, aPoint, CSSIntPoint(0, 0)); + } + + if (EventStateManager::sIsPointerLocked) { + return EventStateManager::sLastScreenPoint; + } + + if (!aEvent || + (aEvent->mClass != eMouseEventClass && + aEvent->mClass != eMouseScrollEventClass && + aEvent->mClass != eWheelEventClass && + aEvent->mClass != ePointerEventClass && + aEvent->mClass != eTouchEventClass && + aEvent->mClass != eDragEventClass && + aEvent->mClass != eSimpleGestureEventClass)) { + return CSSIntPoint(0, 0); + } + + // Doing a straight conversion from LayoutDeviceIntPoint to CSSIntPoint + // seem incorrect, but it is needed to maintain legacy functionality. + WidgetGUIEvent* guiEvent = aEvent->AsGUIEvent(); + if (!aPresContext || !(guiEvent && guiEvent->mWidget)) { + return CSSIntPoint(aPoint.x, aPoint.y); + } + + nsPoint pt = + LayoutDevicePixel::ToAppUnits(aPoint, aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()); + + if (nsIPresShell* ps = aPresContext->GetPresShell()) { + pt = pt.RemoveResolution(nsLayoutUtils::GetCurrentAPZResolutionScale(ps)); + } + + pt += LayoutDevicePixel::ToAppUnits(guiEvent->mWidget->WidgetToScreenOffset(), + aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()); + + return CSSPixel::FromAppUnitsRounded(pt); +} + +// static +CSSIntPoint +Event::GetPageCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint, + CSSIntPoint aDefaultPoint) +{ + CSSIntPoint pagePoint = + Event::GetClientCoords(aPresContext, aEvent, aPoint, aDefaultPoint); + + // If there is some scrolling, add scroll info to client point. + if (aPresContext && aPresContext->GetPresShell()) { + nsIPresShell* shell = aPresContext->GetPresShell(); + nsIScrollableFrame* scrollframe = shell->GetRootScrollFrameAsScrollable(); + if (scrollframe) { + pagePoint += CSSIntPoint::FromAppUnitsRounded(scrollframe->GetScrollPosition()); + } + } + + return pagePoint; +} + +// static +CSSIntPoint +Event::GetClientCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint, + CSSIntPoint aDefaultPoint) +{ + if (EventStateManager::sIsPointerLocked) { + return EventStateManager::sLastClientPoint; + } + + if (!aEvent || + (aEvent->mClass != eMouseEventClass && + aEvent->mClass != eMouseScrollEventClass && + aEvent->mClass != eWheelEventClass && + aEvent->mClass != eTouchEventClass && + aEvent->mClass != eDragEventClass && + aEvent->mClass != ePointerEventClass && + aEvent->mClass != eSimpleGestureEventClass) || + !aPresContext || + !aEvent->AsGUIEvent()->mWidget) { + return aDefaultPoint; + } + + nsIPresShell* shell = aPresContext->GetPresShell(); + if (!shell) { + return CSSIntPoint(0, 0); + } + nsIFrame* rootFrame = shell->GetRootFrame(); + if (!rootFrame) { + return CSSIntPoint(0, 0); + } + nsPoint pt = + nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, aPoint, rootFrame); + + return CSSIntPoint::FromAppUnitsRounded(pt); +} + +// static +CSSIntPoint +Event::GetOffsetCoords(nsPresContext* aPresContext, + WidgetEvent* aEvent, + LayoutDeviceIntPoint aPoint, + CSSIntPoint aDefaultPoint) +{ + if (!aEvent->mTarget) { + return GetPageCoords(aPresContext, aEvent, aPoint, aDefaultPoint); + } + nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->mTarget); + if (!content || !aPresContext) { + return CSSIntPoint(0, 0); + } + nsCOMPtr<nsIPresShell> shell = aPresContext->GetPresShell(); + if (!shell) { + return CSSIntPoint(0, 0); + } + shell->FlushPendingNotifications(Flush_Layout); + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + return CSSIntPoint(0, 0); + } + nsIFrame* rootFrame = shell->GetRootFrame(); + if (!rootFrame) { + return CSSIntPoint(0, 0); + } + CSSIntPoint clientCoords = + GetClientCoords(aPresContext, aEvent, aPoint, aDefaultPoint); + nsPoint pt = CSSPixel::ToAppUnits(clientCoords); + if (nsLayoutUtils::TransformPoint(rootFrame, frame, pt) == + nsLayoutUtils::TRANSFORM_SUCCEEDED) { + pt -= frame->GetPaddingRectRelativeToSelf().TopLeft(); + return CSSPixel::FromAppUnitsRounded(pt); + } + return CSSIntPoint(0, 0); +} + +// To be called ONLY by Event::GetType (which has the additional +// logic for handling user-defined events). +// static +const char* +Event::GetEventName(EventMessage aEventType) +{ + switch(aEventType) { +#define MESSAGE_TO_EVENT(name_, _message, _type, _struct) \ + case _message: return #name_; +#include "mozilla/EventNameList.h" +#undef MESSAGE_TO_EVENT + default: + break; + } + // XXXldb We can hit this case for WidgetEvent objects that we didn't + // create and that are not user defined events since this function and + // SetEventType are incomplete. (But fixing that requires fixing the + // arrays in nsEventListenerManager too, since the events for which + // this is a problem generally *are* created by Event.) + return nullptr; +} + +bool +Event::DefaultPrevented(JSContext* aCx) const +{ + MOZ_ASSERT(aCx, "JS context must be specified"); + + NS_ENSURE_TRUE(mEvent, false); + + // If preventDefault() has never been called, just return false. + if (!mEvent->DefaultPrevented()) { + return false; + } + + // If preventDefault() has been called by content, return true. Otherwise, + // i.e., preventDefault() has been called by chrome, return true only when + // this is called by chrome. + return mEvent->DefaultPreventedByContent() || IsChrome(aCx); +} + +double +Event::TimeStamp() const +{ + if (!sReturnHighResTimeStamp) { + return static_cast<double>(mEvent->mTime); + } + + if (mEvent->mTimeStamp.IsNull()) { + return 0.0; + } + + if (mIsMainThreadEvent) { + if (NS_WARN_IF(!mOwner)) { + return 0.0; + } + + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(mOwner); + if (NS_WARN_IF(!win)) { + return 0.0; + } + + Performance* perf = win->GetPerformance(); + if (NS_WARN_IF(!perf)) { + return 0.0; + } + + return perf->GetDOMTiming()->TimeStampToDOMHighRes(mEvent->mTimeStamp); + } + + // For dedicated workers, we should make times relative to the navigation + // start of the document that created the worker, which is the same as the + // timebase for performance.now(). + workers::WorkerPrivate* workerPrivate = + workers::GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + TimeDuration duration = + mEvent->mTimeStamp - workerPrivate->NowBaseTimeStamp(); + return duration.ToMilliseconds(); +} + +bool +Event::GetPreventDefault() const +{ + nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(mOwner)); + if (win) { + if (nsIDocument* doc = win->GetExtantDoc()) { + doc->WarnOnceAbout(nsIDocument::eGetPreventDefault); + } + } + // GetPreventDefault() is legacy and Gecko specific method. Although, + // the result should be same as defaultPrevented, we don't need to break + // backward compatibility of legacy method. Let's behave traditionally. + return DefaultPrevented(); +} + +NS_IMETHODIMP +Event::GetPreventDefault(bool* aReturn) +{ + NS_ENSURE_ARG_POINTER(aReturn); + *aReturn = GetPreventDefault(); + return NS_OK; +} + +NS_IMETHODIMP +Event::GetDefaultPrevented(bool* aReturn) +{ + NS_ENSURE_ARG_POINTER(aReturn); + // This method must be called by only event handlers implemented by C++. + // Then, the handlers must handle default action. So, this method don't need + // to check if preventDefault() has been called by content or chrome. + *aReturn = DefaultPrevented(); + return NS_OK; +} + +NS_IMETHODIMP_(void) +Event::Serialize(IPC::Message* aMsg, bool aSerializeInterfaceType) +{ + if (aSerializeInterfaceType) { + IPC::WriteParam(aMsg, NS_LITERAL_STRING("event")); + } + + nsString type; + GetType(type); + IPC::WriteParam(aMsg, type); + + IPC::WriteParam(aMsg, Bubbles()); + IPC::WriteParam(aMsg, Cancelable()); + IPC::WriteParam(aMsg, IsTrusted()); + IPC::WriteParam(aMsg, Composed()); + + // No timestamp serialization for now! +} + +NS_IMETHODIMP_(bool) +Event::Deserialize(const IPC::Message* aMsg, PickleIterator* aIter) +{ + nsString type; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &type), false); + + bool bubbles = false; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &bubbles), false); + + bool cancelable = false; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &cancelable), false); + + bool trusted = false; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &trusted), false); + + bool composed = false; + NS_ENSURE_TRUE(IPC::ReadParam(aMsg, aIter, &composed), false); + + InitEvent(type, bubbles, cancelable); + SetTrusted(trusted); + SetComposed(composed); + + return true; +} + +NS_IMETHODIMP_(void) +Event::SetOwner(mozilla::dom::EventTarget* aOwner) +{ + mOwner = nullptr; + + if (!aOwner) { + return; + } + + nsCOMPtr<nsINode> n = do_QueryInterface(aOwner); + if (n) { + mOwner = n->OwnerDoc()->GetScopeObject(); + return; + } + + nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(aOwner); + if (w) { + mOwner = do_QueryInterface(w); + return; + } + + nsCOMPtr<DOMEventTargetHelper> eth = do_QueryInterface(aOwner); + if (eth) { + mOwner = eth->GetParentObject(); + return; + } + +#ifdef DEBUG + nsCOMPtr<nsPIWindowRoot> root = do_QueryInterface(aOwner); + MOZ_ASSERT(root, "Unexpected EventTarget!"); +#endif +} + +// static +nsIContent* +Event::GetShadowRelatedTarget(nsIContent* aCurrentTarget, + nsIContent* aRelatedTarget) +{ + if (!aCurrentTarget || !aRelatedTarget) { + return nullptr; + } + + // Walk up the ancestor node trees of the related target until + // we encounter the node tree of the current target in order + // to find the adjusted related target. Walking up the tree may + // not find a common ancestor node tree if the related target is in + // an ancestor tree, but in that case it does not need to be adjusted. + ShadowRoot* currentTargetShadow = aCurrentTarget->GetContainingShadow(); + if (!currentTargetShadow) { + return nullptr; + } + + nsIContent* relatedTarget = aCurrentTarget; + while (relatedTarget) { + ShadowRoot* ancestorShadow = relatedTarget->GetContainingShadow(); + if (currentTargetShadow == ancestorShadow) { + return relatedTarget; + } + + // Didn't find the ancestor tree, thus related target does not have to + // adjusted. + if (!ancestorShadow) { + return nullptr; + } + + relatedTarget = ancestorShadow->GetHost(); + } + + return nullptr; +} + +NS_IMETHODIMP +Event::GetCancelBubble(bool* aCancelBubble) +{ + NS_ENSURE_ARG_POINTER(aCancelBubble); + *aCancelBubble = CancelBubble(); + return NS_OK; +} + +NS_IMETHODIMP +Event::SetCancelBubble(bool aCancelBubble) +{ + if (aCancelBubble) { + mEvent->StopPropagation(); + } + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::dom; + +already_AddRefed<Event> +NS_NewDOMEvent(EventTarget* aOwner, + nsPresContext* aPresContext, + WidgetEvent* aEvent) +{ + RefPtr<Event> it = new Event(aOwner, aPresContext, aEvent); + return it.forget(); +} |