Bug 1327097 - Part III, Trap mouse/touch/pointer events in audio/video element, r=smaug
This patch implements HTMLMediaElement::GetEventTargetParent and set
aVisitor.mCanHandle to false to mouse/touch/pointer events, when
the media control is present. This tells the event dispatcher that
these events are supposed to be handled exclusively by the
videocontrol binding within the media element, and should not
dispatch nor consumed by the content.
MozReview-Commit-ID: BXWZX9SYsuC
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -573,16 +573,22 @@ public:
{
SetUnsignedIntAttr(nsGkAtoms::height, aValue, 0, aRv);
}
bool Indeterminate() const
{
return mIndeterminate;
}
+
+ bool IsDraggingRange() const
+ {
+ return mIsDraggingRange;
+ }
+
// XPCOM SetIndeterminate() is OK
void GetInputMode(nsAString& aValue);
void SetInputMode(const nsAString& aValue, ErrorResult& aRv)
{
SetHTMLAttr(nsGkAtoms::inputmode, aValue, aRv);
}
@@ -1530,17 +1536,17 @@ protected:
bool IsPopupBlocked() const;
GetFilesHelper* GetOrCreateGetFilesHelper(bool aRecursiveFlag,
ErrorResult& aRv);
void ClearGetFilesHelpers();
/**
- * nsINode::SetMayBeApzAware() will be invoked in this function if necessary
+ * nsINode::SetMayBeApzAware() will be invoked in this function if necessary
* to prevent default action of APZC so that we can increase/decrease the
* value of this InputElement when mouse wheel event comes without scrolling
* the page.
*
* SetMayBeApzAware() will set flag MayBeApzAware which is checked by apzc to
* decide whether to add this element into its dispatch-to-content region.
*/
void UpdateApzAwareFlag();
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -9,16 +9,17 @@
#include "mozilla/dom/HTMLSourceElement.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/dom/MediaEncryptedEvent.h"
#include "mozilla/EMEUtils.h"
+#include "mozilla/EventDispatcher.h"
#include "mozilla/Sprintf.h"
#include "base/basictypes.h"
#include "nsIDOMHTMLMediaElement.h"
#include "TimeRanges.h"
#include "nsGenericHTMLElement.h"
#include "nsAttrValueInlines.h"
#include "nsDocShellLoadTypes.h"
@@ -130,16 +131,17 @@ static mozilla::LazyLogModule gMediaElem
#include "mozilla/Preferences.h"
#include "mozilla/FloatingPoint.h"
#include "nsIPermissionManager.h"
#include "nsDocShell.h"
#include "mozilla/EventStateManager.h"
+#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "mozilla/dom/VideoPlaybackQuality.h"
#include "HTMLMediaElement.h"
#include "GMPCrashHelper.h"
using namespace mozilla::layers;
using mozilla::net::nsMediaFragmentURIParser;
@@ -4378,16 +4380,70 @@ HTMLMediaElement::OutputMediaStream::Out
HTMLMediaElement::OutputMediaStream::~OutputMediaStream()
{
for (auto pair : mTrackPorts) {
pair.second()->Destroy();
}
}
+nsresult
+HTMLMediaElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
+{
+ if (!this->Controls() || !aVisitor.mEvent->mFlags.mIsTrusted) {
+ return nsGenericHTMLElement::GetEventTargetParent(aVisitor);
+ }
+
+ HTMLInputElement* el = nullptr;
+ nsCOMPtr<nsINode> node;
+
+ // We will need to trap pointer, touch, and mouse events within the media
+ // element, allowing media control exclusive consumption on these events,
+ // and preventing the content from handling them.
+ switch (aVisitor.mEvent->mMessage) {
+ case ePointerDown:
+ case ePointerUp:
+ case eTouchEnd:
+ // Always prevent touchmove captured in video element from being handled by content,
+ // since we always do that for touchstart.
+ case eTouchMove:
+ case eTouchStart:
+ case eMouseClick:
+ case eMouseDoubleClick:
+ case eMouseDown:
+ case eMouseUp:
+ aVisitor.mCanHandle = false;
+ return NS_OK;
+
+ // The *move events however are only comsumed when the range input is being
+ // dragged.
+ case ePointerMove:
+ case eMouseMove:
+ node = do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
+ if (node->IsInNativeAnonymousSubtree()) {
+ if (node->IsHTMLElement(nsGkAtoms::input)) {
+ // The node is a <input type="range">
+ el = static_cast<HTMLInputElement*>(node.get());
+ } else if (node->GetParentNode() &&
+ node->GetParentNode()->IsHTMLElement(nsGkAtoms::input)) {
+ // The node is a child of <input type="range">
+ el = static_cast<HTMLInputElement*>(node->GetParentNode());
+ }
+ }
+ if (el && el->IsDraggingRange()) {
+ aVisitor.mCanHandle = false;
+ return NS_OK;
+ }
+ return nsGenericHTMLElement::GetEventTargetParent(aVisitor);
+
+ default:
+ return nsGenericHTMLElement::GetEventTargetParent(aVisitor);
+ }
+}
+
bool HTMLMediaElement::ParseAttribute(int32_t aNamespaceID,
nsAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult)
{
// Mappings from 'preload' attribute strings to an enumeration.
static const nsAttrValue::EnumTable kPreloadTable[] = {
{ "", HTMLMediaElement::PRELOAD_ATTR_EMPTY },
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -140,16 +140,20 @@ public:
// nsIDOMHTMLMediaElement
NS_DECL_NSIDOMHTMLMEDIAELEMENT
// nsISupports
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLMediaElement,
nsGenericHTMLElement)
+ // nsIDOMEventTarget
+ virtual nsresult
+ GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
+
virtual bool ParseAttribute(int32_t aNamespaceID,
nsAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult) override;
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
bool aCompileEventHandlers) override;
--- a/gfx/layers/apz/test/mochitest/helper_bug1162771.html
+++ b/gfx/layers/apz/test/mochitest/helper_bug1162771.html
@@ -93,12 +93,12 @@ waitUntilApzStable()
border:solid black 1px;
background-color: #88a;
}
</style>
</head>
<body>
<p>Tap on the colored boxes to hide them.</p>
<video id="video"></video>
- <audio id="audio" controls></audio>
+ <audio id="audio"></audio>
<div id="div"></div>
</body>
</html>
--- a/toolkit/content/tests/widgets/test_videocontrols.html
+++ b/toolkit/content/tests/widgets/test_videocontrols.html
@@ -114,16 +114,26 @@ add_task(async function setup() {
video.addEventListener("play", verifyExpectedEvent);
video.addEventListener("pause", verifyExpectedEvent);
video.addEventListener("volumechange", verifyExpectedEvent);
video.addEventListener("seeking", verifyExpectedEvent);
video.addEventListener("seeked", verifyExpectedEvent);
document.addEventListener("mozfullscreenchange", verifyExpectedEvent);
+ ["mousedown", "mouseup", "dblclick", "click"]
+ .forEach((eventType) => {
+ window.addEventListener(eventType, (evt) => {
+ // Prevent default action of leaked events and make the tests fail.
+ evt.preventDefault();
+ ok(false, "Event " + eventType + " in videocontrol should not leak to content;" +
+ "the event was dispatched from the " + evt.target.tagName.toLowerCase() + " element.");
+ });
+ });
+
// Check initial state upon load
is(video.paused, true, "checking video play state");
is(video.muted, false, "checking video mute state");
});
add_task(async function click_playbutton() {
synthesizeMouse(video, playButtonCenterX, playButtonCenterY, {});
await waitForEvent("play");
@@ -207,25 +217,34 @@ add_task(async function reset_currentTim
/*
* Drag the slider's thumb to the halfway point with the mouse.
*/
add_task(async function drag_slider() {
const beginDragX = scrubberOffsetX;
const endDragX = scrubberOffsetX + (scrubberWidth / 2);
const expectedTime = videoDuration / 2;
+ function mousemoved(evt) {
+ ok(false, "Mousemove event should not be handled by content while dragging; " +
+ "the event was dispatched from the " + evt.target.tagName.toLowerCase() + " element.");
+ }
+
+ window.addEventListener("mousemove", mousemoved);
+
synthesizeMouse(video, beginDragX, scrubberCenterY, {type: "mousedown", button: 0});
synthesizeMouse(video, endDragX, scrubberCenterY, {type: "mousemove", button: 0});
synthesizeMouse(video, endDragX, scrubberCenterY, {type: "mouseup", button: 0});
await waitForEvent("seeking", "seeked");
ok(true, "video position is at " + video.currentTime);
// The width of srubber is not equal in every platform as we use system default font
// in duration and position box. We can not precisely drag to expected position, so
// we just make sure the difference is within 10% of video duration.
ok(Math.abs(video.currentTime - expectedTime) < videoDuration / 10, "checking expected playback position");
+
+ window.removeEventListener("mousemove", mousemoved);
});
/*
* Click the slider at the 1/4 point with the mouse (jump backwards)
*/
add_task(async function click_slider() {
synthesizeMouse(video, scrubberOffsetX + (scrubberWidth / 4), scrubberCenterY, {});
await waitForEvent("seeking", "seeked");