Bug 1495362 - FeaturePolicy: fullscreen, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 04 Oct 2018 14:34:37 +0200
changeset 439580 cafd172dc48a1d45bedad15185c720f0103bfec3
parent 439579 82efcc7fe32fe0bf727f3e9aea588d11c5a24ded
child 439581 52f118be7c585e6b4012f42463977fdd220a5eb6
push id108616
push useramarchesini@mozilla.com
push dateThu, 04 Oct 2018 12:34:55 +0000
treeherdermozilla-inbound@cafd172dc48a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1495362
milestone64.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 1495362 - FeaturePolicy: fullscreen, r=smaug
dom/base/Element.cpp
dom/html/HTMLIFrameElement.cpp
dom/html/test/file_fullscreen-featurePolicy-inner.html
dom/html/test/file_fullscreen-featurePolicy.html
dom/html/test/mochitest.ini
dom/html/test/test_fullscreen-api.html
dom/locales/en-US/chrome/dom/dom.properties
dom/security/featurepolicy/FeaturePolicyUtils.cpp
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -47,16 +47,17 @@
 #include "nsContentList.h"
 #include "nsVariant.h"
 #include "nsDOMTokenList.h"
 #include "nsXBLPrototypeBinding.h"
 #include "nsError.h"
 #include "nsDOMString.h"
 #include "nsIScriptSecurityManager.h"
 #include "mozilla/dom/AnimatableBinding.h"
+#include "mozilla/dom/FeaturePolicyUtils.h"
 #include "mozilla/dom/HTMLDivElement.h"
 #include "mozilla/dom/HTMLSpanElement.h"
 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
 #include "mozilla/dom/MutationEventBinding.h"
 #include "mozilla/AnimationComparator.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/DeclarationBlock.h"
@@ -3585,16 +3586,23 @@ GetFullscreenError(CallerType aCallerTyp
   return nullptr;
 }
 
 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(),
+                                            NS_LITERAL_STRING("fullscreen"))) {
+    request->Reject("FullscreenDeniedFeaturePolicy");
+    return promise.forget();
+  }
+
   // Only grant fullscreen requests if this is called from inside a trusted
   // event handler (i.e. inside an event handler for a user initiated event).
   // This stops the fullscreen from being abused similar to the popups of old,
   // and it also makes it harder for bad guys' script to go fullscreen and
   // spoof the browser chrome/window and phish logins etc.
   // Note that requests for fullscreen inside a web app's origin are exempt
   // from this restriction.
   if (const char* error = GetFullscreenError(aCallerType)) {
--- a/dom/html/HTMLIFrameElement.cpp
+++ b/dom/html/HTMLIFrameElement.cpp
@@ -307,14 +307,18 @@ HTMLIFrameElement::RefreshFeaturePolicy(
   }
 
   mFeaturePolicy->InheritPolicy(OwnerDoc()->Policy());
 
   if (AllowPaymentRequest()) {
     mFeaturePolicy->MaybeSetAllowedPolicy(NS_LITERAL_STRING("payment"));
   }
 
+  if (AllowFullscreen()) {
+    mFeaturePolicy->MaybeSetAllowedPolicy(NS_LITERAL_STRING("fullscreen"));
+  }
+
   // TODO: https://wicg.github.io/feature-policy/#process-feature-policy-attributes
-  // requires to check allowfullscreen, and allowusermediarequest
+  // requires to check allowusermediarequest
 }
 
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_fullscreen-featurePolicy-inner.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="doRequestFullscreen()">
+<script>
+function doRequestFullscreen() {
+  function handler(evt) {
+    document.removeEventListener("fullscreenchange", handler);
+    document.removeEventListener("fullscreenerror", handler);
+    if (evt.type == "fullscreenchange") {
+      document.exitFullscreen();
+    }
+    parent.continueTest(evt.type);
+  }
+  parent.ok(document.fullscreenEnabled, "Fullscreen " +
+            `should be enabled in ${parent.testTargetName}`);
+  document.addEventListener("fullscreenchange", handler);
+  document.addEventListener("fullscreenerror", handler);
+  document.documentElement.requestFullscreen();
+}
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_fullscreen-featurePolicy.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for FeaturePolicy + fullscreen</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+  <style>
+  body {
+    background-color: black;
+  }
+  </style>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function ok(condition, msg) {
+  opener.ok(condition, "[featurePolicy] " + msg);
+}
+
+function is(a, b, msg) {
+  opener.is(a, b, "[featurePolicy] " + msg);
+}
+
+const INNER_FILE = "file_fullscreen-featurePolicy-inner.html";
+
+function setupForInnerTest(targetName, callback) {
+  window.testTargetName = targetName;
+  window.continueTest = event => {
+    delete window.testTargetName;
+    delete window.continueTest;
+    callback(event);
+  };
+}
+
+function begin() {
+  nextTest();
+}
+
+var tests = [
+  [ "fullscreen 'none'", "fullscreenerror"],
+  [ "fullscreen", "fullscreenchange"],
+  [ "fullscreen 'src'", "fullscreenchange"],
+  [ "fullscreen 'self'", "fullscreenchange"],
+  [ "fullscreen *", "fullscreenchange"],
+  [ "fullscreen http://random.net", "fullscreenerror"],
+  [ null, "fullscreenchange"],
+];
+
+function nextTest() {
+  if (tests.length == 0) {
+    opener.nextTest();
+    return;
+  }
+
+  let test = tests.shift();
+
+  // Create an iframe with an allowfullscreen and with an allow attribute.
+  // The request should be denied or allowed, based on the current test.
+  var iframe = document.createElement("iframe");
+  iframe.setAttribute("allowfullscreen", "true");
+  if (test[0]) {
+    iframe.setAttribute("allow", test[0]);
+  }
+  iframe.src = INNER_FILE;
+  setupForInnerTest("an iframe+allowfullscreen+allow: " + test[0], event => {
+    is(event, test[1], "Expected a " + test[1] + " event");
+    document.body.removeChild(iframe);
+    SimpleTest.executeSoon(nextTest);
+  });
+  document.body.appendChild(iframe);
+}
+
+</script>
+</body>
+</html>
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -441,16 +441,18 @@ skip-if = toolkit == 'android'
 support-files =
   file_fullscreen-api.html
   file_fullscreen-backdrop.html
   file_fullscreen-denied-inner.html
   file_fullscreen-denied.html
   file_fullscreen-esc-exit-inner.html
   file_fullscreen-esc-exit.html
   file_fullscreen-event-order.html
