Bug 1428913 - Deny full-screen on right or middle mouse button. r=smaug
authorPaul Zuehlcke <pzuhlcke@mozilla.com>
Wed, 22 May 2019 19:16:31 +0000
changeset 475195 eaafaaab2b4d5ec0fbd553eb951d0cea2c82ff77
parent 475194 4ff05bda88b2aa7f9aac46bf68b9669b3ad0f655
child 475196 58dd65c6aa95426b911013eb10ba2e2f460e1b16
push id36057
push useraciure@mozilla.com
push dateThu, 23 May 2019 21:52:03 +0000
treeherdermozilla-central@d551d37b9ad0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1428913
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1428913 - Deny full-screen on right or middle mouse button. r=smaug Differential Revision: https://phabricator.services.mozilla.com/D31481
dom/base/Element.cpp
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/events/EventStateManager.cpp
dom/events/EventStateManager.h
dom/html/test/file_fullscreen-denied.html
dom/html/test/file_fullscreen-utils.js
dom/locales/en-US/chrome/dom/dom.properties
modules/libpref/init/StaticPrefList.h
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3286,21 +3286,17 @@ CORSMode Element::AttrValueToCORSMode(co
   if (!aValue) {
     return CORS_NONE;
   }
 
   return CORSMode(aValue->GetEnumValue());
 }
 
 static const char* GetFullscreenError(CallerType aCallerType) {
-  if (!nsContentUtils::IsRequestFullscreenAllowed(aCallerType)) {
-    return "FullscreenDeniedNotInputDriven";
-  }
-
-  return nullptr;
+  return nsContentUtils::CheckRequestFullscreenAllowed(aCallerType);
 }
 
 already_AddRefed<Promise> Element::RequestFullscreen(CallerType aCallerType,
                                                      ErrorResult& aRv) {
   auto request = FullscreenRequest::Create(this, aCallerType, aRv);
   RefPtr<Promise> promise = request->GetPromise();
 
   if (!FeaturePolicyUtils::IsFeatureAllowed(OwnerDoc(),
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6576,34 +6576,46 @@ bool nsContentUtils::ChannelShouldInheri
          // One more check here.  CheckMayLoad will always return true for the
          // system principal, but we do NOT want to inherit in that case.
          !aLoadingPrincipal->IsSystemPrincipal());
   }
   return inherit;
 }
 
 /* static */
-bool nsContentUtils::IsRequestFullscreenAllowed(CallerType aCallerType) {
-  // If more time has elapsed since the user input than is specified by the
-  // dom.event.handling-user-input-time-limit pref (default 1 second), this
-  // function also returns false.
-
+const char* nsContentUtils::CheckRequestFullscreenAllowed(
+    CallerType aCallerType) {
   if (!StaticPrefs::full_screen_api_allow_trusted_requests_only() ||
       aCallerType == CallerType::System) {
-    return true;
-  }
-
-  if (EventStateManager::IsHandlingUserInput()) {
-    TimeDuration timeout = HandlingUserInputTimeout();
-    return timeout <= TimeDuration(nullptr) ||
-           (TimeStamp::Now() - EventStateManager::GetHandlingInputStart()) <=
-               timeout;
-  }
-
-  return false;
+    return nullptr;
+  }
+
+  if (!EventStateManager::IsHandlingUserInput()) {
+    return "FullscreenDeniedNotInputDriven";
+  }
+
+  // If more time has elapsed since the user input than is specified by the
+  // dom.event.handling-user-input-time-limit pref (default 1 second),
+  // disallow fullscreen
+  TimeDuration timeout = HandlingUserInputTimeout();
+  if (timeout > TimeDuration(nullptr) &&
+      (TimeStamp::Now() - EventStateManager::GetHandlingInputStart()) >
+          timeout) {
+    return "FullscreenDeniedNotInputDriven";
+  }
+
+  // Entering full-screen on mouse mouse event is only allowed with left mouse
+  // button
+  if (StaticPrefs::full_screen_api_mouse_event_allow_left_button_only() &&
+      (EventStateManager::sCurrentMouseBtn == MouseButton::eMiddle ||
+       EventStateManager::sCurrentMouseBtn == MouseButton::eRight)) {
+    return "FullscreenDeniedMouseEventOnlyLeftBtn";
+  }
+
+  return nullptr;
 }
 
 /* static */
 bool nsContentUtils::IsCutCopyAllowed(nsIPrincipal* aSubjectPrincipal) {
   if (StaticPrefs::dom_allow_cut_copy() &&
       EventStateManager::IsHandlingUserInput()) {
     return true;
   }
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2327,22 +2327,24 @@ class nsContentUtils {
    * Determine whether a content node is focused or not,
    *
    * @param aContent the content node to check
    * @return true if the content node is focused, false otherwise.
    */
   static bool IsFocusedContent(const nsIContent* aContent);
 
   /**
-   * Returns true if requests for fullscreen are allowed in the current
+   * Returns nullptr if requests for fullscreen are allowed in the current
    * context. Requests are only allowed if the user initiated them (like with
    * a mouse-click or key press), unless this check has been disabled by
    * setting the pref "full-screen-api.allow-trusted-requests-only" to false.
+   * If fullscreen is not allowed, a key for the error message is returned.
    */
-  static bool IsRequestFullscreenAllowed(mozilla::dom::CallerType aCallerType);
+  static const char* CheckRequestFullscreenAllowed(
+      mozilla::dom::CallerType aCallerType);
 
   /**
    * Returns true if calling execCommand with 'cut' or 'copy' arguments is
    * allowed for the given subject principal. These are only allowed if the user
    * initiated them (like with a mouse-click or key press).
    */
   static bool IsCutCopyAllowed(nsIPrincipal* aSubjectPrincipal);
 
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -192,16 +192,17 @@ NS_INTERFACE_MAP_END
 /* mozilla::EventStateManager                                     */
 /******************************************************************/
 
 static uint32_t sESMInstanceCount = 0;
 
 int32_t EventStateManager::sUserInputEventDepth = 0;
 int32_t EventStateManager::sUserKeyboardEventDepth = 0;
 bool EventStateManager::sNormalLMouseEventInProcess = false;
+int16_t EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
 EventStateManager* EventStateManager::sActiveESM = nullptr;
 Document* EventStateManager::sMouseOverDocument = nullptr;
 AutoWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
 LayoutDeviceIntPoint EventStateManager::sPreLockPoint =
     LayoutDeviceIntPoint(0, 0);
 LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
 CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0);
 LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint;
@@ -6277,27 +6278,36 @@ AutoHandlingUserInputStatePusher::AutoHa
     NS_ENSURE_TRUE_VOID(fm);
     // If it's in modal state, mouse button event handling may be nested.
     // E.g., a modal dialog is opened at mousedown or mouseup event handler
     // and the dialog is clicked.  Therefore, we should store current
     // mouse button event handling document if nsFocusManager already has it.
     mMouseButtonEventHandlingDocument =
         fm->SetMouseButtonHandlingDocument(aDocument);
   }
+  if (NeedsToUpdateCurrentMouseBtnState()) {
+    WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+    if (mouseEvent) {
+      EventStateManager::sCurrentMouseBtn = mouseEvent->mButton;
+    }
+  }
 }
 
 AutoHandlingUserInputStatePusher::~AutoHandlingUserInputStatePusher() {
   if (!mIsHandlingUserInput) {
     return;
   }
   EventStateManager::StopHandlingUserInput(mMessage);
   if (mMessage == eMouseDown) {
     PresShell::AllowMouseCapture(false);
   }
   if (NeedsToResetFocusManagerMouseButtonHandlingState()) {
     nsFocusManager* fm = nsFocusManager::GetFocusManager();
     NS_ENSURE_TRUE_VOID(fm);
     nsCOMPtr<Document> handlingDocument =
         fm->SetMouseButtonHandlingDocument(mMouseButtonEventHandlingDocument);
   }
+  if (NeedsToUpdateCurrentMouseBtnState()) {
+    EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
+  }
 }
 
 }  // namespace mozilla
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -1257,16 +1257,17 @@ class EventStateManager : public nsSuppo
   // events.  sUserKeyboardEventDepth is the number of keyboard input events.
   // Incremented whenever we start handling a user input, decremented when we
   // have finished handling a user input. This depth is *not* reset in case
   // of nested event loops.
   static int32_t sUserInputEventDepth;
   static int32_t sUserKeyboardEventDepth;
 
   static bool sNormalLMouseEventInProcess;