+  file_fullscreen-featurePolicy.html
+  file_fullscreen-featurePolicy-inner.html
   file_fullscreen-hidden.html
   file_fullscreen-lenient-setters.html
   file_fullscreen-multiple-inner.html
   file_fullscreen-multiple.html
   file_fullscreen-navigation.html
   file_fullscreen-nested.html
   file_fullscreen-prefixed.html
   file_fullscreen-plugins.html
--- a/dom/html/test/test_fullscreen-api.html
+++ b/dom/html/test/test_fullscreen-api.html
@@ -23,36 +23,37 @@
 
 /** Tests for Bug 545812 **/
 SimpleTest.requestFlakyTimeout("untriaged");
 
 // Run the tests which go full-screen in new windows, as mochitests normally
 // run in an iframe, which by default will not have the allowfullscreen
 // attribute set, so full-screen won't work.
 var gTestWindows = [
-  "file_fullscreen-multiple.html",
-  "file_fullscreen-rollback.html",
-  "file_fullscreen-esc-exit.html",
-  "file_fullscreen-denied.html",
-  "file_fullscreen-api.html",
-  "file_fullscreen-plugins.html",
-  "file_fullscreen-hidden.html",
-  "file_fullscreen-svg-element.html",
-  "file_fullscreen-navigation.html",
-  "file_fullscreen-scrollbar.html",
-  "file_fullscreen-selector.html",
-  "file_fullscreen-shadowdom.html",
-  "file_fullscreen-top-layer.html",
-  "file_fullscreen-backdrop.html",
-  "file_fullscreen-nested.html",
-  "file_fullscreen-prefixed.html",
-  "file_fullscreen-unprefix-disabled.html",
-  "file_fullscreen-lenient-setters.html",
-  "file_fullscreen-table.html",
-  "file_fullscreen-event-order.html",
+  { test: "file_fullscreen-multiple.html" },
+  { test: "file_fullscreen-rollback.html" },
+  { test: "file_fullscreen-esc-exit.html" },
+  { test: "file_fullscreen-denied.html" },
+  { test: "file_fullscreen-api.html" },
+  { test: "file_fullscreen-plugins.html" },
+  { test: "file_fullscreen-hidden.html" },
+  { test: "file_fullscreen-svg-element.html" },
+  { test: "file_fullscreen-navigation.html" },
+  { test: "file_fullscreen-scrollbar.html" },
+  { test: "file_fullscreen-selector.html" },
+  { test: "file_fullscreen-shadowdom.html" },
+  { test: "file_fullscreen-top-layer.html" },
+  { test: "file_fullscreen-backdrop.html" },
+  { test: "file_fullscreen-nested.html" },
+  { test: "file_fullscreen-prefixed.html" },
+  { test: "file_fullscreen-unprefix-disabled.html" },
+  { test: "file_fullscreen-lenient-setters.html" },
+  { test: "file_fullscreen-table.html" },
+  { test: "file_fullscreen-event-order.html" },
+  { test: "file_fullscreen-featurePolicy.html", prefs: [["dom.security.featurePolicy.enabled", true]] },
 ];
 
 var testWindow = null;
 var gTestIndex = 0;
 
 function finish() {
   SimpleTest.finish();
 }