+  static int16_t sCurrentMouseBtn;
 
   static EventStateManager* sActiveESM;
 
   static void ClearGlobalActiveContent(EventStateManager* aClearer);
 
   // Functions used for click hold context menus
   nsCOMPtr<nsITimer> mClickHoldTimer;
   void CreateClickHoldTimer(nsPresContext* aPresContext, nsIFrame* aDownFrame,
@@ -1293,16 +1294,21 @@ class MOZ_RAII AutoHandlingUserInputStat
  protected:
   RefPtr<dom::Document> mMouseButtonEventHandlingDocument;
   EventMessage mMessage;
   bool mIsHandlingUserInput;
 
   bool NeedsToResetFocusManagerMouseButtonHandlingState() const {
     return mMessage == eMouseDown || mMessage == eMouseUp;
   }
+
+  bool NeedsToUpdateCurrentMouseBtnState() const {
+    return mMessage == eMouseDown || mMessage == eMouseUp ||
+           mMessage == ePointerDown || mMessage == ePointerUp;
+  }
 };
 
 }  // namespace mozilla
 
 // Click and double-click events need to be handled even for content that
 // has no frame. This is required for Web compatibility.
 #define NS_EVENT_NEEDS_FRAME(event)               \
   (!(event)->HasPluginActivationEventMessage() && \
--- a/dom/html/test/file_fullscreen-denied.html
+++ b/dom/html/test/file_fullscreen-denied.html
@@ -106,24 +106,59 @@ function testLongRunningEventHandler() {
     var end = (new Date()).getTime() + 2000;
     while ((new Date()).getTime() < end) {
       ; // Wait...
     }
     document.documentElement.requestFullscreen();
   }
   addFullscreenErrorContinuation(() => {
     ok(!document.fullscreenElement,
-       "Should not grant request in long-running event handler.");
-    // Restore the pref environment we changed before
-    // entering testNonTrustContext.
-    SpecialPowers.popPrefEnv(finish);
+      "Should not grant request in long-running event handler.");
+    SimpleTest.executeSoon(testFullscreenMouseBtn);
   });
   window.addEventListener("keypress", longRunningHandler);
   sendString("a");
 }
 
+function requestFullscreenMouseBtn(event, button) {
+  let clickEl = document.createElement("p");
+  clickEl.innerText = "Click Me";
+
+  function eventHandler(event) {
+    document.body.requestFullscreen();
+    event.target.removeEventListener(event, this);
+  }
+
+  clickEl.addEventListener(event, eventHandler);
+  document.body.appendChild(clickEl);
+  synthesizeMouseAtCenter(clickEl, { button });
+}
+
+async function testFullscreenMouseBtn(event, button, next) {
+  await SpecialPowers.pushPrefEnv({
+    "set": [["full-screen-api.mouse-event-allow-left-button-only", true]]
+  });
+  let fsRequestEvents = ["mousedown", "mouseup", "pointerdown", "pointerup"];
+  let mouseButtons = [1, 2];
+
+  for (let i = 0; i < fsRequestEvents.length; i++) {
+    let event = fsRequestEvents[i];
+    for (let j = 0; j < mouseButtons.length; j++) {
+      let mouseButton = mouseButtons[j];
+      let fsDenied = addFullscreenErrorContinuation();
+      requestFullscreenMouseBtn(event, mouseButton);
+      await fsDenied;
+      ok(!document.fullscreenElement, `Should not grant request on '${event}' triggered by mouse button ${mouseButton}`);
+    }
+  }
+  // Restore the pref environment we changed before
+  // entering testNonTrustContext.
+  await SpecialPowers.popPrefEnv();
+  finish();
+}
+
 function finish() {
   opener.nextTest();
 }
 
 </script>
 </body>
 </html>