@@ -107,40 +108,45 @@ function waitForEvent(eventTarget, event
     eventTarget.removeEventListener(eventName, listener);
     callback();
   });
 }
 
 function runNextTest() {
   if (gTestIndex < gTestWindows.length) {
     let test = gTestWindows[gTestIndex];
-    if (shouldSkipTest(test)) {
-      info(`Skip test ${test}`);
+    if (shouldSkipTest(test.test)) {
+      info(`Skip test ${test.test}`);
       SimpleTest.executeSoon(runNextTest);
     } else {
-      info(`Run test ${test}`);
-      testWindow = window.open(test, "", "width=500,height=500,scrollbars=yes");
-      // We'll wait for the window to load, then make sure our window is refocused
-      // before starting the test, which will get kicked off on "focus".
-      // This ensures that we're essentially back on the primary "desktop" on
-      // OS X Lion before we run the test.
-      waitForLoadAndPaint(testWindow, function() {
-        SimpleTest.waitForFocus(function() {
+      let promise = ("prefs" in test)
+                      ? SpecialPowers.pushPrefEnv({"set": test.prefs})
+                      : Promise.resolve();
+      promise.then(function() {
+        info(`Run test ${test.test}`);
+        testWindow = window.open(test.test, "", "width=500,height=500,scrollbars=yes");
+        // We'll wait for the window to load, then make sure our window is refocused
+        // before starting the test, which will get kicked off on "focus".
+        // This ensures that we're essentially back on the primary "desktop" on
+        // OS X Lion before we run the test.
+        waitForLoadAndPaint(testWindow, function() {
           SimpleTest.waitForFocus(function() {
-            // For the platforms that support reporting occlusion state (e.g. Mac),
-            // we should wait until the docshell has been activated again,
-            // otherwise, the fullscreen request might be denied.
-            if (testWindow.document.hidden) {
-              waitForEvent(testWindow.document, "visibilitychange", (event) => {
-                return !testWindow.document.hidden;
-              }, testWindow.begin);
-              return;
-            }
-            testWindow.begin();
-          }, testWindow);
+            SimpleTest.waitForFocus(function() {
+              // For the platforms that support reporting occlusion state (e.g. Mac),
+              // we should wait until the docshell has been activated again,
+              // otherwise, the fullscreen request might be denied.
+              if (testWindow.document.hidden) {
+                waitForEvent(testWindow.document, "visibilitychange", (event) => {
+                  return !testWindow.document.hidden;
+                }, testWindow.begin);
+                return;
+              }
+              testWindow.begin();
+            }, testWindow);
+          });
         });
       });
     }
     gTestIndex++;
   } else {
     SimpleTest.finish();
   }
 }
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -65,16 +65,17 @@ 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.
 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 FeturePolicy directives.
 RemovedFullscreenElement=Exited fullscreen because fullscreen element was removed from document.
 FocusedWindowedPluginWhileFullscreen=Exited fullscreen because windowed plugin was focused.
 PointerLockDeniedDisabled=Request for pointer lock was denied because Pointer Lock API is disabled by user preference.
 PointerLockDeniedInUse=Request for pointer lock was denied because the pointer is currently controlled by a different document.
 PointerLockDeniedNotInDocument=Request for pointer lock was denied because the requesting element is not in a document.
 PointerLockDeniedSandboxed=Request for pointer lock was denied because Pointer Lock API is restricted via sandbox.
 PointerLockDeniedHidden=Request for pointer lock was denied because the document is not visible.
 PointerLockDeniedNotFocused=Request for pointer lock was denied because the document is not focused.
--- a/dom/security/featurepolicy/FeaturePolicyUtils.cpp
+++ b/dom/security/featurepolicy/FeaturePolicyUtils.cpp
@@ -25,17 +25,16 @@ struct FeatureMap {
  * DOM Security peer!
  */
 static FeatureMap sSupportedFeatures[] = {
   // TODO: not supported yet!!!
   { "autoplay", FeatureMap::eAll },
   // TODO: not supported yet!!!
   { "camera", FeatureMap::eAll  },
   { "encrypted-media", FeatureMap::eAll  },
-  // TODO: not supported yet!!!
   { "fullscreen", FeatureMap::eAll  },
   // TODO: not supported yet!!!
   { "geolocation", FeatureMap::eAll  },
   // TODO: not supported yet!!!
   { "microphone", FeatureMap::eAll  },
   { "midi", FeatureMap::eAll  },
   { "payment", FeatureMap::eAll  },
   // TODO: not supported yet!!!