--- a/dom/html/test/file_fullscreen-utils.js
+++ b/dom/html/test/file_fullscreen-utils.js
@@ -67,22 +67,29 @@ function addFullscreenChangeContinuation
     }
     topWin.addEventListener("resize", onResize);
   }
   doc.addEventListener("fullscreenchange", onFullscreenChange);
 }
 
 // Calls |callback| when the next fullscreenerror is dispatched to inDoc||document.
 function addFullscreenErrorContinuation(callback, inDoc) {
-  var doc = inDoc || document;
-  var listener = function(event) {
-    doc.removeEventListener("fullscreenerror", listener);
-    setTimeout(function(){callback(event);}, 0);
-  };
-  doc.addEventListener("fullscreenerror", listener);
+  return new Promise((resolve) => {
+    let doc = inDoc || document;
+    let listener = function(event) {
+      doc.removeEventListener("fullscreenerror", listener);
+      setTimeout(function(){
+        if(callback) {
+          callback(event);
+        }
+        resolve();
+      }, 0);
+    };
+    doc.addEventListener("fullscreenerror", listener);
+  })
 }
 
 // Waits until the window has both the load event and a MozAfterPaint called on
 // it, and then invokes the callback
 function waitForLoadAndPaint(win, callback) {
   win.addEventListener("MozAfterPaint", function() {
     // The load event may have fired before the MozAfterPaint, in which case
     // listening for it now will hang. Instead we check the readyState to see if
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -58,16 +58,17 @@ FormValidationStepMismatch=Please select
 FormValidationStepMismatchOneValue=Please select a valid value. The nearest valid value is %S.
 FormValidationBadInputNumber=Please enter a number.
 EnablePrivilegeWarning=Use of enablePrivilege is deprecated.  Please use code that runs with the system principal (e.g. an extension) instead.
 FullscreenDeniedDisabled=Request for fullscreen was denied because Fullscreen API is disabled by user preference.
 FullscreenDeniedFocusedPlugin=Request for fullscreen was denied because a windowed plugin is focused.
 FullscreenDeniedHidden=Request for fullscreen was denied because the document is no longer visible.
 FullscreenDeniedContainerNotAllowed=Request for fullscreen was denied because at least one of the document’s containing elements is not an iframe or does not have an “allowfullscreen” attribute.
 FullscreenDeniedNotInputDriven=Request for fullscreen was denied because Element.requestFullscreen() was not called from inside a short running user-generated event handler.
+FullscreenDeniedMouseEventOnlyLeftBtn=Request for fullscreen was denied because Element.requestFullscreen() was called from inside a mouse event handler not triggered by left mouse button.
 FullscreenDeniedNotHTMLSVGOrMathML=Request for fullscreen was denied because requesting element is not <svg>, <math>, or an HTML element.
 FullscreenDeniedNotInDocument=Request for fullscreen was denied because requesting element is no longer in its document.
 FullscreenDeniedMovedDocument=Request for fullscreen was denied because requesting element has moved document.
 FullscreenDeniedLostWindow=Request for fullscreen was denied because we no longer have a window.
 FullscreenDeniedSubDocFullscreen=Request for fullscreen was denied because a subdocument of the document requesting fullscreen is already fullscreen.
 FullscreenDeniedNotDescendant=Request for fullscreen was denied because requesting element is not a descendant of the current fullscreen element.
 FullscreenDeniedNotFocusedTab=Request for fullscreen was denied because requesting element is not in the currently focused tab.
 FullscreenDeniedFeaturePolicy=Request for fullscreen was denied because of FeaturePolicy directives.
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -781,16 +781,22 @@ VARCACHE_PREF(
 )
 
 VARCACHE_PREF(
   "full-screen-api.allow-trusted-requests-only",
    full_screen_api_allow_trusted_requests_only,
   bool, true
 )
 
+VARCACHE_PREF(
+  "full-screen-api.mouse-event-allow-left-button-only",
+   full_screen_api_mouse_event_allow_left_button_only,
+  bool, true
+)
+
 //---------------------------------------------------------------------------
 // Preference stylesheet prefs.
 //---------------------------------------------------------------------------
 
 VARCACHE_PREF(
   "browser.display.focus_ring_on_anything",
    browser_display_focus_ring_on_anything,
   bool, false