Bug 1390801 - FeaturePolicy - part 2 - WebIDL + DOM integration, r=ckerschb
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 01 Oct 2018 08:09:44 +0200
changeset 494693 8edf2b229c9c3f51f15e33169affd1e733043664
parent 494692 eb62db6b03435f82ab697fc81776ccd965aa36e2
child 494694 15516085ee089c2d8411ed934893260e28b7a553
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb
bugs1390801
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 1390801 - FeaturePolicy - part 2 - WebIDL + DOM integration, r=ckerschb
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
dom/bindings/Bindings.conf
dom/html/HTMLIFrameElement.cpp
dom/html/HTMLIFrameElement.h
dom/html/nsGenericHTMLFrameElement.cpp
dom/html/nsGenericHTMLFrameElement.h
dom/locales/en-US/chrome/security/security.properties
dom/security/featurepolicy/Feature.cpp
dom/security/featurepolicy/Feature.h
dom/security/featurepolicy/FeaturePolicy.cpp
dom/security/featurepolicy/FeaturePolicy.h
dom/security/featurepolicy/FeaturePolicyParser.cpp
dom/security/featurepolicy/FeaturePolicyParser.h
dom/security/featurepolicy/FeaturePolicyUtils.cpp
dom/security/featurepolicy/FeaturePolicyUtils.h
dom/security/featurepolicy/moz.build
dom/security/featurepolicy/test/gtest/TestFeaturePolicyParser.cpp
dom/security/featurepolicy/test/mochitest/empty.html
dom/security/featurepolicy/test/mochitest/mochitest.ini
dom/security/featurepolicy/test/mochitest/test_parser.html
dom/security/featurepolicy/test/mochitest/test_parser.html^headers^
dom/webidl/Document.webidl
dom/webidl/FeaturePolicy.webidl
dom/webidl/HTMLIFrameElement.webidl
dom/webidl/moz.build
modules/libpref/init/StaticPrefList.h
xpcom/ds/StaticAtoms.py
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -59,16 +59,17 @@
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/FullscreenChange.h"
 
 #include "mozilla/dom/Attr.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
+#include "mozilla/dom/FeaturePolicy.h"
 #include "mozilla/dom/FramingChecker.h"
 #include "mozilla/dom/HTMLSharedElement.h"
 #include "mozilla/dom/SVGUseElement.h"
 #include "nsGenericHTMLElement.h"
 #include "mozilla/dom/CDATASection.h"
 #include "mozilla/dom/ProcessingInstruction.h"
 #include "nsDOMString.h"
 #include "nsNodeUtils.h"
@@ -1922,16 +1923,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
 
   // Traverse all our nsCOMArrays.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
 
   for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameRequestCallbacks[i]");
     cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i].mCallback);
@@ -2014,16 +2016,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n);
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
 
   tmp->mParentDocument = nullptr;
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
 
   tmp->ClearAllBoxObjects();
@@ -2774,16 +2777,20 @@ nsDocument::StartDocumentLoad(const char
   }
 
   // If this is not a data document, set CSP.
   if (!mLoadedAsData) {
     nsresult rv = InitCSP(aChannel);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  // Initialize FeaturePolicy
+  nsresult rv = InitFeaturePolicy(aChannel);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // XFO needs to be checked after CSP because it is ignored if
   // the CSP defines frame-ancestors.
   if (!FramingChecker::CheckFrameOptions(aChannel, docShell, NodePrincipal())) {
     MOZ_LOG(gCspPRLog, LogLevel::Debug,
             ("XFO doesn't like frame's ancestry, not loading."));
     // stop!  ERROR page!
     aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
   }
@@ -2998,16 +3005,79 @@ nsIDocument::InitCSP(nsIChannel* aChanne
       // stop!  ERROR page!
       aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
     }
   }
   ApplySettingsFromCSP(false);
   return NS_OK;
 }
 
+nsresult
+nsIDocument::InitFeaturePolicy(nsIChannel* aChannel)
+{
+  MOZ_ASSERT(!mFeaturePolicy, "we should only call init once");
+
+  // we need to create a policy here so getting the policy within
+  // ::Policy() can *always* return a non null policy
+  mFeaturePolicy = new FeaturePolicy(this);
+
+  if (!StaticPrefs::dom_security_featurePolicy_enabled()) {
+    return NS_OK;
+  }
+
+  nsAutoString origin;
+  nsresult rv = nsContentUtils::GetUTFOrigin(NodePrincipal(), origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mFeaturePolicy->SetDefaultOrigin(origin);
+
+  RefPtr<FeaturePolicy> parentPolicy = nullptr;
+  if (mDocumentContainer) {
+    nsPIDOMWindowOuter* containerWindow = mDocumentContainer->GetWindow();
+    if (containerWindow) {
+      nsCOMPtr<nsINode> node = containerWindow->GetFrameElementInternal();
+      if (node) {
+        HTMLIFrameElement* iframe = HTMLIFrameElement::FromNode(node);
+        if (iframe) {
+          parentPolicy = iframe->Policy();
+        }
+      }
+    }
+  }
+
+  if (parentPolicy) {
+    // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
+    mFeaturePolicy->InheritPolicy(parentPolicy);
+  }
+
+  nsCOMPtr<nsIHttpChannel> httpChannel;
+  rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!httpChannel) {
+    return NS_OK;
+  }
+
+  // query the policy from the header
+  nsAutoCString value;
+  rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Feature-Policy"),
+                                      value);
+  if (NS_SUCCEEDED(rv)) {
+    mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
+                                      origin, EmptyString(),
+                                      false /* 'src' enabled */);
+  }
+
+  return NS_OK;
+}
+
 void
 nsDocument::StopDocumentLoad()
 {
   if (mParser) {
     mParserAborted = true;
     mParser->Terminate();
   }
 }
@@ -10159,16 +10229,27 @@ nsIDocument::MaybeResolveReadyForIdle()
 {
   IgnoredErrorResult rv;
   Promise* readyPromise = GetDocumentReadyForIdle(rv);
   if (readyPromise) {
     readyPromise->MaybeResolve(this);
   }
 }
 
+FeaturePolicy*
+nsIDocument::Policy() const
+{
+  MOZ_ASSERT(StaticPrefs::dom_security_featurePolicy_enabled());
+
+  // The policy is created when the document is initialized. We _must_ have a
+  // policy here.
+  MOZ_ASSERT(mFeaturePolicy);
+  return mFeaturePolicy;
+}
+
 nsIDOMXULCommandDispatcher*
 nsIDocument::GetCommandDispatcher()
 {
   // Only chrome documents are allowed to use command dispatcher.
   if (!nsContentUtils::IsChromeDoc(this)) {
     return nullptr;
   }
   if (!mCommandDispatcher) {
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -159,16 +159,17 @@ class DocumentTimeline;
 class DocumentType;
 class DOMImplementation;
 class DOMIntersectionObserver;
 class DOMStringList;
 class Element;
 struct ElementCreationOptions;
 class Event;
 class EventTarget;
+class FeaturePolicy;
 class FontFaceSet;
 class FrameRequestCallback;
 class ImageTracker;
 class HTMLBodyElement;
 class HTMLSharedElement;
 class HTMLImageElement;
 struct LifecycleCallbackArgs;
 class Link;
@@ -1435,16 +1436,18 @@ public:
   //
   already_AddRefed<nsSimpleContentList> BlockedTrackingNodes() const;
 
 protected:
   friend class nsUnblockOnloadEvent;
 
   nsresult InitCSP(nsIChannel* aChannel);
 
+  nsresult InitFeaturePolicy(nsIChannel* aChannel);
+
   void PostUnblockOnloadEvent();
 
   void DoUnblockOnload();
 
   void ClearAllBoxObjects();
 
   void MaybeEndOutermostXBLUpdate();
 
@@ -3768,16 +3771,19 @@ public:
     return mAllowPaymentRequest;
   }
 
   void SetAllowPaymentRequest(bool aAllowPaymentRequest)
   {
     mAllowPaymentRequest = aAllowPaymentRequest;
   }
 
+  mozilla::dom::FeaturePolicy*
+  Policy() const;
+
   bool IsShadowDOMEnabled() const
   {
     return mIsShadowDOMEnabled;
   }
 
   bool ModuleScriptsEnabled();
 
   /**
@@ -4081,16 +4087,18 @@ protected:
   // Last time this document or a one of its sub-documents was focused.  If
   // focus has never occurred then mLastFocusTime.IsNull() will be true.
   mozilla::TimeStamp mLastFocusTime;
 
   mozilla::EventStates mDocumentState;
 
   RefPtr<mozilla::dom::Promise> mReadyForIdle;
 
+  RefPtr<mozilla::dom::FeaturePolicy> mFeaturePolicy;
+
   // True if BIDI is enabled.
   bool mBidiEnabled : 1;
   // True if a MathML element has ever been owned by this document.
   bool mMathMLEnabled : 1;
 
   // True if this document is the initial document for a window.  This should
   // basically be true only for documents that exist in newly-opened windows or
   // documents created to satisfy a GetDocument() on a window when there's no
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -692,16 +692,20 @@ DOMInterfaces = {
 'PluginArray': {
     'nativeType': 'nsPluginArray',
 },
 
 'PluginTag': {
     'nativeType': 'nsIPluginTag',
 },
 
+'Policy': {
+    'nativeType': 'mozilla::dom::FeaturePolicy',
+},
+
 'Position': {
     'headerFile': 'nsGeoPosition.h'
 },
 
 'PromiseDebugging': {
     'concrete': False,
 },
 
--- a/dom/html/HTMLIFrameElement.cpp
+++ b/dom/html/HTMLIFrameElement.cpp
@@ -1,23 +1,26 @@
 /* -*- 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 "mozilla/dom/HTMLIFrameElement.h"
 #include "mozilla/dom/HTMLIFrameElementBinding.h"
+#include "mozilla/dom/FeaturePolicy.h"
 #include "mozilla/MappedDeclarations.h"
+#include "mozilla/StaticPrefs.h"
 #include "nsMappedAttributes.h"
 #include "nsAttrValueInlines.h"
 #include "nsError.h"
 #include "nsStyleConsts.h"
 #include "nsContentUtils.h"
 #include "nsSandboxFlags.h"
+#include "nsNetUtil.h"
 
 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(IFrame)
 
 namespace mozilla {
 namespace dom {
 
 // static
 const DOMTokenListSupportedToken HTMLIFrameElement::sSupportedSandboxTokens[] = {
@@ -26,24 +29,43 @@ const DOMTokenListSupportedToken HTMLIFr
 #undef SANDBOX_KEYWORD
   nullptr
 };
 
 HTMLIFrameElement::HTMLIFrameElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
                                      FromParser aFromParser)
   : nsGenericHTMLFrameElement(std::move(aNodeInfo), aFromParser)
 {
+  if (StaticPrefs::dom_security_featurePolicy_enabled()) {
+    mFeaturePolicy = new FeaturePolicy(this);
+  }
 }
 
 HTMLIFrameElement::~HTMLIFrameElement()
 {
 }
 
 NS_IMPL_ELEMENT_CLONE(HTMLIFrameElement)
 
+nsresult
+HTMLIFrameElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                              nsIContent* aBindingParent)
+{
+  nsresult rv = nsGenericHTMLFrameElement::BindToTree(aDocument, aParent,
+                                                      aBindingParent);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (StaticPrefs::dom_security_featurePolicy_enabled()) {
+    RefreshFeaturePolicy();
+  }
+  return NS_OK;
+}
+
 bool
 HTMLIFrameElement::ParseAttribute(int32_t aNamespaceID,
                                   nsAtom* aAttribute,
                                   const nsAString& aValue,
                                   nsIPrincipal* aMaybeScriptedPrincipal,
                                   nsAttrValue& aResult)
 {
   if (aNamespaceID == kNameSpaceID_None) {
@@ -145,16 +167,22 @@ HTMLIFrameElement::AfterSetAttr(int32_t 
     if (aName == nsGkAtoms::sandbox) {
       if (mFrameLoader) {
         // If we have an nsFrameLoader, apply the new sandbox flags.
         // Since this is called after the setter, the sandbox flags have
         // alreay been updated.
         mFrameLoader->ApplySandboxFlags(GetSandboxFlags());
       }
     }
+    if ((aName == nsGkAtoms::allow ||
+         aName == nsGkAtoms::src ||
+         aName == nsGkAtoms::sandbox) &&
+        StaticPrefs::dom_security_featurePolicy_enabled()) {
+      RefreshFeaturePolicy();
+    }
   }
   return nsGenericHTMLFrameElement::AfterSetAttr(aNameSpaceID, aName,
                                                  aValue, aOldValue,
                                                  aMaybeScriptedPrincipal,
                                                  aNotify);
 }
 
 nsresult
@@ -178,26 +206,110 @@ HTMLIFrameElement::AfterMaybeChangeAttr(
       // Don't propagate errors from LoadSrc. The attribute was successfully
       // set/unset, that's what we should reflect.
       LoadSrc();
     }
   }
 }
 
 uint32_t
-HTMLIFrameElement::GetSandboxFlags()
+HTMLIFrameElement::GetSandboxFlags() const
 {
   const nsAttrValue* sandboxAttr = GetParsedAttr(nsGkAtoms::sandbox);
   // No sandbox attribute, no sandbox flags.
   if (!sandboxAttr) {
     return SANDBOXED_NONE;
   }
   return nsContentUtils::ParseSandboxAttributeToFlags(sandboxAttr);
 }
 
 JSObject*
 HTMLIFrameElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return HTMLIFrameElement_Binding::Wrap(aCx, this, aGivenProto);
 }
 
+FeaturePolicy*
+HTMLIFrameElement::Policy() const
+{
+  MOZ_ASSERT(StaticPrefs::dom_security_featurePolicy_enabled());
+  return mFeaturePolicy;
+}
+
+nsresult
+HTMLIFrameElement::GetFeaturePolicyDefaultOrigin(nsAString& aDefaultOrigin) const
+{
+  aDefaultOrigin.Truncate();
+
+  nsresult rv;
+  nsAutoString src;
+  GetURIAttr(nsGkAtoms::src, nullptr, src);
+
+  nsCOMPtr<nsIURI> nodeURI;
+  if (!src.IsEmpty()) {
+    nsCOMPtr<nsIURI> baseURI = OwnerDoc()->GetBaseURI();
+
+    rv = NS_NewURI(getter_AddRefs(nodeURI), src,
+                   OwnerDoc()->GetDocumentCharacterSet(),
+                   baseURI);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      nodeURI = nullptr;
+    }
+  }
+
+  if (!nodeURI) {
+    if (OwnerDoc()->GetSandboxFlags() & SANDBOXED_ORIGIN) {
+      return NS_OK;
+    }
+
+    nodeURI = OwnerDoc()->GetDocumentURI();
+  }
+
+  nsAutoString origin;
+  rv = nsContentUtils::GetUTFOrigin(nodeURI, origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  aDefaultOrigin.Assign(origin);
+  return NS_OK;
+}
+
+void
+HTMLIFrameElement::RefreshFeaturePolicy()
+{
+  MOZ_ASSERT(StaticPrefs::dom_security_featurePolicy_enabled());
+  mFeaturePolicy->ResetDeclaredPolicy();
+
+  // The origin can change if 'src' attribute changes.
+  nsAutoString origin;
+  nsresult rv = GetFeaturePolicyDefaultOrigin(origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  mFeaturePolicy->SetDefaultOrigin(origin);
+
+  nsAutoString allow;
+  GetAttr(nsGkAtoms::allow, allow);
+
+  if (!allow.IsEmpty()) {
+    nsAutoString documentOrigin;
+    if (OwnerDoc()->GetSandboxFlags() ^ SANDBOXED_ORIGIN) {
+      nsresult rv = nsContentUtils::GetUTFOrigin(NodePrincipal(), documentOrigin);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return;
+      }
+    }
+
+    // Set or reset the FeaturePolicy directives.
+    mFeaturePolicy->SetDeclaredPolicy(OwnerDoc(), allow, documentOrigin,
+                                      origin, true /* 'src' enabled */);
+  }
+
+  mFeaturePolicy->InheritPolicy(OwnerDoc()->Policy());
+
+  // TODO: https://wicg.github.io/feature-policy/#process-feature-policy-attributes
+  // requires to check allowfullscreen, allowpaymentrequest and allowusermediarequest
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLIFrameElement.h
+++ b/dom/html/HTMLIFrameElement.h
@@ -28,27 +28,29 @@ public:
 
   // Element
   virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override
   {
     return true;
   }
 
   // nsIContent
+  virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                              nsIContent* aBindingParent) override;
   virtual bool ParseAttribute(int32_t aNamespaceID,
                                 nsAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsIPrincipal* aMaybeScriptedPrincipal,
                                 nsAttrValue& aResult) override;
   NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
 
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 
-  uint32_t GetSandboxFlags();
+  uint32_t GetSandboxFlags() const;
 
   // Web IDL binding methods
   void GetSrc(nsString& aSrc) const
   {
     GetURIAttr(nsGkAtoms::src, nullptr, aSrc);
   }
   void SetSrc(const nsAString& aSrc, nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError)
   {
@@ -111,16 +113,24 @@ public:
   void GetAlign(DOMString& aAlign)
   {
     GetHTMLAttr(nsGkAtoms::align, aAlign);
   }
   void SetAlign(const nsAString& aAlign, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
   }
+  void GetAllow(DOMString& aAllow)
+  {
+    GetHTMLAttr(nsGkAtoms::allow, aAllow);
+  }
+  void SetAllow(const nsAString& aAllow, ErrorResult& aError)
+  {
+    SetHTMLAttr(nsGkAtoms::allow, aAllow, aError);
+  }
   void GetScrolling(DOMString& aScrolling)
   {
     GetHTMLAttr(nsGkAtoms::scrolling, aScrolling);
   }
   void SetScrolling(const nsAString& aScrolling, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::scrolling, aScrolling, aError);
   }
@@ -183,16 +193,19 @@ public:
 
   // The fullscreen flag is set to true only when requestFullscreen is
   // explicitly called on this <iframe> element. In case this flag is
   // set, the fullscreen state of this element will not be reverted
   // automatically when its subdocument exits fullscreen.
   bool FullscreenFlag() const { return mFullscreenFlag; }
   void SetFullscreenFlag(bool aValue) { mFullscreenFlag = aValue; }
 
+  FeaturePolicy*
+  Policy() const;
+
 protected:
   virtual ~HTMLIFrameElement();
 
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
                                 const nsAttrValue* aValue,
                                 const nsAttrValue* aOldValue,
@@ -203,16 +216,21 @@ protected:
                                           bool aNotify) override;
 
 private:
   static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                     MappedDeclarations&);
 
   static const DOMTokenListSupportedToken sSupportedSandboxTokens[];
 
+  void RefreshFeaturePolicy();
+
+  nsresult
+  GetFeaturePolicyDefaultOrigin(nsAString& aDefaultOrigin) const;
+
   /**
    * This function is called by AfterSetAttr and OnAttrSetButNotChanged.
    * This function will be called by AfterSetAttr whether the attribute is being
    * set or unset.
    *
    * @param aNamespaceID the namespace of the attr being set
    * @param aName the localname of the attribute being set
    * @param aNotify Whether we plan to notify document observers.
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -30,27 +30,29 @@ using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericHTMLFrameElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGenericHTMLFrameElement,
                                                   nsGenericHTMLElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpenerWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserElementAPI)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsGenericHTMLFrameElement,
                                                 nsGenericHTMLElement)
   if (tmp->mFrameLoader) {
     tmp->mFrameLoader->Destroy();
   }
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameLoader)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpenerWindow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserElementAPI)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(nsGenericHTMLFrameElement,
                                              nsGenericHTMLElement,
                                              nsIFrameLoaderOwner,
                                              nsIDOMMozBrowserFrame,
                                              nsIMozBrowserFrame,
                                              nsGenericHTMLFrameElement)
--- a/dom/html/nsGenericHTMLFrameElement.h
+++ b/dom/html/nsGenericHTMLFrameElement.h
@@ -5,16 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsGenericHTMLFrameElement_h
 #define nsGenericHTMLFrameElement_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/nsBrowserElement.h"
+#include "mozilla/dom/FeaturePolicy.h"
 
 #include "nsFrameLoader.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIDOMEventListener.h"
 #include "nsIFrameLoaderOwner.h"
 #include "nsIMozBrowserFrame.h"
 
 namespace mozilla {
@@ -122,16 +123,19 @@ protected:
                                           const nsAttrValueOrString& aValue,
                                           bool aNotify) override;
 
   RefPtr<nsFrameLoader> mFrameLoader;
   nsCOMPtr<nsPIDOMWindowOuter> mOpenerWindow;
 
   nsCOMPtr<nsIPrincipal> mSrcTriggeringPrincipal;
 
+  // Used by <iframe> only.
+  RefPtr<mozilla::dom::FeaturePolicy> mFeaturePolicy;
+
   /**
    * True if we have already loaded the frame's original src
    */
   bool mSrcLoadHappened;
 
   /**
    * True when the element is created by the parser using the
    * NS_FROM_PARSER_NETWORK flag.
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -96,10 +96,12 @@ BlockSubresourceFTP=Loading FTP subresource within http(s) page not allowed (Blocked loading of: “%1$S”)
 # %1$S is the browser name "brandShortName"; %2$S is the URL of the upgraded request; %1$S is the upgraded scheme.
 BrowserUpgradeInsecureDisplayRequest = %1$S is upgrading an insecure display request ‘%2$S’ to use ‘%3$S’
 # LOCALIZATION NOTE (RunningClearSiteDataValue):
 # %S is the URI of the resource whose data was cleaned up
 RunningClearSiteDataValue=Clear-Site-Data header forced the clean up of “%S” data.
 UnknownClearSiteDataValue=Clear-Site-Data header found. Unknown value “%S”.
 
 FeaturePolicyUnsupportedFeatureName=Feature Policy: Skipping unsupported feature name “%S”.
-# TODO: would be nice to add a link to the Feature-Policy MDN documentation here.
+# TODO: would be nice to add a link to the Feature-Policy MDN documentation here. See bug 1449501
+FeaturePolicyInvalidEmptyAllowValue= Feature Policy: Skipping empty allow list for feature: “%S”.
+# TODO: would be nice to add a link to the Feature-Policy MDN documentation here. See bug 1449501
 FeaturePolicyInvalidAllowValue=Feature Policy: Skipping unsupported allow value “%S”.
--- a/dom/security/featurepolicy/Feature.cpp
+++ b/dom/security/featurepolicy/Feature.cpp
@@ -1,19 +1,44 @@
 /* -*- 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 "Feature.h"
 
-#include "nsIURI.h"
+using namespace mozilla::dom;
+
+void
+Feature::GetWhiteListedOrigins(nsTArray<nsString>& aList) const
+{
+  MOZ_ASSERT(mPolicy == eWhiteList);
+  aList.AppendElements(mWhiteListedOrigins);
+}
 
-using namespace mozilla::dom;
+bool
+Feature::Allows(const nsAString& aOrigin) const
+{
+  if (mPolicy == eNone) {
+    return false;
+  }
+
+  if (mPolicy == eAll) {
+    return true;
+  }
+
+  for (const nsString& whiteListedOrigin : mWhiteListedOrigins) {
+    if (whiteListedOrigin.Equals(aOrigin)) {
+      return true;
+    }
+  }
+
+  return false;
+}
 
 Feature::Feature(const nsAString& aFeatureName)
   : mFeatureName(aFeatureName)
   , mPolicy(eWhiteList)
 {}
 
 Feature::~Feature() = default;
 
@@ -22,61 +47,52 @@ Feature::Name() const
 {
   return mFeatureName;
 }
 
 void
 Feature::SetAllowsNone()
 {
   mPolicy = eNone;
-  mWhiteList.Clear();
+  mWhiteListedOrigins.Clear();
 }
 
 bool
 Feature::AllowsNone() const
 {
   return mPolicy == eNone;
 }
 
 void
 Feature::SetAllowsAll()
 {
   mPolicy = eAll;
-  mWhiteList.Clear();
+  mWhiteListedOrigins.Clear();
 }
 
 bool
 Feature::AllowsAll() const
 {
   return mPolicy == eAll;
 }
 
 void
-Feature::AppendURIToWhiteList(nsIURI* aURI)
+Feature::AppendOriginToWhiteList(const nsAString& aOrigin)
 {
   mPolicy = eWhiteList;
-  mWhiteList.AppendElement(aURI);
+  mWhiteListedOrigins.AppendElement(aOrigin);
 }
 
 bool
-Feature::WhiteListContains(nsIURI* aURI) const
+Feature::WhiteListContains(const nsAString& aOrigin) const
 {
-  MOZ_ASSERT(aURI);
-
   if (!IsWhiteList()) {
     return false;
   }
 
-  bool equal = false;
-  for (nsIURI* uri : mWhiteList) {
-    if (NS_SUCCEEDED(uri->Equals(aURI, &equal)) && equal) {
-      return true;
-    }
-  }
-
-  return false;
+  return mWhiteListedOrigins.Contains(aOrigin);
 }
 
 bool
 Feature::IsWhiteList() const
 {
   return mPolicy == eWhiteList;
 }
--- a/dom/security/featurepolicy/Feature.h
+++ b/dom/security/featurepolicy/Feature.h
@@ -6,18 +6,16 @@
 
 #ifndef mozilla_dom_Feature_h
 #define mozilla_dom_Feature_h
 
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsCOMPtr.h"
 
-class nsIURI;
-
 namespace mozilla {
 namespace dom {
 
 class Feature final
 {
 public:
   explicit Feature(const nsAString& aFeatureName);
 
@@ -34,39 +32,45 @@ public:
 
   void
   SetAllowsAll();
 
   bool
   AllowsAll() const;
 
   void
-  AppendURIToWhiteList(nsIURI* aURI);
+  AppendOriginToWhiteList(const nsAString& aOrigin);
+
+  void
+  GetWhiteListedOrigins(nsTArray<nsString>& aList) const;
 
   bool
-  WhiteListContains(nsIURI* aURI) const;
+  WhiteListContains(const nsAString& aOrigin) const;
 
   bool
   IsWhiteList() const;
 
+  bool
+  Allows(const nsAString& aOrigin) const;
+
 private:
   nsString mFeatureName;
 
   enum Policy {
     // denotes a policy of "feature 'none'"
     eNone,
 
     // denotes a policy of "feature *"
     eAll,
 
     // denotes a policy of "feature bar.com foo.com"
     eWhiteList,
   };
 
   Policy mPolicy;
 
-  nsTArray<nsCOMPtr<nsIURI>> mWhiteList;
+  nsTArray<nsString> mWhiteListedOrigins;
 };
 
 } // dom namespace
 } // mozilla namespace
 
 #endif // mozilla_dom_Feature_h
new file mode 100644
--- /dev/null
+++ b/dom/security/featurepolicy/FeaturePolicy.cpp
@@ -0,0 +1,184 @@
+/* -*- 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 "FeaturePolicy.h"
+#include "mozilla/dom/FeaturePolicyBinding.h"
+#include "mozilla/dom/FeaturePolicyParser.h"
+#include "mozilla/dom/FeaturePolicyUtils.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FeaturePolicy, mParentNode)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FeaturePolicy)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FeaturePolicy)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FeaturePolicy)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+FeaturePolicy::FeaturePolicy(nsINode* aNode)
+  : mParentNode(aNode)
+{}
+
+void
+FeaturePolicy::InheritPolicy(FeaturePolicy* aParentPolicy)
+{
+  MOZ_ASSERT(aParentPolicy);
+
+  mInheritedDeniedFeatureNames.Clear();
+
+  RefPtr<FeaturePolicy> dest = this;
+  RefPtr<FeaturePolicy> src = aParentPolicy;
+  nsString origin = mDefaultOrigin;
+  FeaturePolicyUtils::ForEachFeature([dest, src, origin](const char* aFeatureName) {
+    nsString featureName;
+    featureName.AppendASCII(aFeatureName);
+
+    // If the destination has a declared feature (via the HTTP header or 'allow'
+    // attribute) we allow the feature only if both parent FeaturePolicy and this
+    // one allow the current origin.
+    if (dest->HasDeclaredFeature(featureName)) {
+      if (!dest->AllowsFeatureInternal(featureName, origin) ||
+          !src->AllowsFeatureInternal(featureName, origin)) {
+        dest->SetInheritedDeniedFeature(featureName);
+      }
+      return;
+    }
+
+    // If there was not a declared feature, we allow the feature if the parent
+    // FeaturePolicy allows the current origin.
+    if (!src->AllowsFeatureInternal(featureName, origin)) {
+      dest->SetInheritedDeniedFeature(featureName);
+    }
+  });
+}
+
+void
+FeaturePolicy::SetInheritedDeniedFeature(const nsAString& aFeatureName)
+{
+  MOZ_ASSERT(!HasInheritedDeniedFeature(aFeatureName));
+  mInheritedDeniedFeatureNames.AppendElement(aFeatureName);
+}
+
+bool
+FeaturePolicy::HasInheritedDeniedFeature(const nsAString& aFeatureName) const
+{
+  return mInheritedDeniedFeatureNames.Contains(aFeatureName);
+}
+
+bool
+FeaturePolicy::HasDeclaredFeature(const nsAString& aFeatureName) const
+{
+  for (const Feature& feature : mFeatures) {
+    if (feature.Name().Equals(aFeatureName)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void
+FeaturePolicy::SetDeclaredPolicy(nsIDocument* aDocument,
+                                 const nsAString& aPolicyString,
+                                 const nsAString& aSelfOrigin,
+                                 const nsAString& aSrcOrigin,
+                                 bool aSrcEnabled)
+{
+  ResetDeclaredPolicy();
+
+  Unused << NS_WARN_IF(!FeaturePolicyParser::ParseString(aPolicyString,
+                                                         aDocument,
+                                                         aSelfOrigin,
+                                                         aSrcOrigin,
+                                                         aSrcEnabled,
+                                                         mFeatures));
+}
+
+void
+FeaturePolicy::ResetDeclaredPolicy()
+{
+  mFeatures.Clear();
+}
+
+JSObject*
+FeaturePolicy::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return Policy_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+bool
+FeaturePolicy::AllowsFeature(const nsAString& aFeatureName,
+                             const Optional<nsAString>& aOrigin) const
+{
+  return AllowsFeatureInternal(aFeatureName,
+                               aOrigin.WasPassed()
+                                 ? aOrigin.Value()
+                                 : mDefaultOrigin);
+}
+
+bool
+FeaturePolicy::AllowsFeatureInternal(const nsAString& aFeatureName,
+                                     const nsAString& aOrigin) const
+{
+  // Let's see if have to disable this feature because inherited policy.
+  if (HasInheritedDeniedFeature(aFeatureName)) {
+    return false;
+  }
+
+  for (const Feature& feature : mFeatures) {
+    if (feature.Name().Equals(aFeatureName)) {
+      return feature.Allows(aOrigin);
+    }
+  }
+
+  return FeaturePolicyUtils::AllowDefaultFeature(aFeatureName, mDefaultOrigin,
+                                                 aOrigin);
+}
+
+void
+FeaturePolicy::AllowedFeatures(nsTArray<nsString>& aAllowedFeatures)
+{
+  RefPtr<FeaturePolicy> self = this;
+  FeaturePolicyUtils::ForEachFeature([self, &aAllowedFeatures](const char* aFeatureName) {
+    nsString featureName;
+    featureName.AppendASCII(aFeatureName);
+
+    if (self->AllowsFeatureInternal(featureName, self->mDefaultOrigin)) {
+      aAllowedFeatures.AppendElement(featureName);
+    }
+  });
+}
+
+void
+FeaturePolicy::GetAllowlistForFeature(const nsAString& aFeatureName,
+                                      nsTArray<nsString>& aList) const
+{
+  if (!AllowsFeatureInternal(aFeatureName, mDefaultOrigin)) {
+    return;
+  }
+
+  for (const Feature& feature : mFeatures) {
+    if (feature.Name().Equals(aFeatureName)) {
+      if (feature.AllowsAll()) {
+        aList.AppendElement(NS_LITERAL_STRING("*"));
+        return;
+      }
+
+      feature.GetWhiteListedOrigins(aList);
+      return;
+    }
+  }
+
+  nsString defaultAllowList;
+  FeaturePolicyUtils::DefaultAllowListFeature(aFeatureName, mDefaultOrigin,
+                                              defaultAllowList);
+   if (!defaultAllowList.IsEmpty()) {
+    aList.AppendElement(defaultAllowList);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/security/featurepolicy/FeaturePolicy.h
@@ -0,0 +1,166 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_FeaturePolicy_h
+#define mozilla_dom_FeaturePolicy_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Feature.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+/**
+ * FeaturePolicy
+ * ~~~~~~~~~~~~~
+ *
+ * Each document and each HTMLIFrameElement have a FeaturePolicy object which is
+ * used to allow or deny features in their contexts. FeaturePolicy is active
+ * when pref dom.security.featurePolicy.enabled is set to true.
+ *
+ * FeaturePolicy is composed by a set of directives configured by the
+ * 'Feature-Policy' HTTP Header and the 'allow' attribute in HTMLIFrameElements.
+ * Both header and attribute are parsed by FeaturePolicyParser which returns an
+ * array of Feature objects. Each Feature object has a feature name and one of
+ * these policies:
+ * - eNone - the feature is fully disabled.
+ * - eAll - the feature is allowed.
+ * - eWhitelist - the feature is allowed for a list of origins.
+ *
+ * An interesting element of FeaturePolicy is the inheritance: each context
+ * inherits the feature-policy directives from the parent context, if it exists.
+ * When a context inherits a policy for feature X, it only knows if that feature
+ * is allowed or denied (it ignores the list of whitelist origins for instance).
+ * This information is stored in an array of inherited feature strings because
+ * we care only to know when they are denied.
+ *
+ * FeaturePolicy can be reset if the 'allow' or 'src' attributes change in
+ * HTMLIFrameElements. 'src' attribute is important to compute correcly
+ * the features via FeaturePolicy 'src' keyword.
+ *
+ * When FeaturePolicy must decide if feature X is allowed or denied for the
+ * current origin, it checks if the parent context denied that feature.
+ * If not, it checks if there is a Feature object for that
+ * feature named X and if the origin is allowed or not.
+ *
+ * From a C++ point of view, use FeaturePolicyUtils to obtain the list of
+ * features and to check if they are allowed in the current context.
+ **/
+
+class nsIDocument;
+class nsIHttpChannel;
+class nsINode;
+
+namespace mozilla {
+namespace dom {
+
+class FeaturePolicyUtils;
+
+class FeaturePolicy final : public nsISupports
+                          , public nsWrapperCache
+{
+  friend class FeaturePolicyUtils;
+
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FeaturePolicy)
+
+  explicit FeaturePolicy(nsINode* aNode);
+
+  // A FeaturePolicy must have a default origin, if not in a sandboxed context.
+  // This method must be called before any other exposed WebIDL method or before
+  // checking if a feature is allowed.
+  void
+  SetDefaultOrigin(const nsAString& aOrigin)
+  {
+    // aOrigin can be an empty string if this is a opaque origin.
+    mDefaultOrigin = aOrigin;
+  }
+
+  const nsAString& DefaultOrigin() const
+  {
+    // Returns an empty string if this is an opaque origin.
+    return mDefaultOrigin;
+  }
+
+  // Inherits the policy from the 'parent' context if it exists.
+  void
+  InheritPolicy(FeaturePolicy* aParentFeaturePolicy);
+
+  // Sets the declarative part of the policy. This can be from the HTTP header
+  // or for the 'allow' HTML attribute.
+  void
+  SetDeclaredPolicy(nsIDocument* aDocument,
+                    const nsAString& aPolicyString,
+                    const nsAString& aSelfOrigin,
+                    const nsAString& aSrcOrigin,
+                    bool aSrcEnabled);
+
+  // Clears all the declarative policy directives. This is needed when the
+  // 'allow' attribute or the 'src' attribute change for HTMLIFrameElement's
+  // policy.
+  void
+  ResetDeclaredPolicy();
+
+  // WebIDL internal methods.
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsINode*
+  GetParentObject() const
+  {
+    return mParentNode;
+  }
+
+  // WebIDL explosed methods.
+
+  bool
+  AllowsFeature(const nsAString& aFeatureName,
+                const Optional<nsAString>& aOrigin) const;
+
+  void
+  AllowedFeatures(nsTArray<nsString>& aAllowedFeatures);
+
+  void
+  GetAllowlistForFeature(const nsAString& aFeatureName,
+                         nsTArray<nsString>& aList) const;
+
+private:
+  ~FeaturePolicy() = default;
+
+  bool
+  AllowsFeatureInternal(const nsAString& aFeatureName,
+                        const nsAString& aOrigin) const;
+
+  // Inherits a single denied feature from the parent context.
+  void
+  SetInheritedDeniedFeature(const nsAString& aFeatureName);
+
+  bool
+  HasInheritedDeniedFeature(const nsAString& aFeatureName) const;
+
+  bool
+  HasDeclaredFeature(const nsAString& aFeatureName) const;
+
+  nsCOMPtr<nsINode> mParentNode;
+
+  // This is set in sub-contexts when the parent blocks some feature for the
+  // current context.
+  nsTArray<nsString> mInheritedDeniedFeatureNames;
+
+  // Feature policy for the current context.
+  nsTArray<Feature> mFeatures;
+
+  nsString mDefaultOrigin;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_FeaturePolicy_h
--- a/dom/security/featurepolicy/FeaturePolicyParser.cpp
+++ b/dom/security/featurepolicy/FeaturePolicyParser.cpp
@@ -28,16 +28,30 @@ ReportToConsoleUnsupportedFeature(nsIDoc
                                   NS_LITERAL_CSTRING("Feature Policy"),
                                   aDocument,
                                   nsContentUtils::eSECURITY_PROPERTIES,
                                   "FeaturePolicyUnsupportedFeatureName",
                                   params, ArrayLength(params));
 }
 
 void
+ReportToConsoleInvalidEmptyAllowValue(nsIDocument* aDocument,
+                                      const nsString& aFeatureName)
+{
+  const char16_t* params[] = { aFeatureName.get() };
+
+  nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                  NS_LITERAL_CSTRING("Feature Policy"),
+                                  aDocument,
+                                  nsContentUtils::eSECURITY_PROPERTIES,
+                                  "FeaturePolicyInvalidEmptyAllowValue",
+                                  params, ArrayLength(params));
+}
+
+void
 ReportToConsoleInvalidAllowValue(nsIDocument* aDocument,
                                  const nsString& aValue)
 {
   const char16_t* params[] = { aValue.get() };
 
   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                   NS_LITERAL_CSTRING("Feature Policy"),
                                   aDocument,
@@ -46,21 +60,21 @@ ReportToConsoleInvalidAllowValue(nsIDocu
                                   params, ArrayLength(params));
 }
 
 } // anonymous
 
 /* static */ bool
 FeaturePolicyParser::ParseString(const nsAString& aPolicy,
                                  nsIDocument* aDocument,
-                                 nsIURI* aSelfURI,
+                                 const nsAString& aSelfOrigin,
+                                 const nsAString& aSrcOrigin,
+                                 bool aSrcEnabled,
                                  nsTArray<Feature>& aParsedFeatures)
 {
-  MOZ_ASSERT(aSelfURI);
-
   nsTArray<nsTArray<nsString>> tokens;
   PolicyTokenizer::tokenizePolicy(aPolicy, tokens);
 
   nsTArray<Feature> parsedFeatures;
 
   for (const nsTArray<nsString>& featureTokens : tokens) {
     if (featureTokens.IsEmpty()) {
       continue;
@@ -68,42 +82,81 @@ FeaturePolicyParser::ParseString(const n
 
     if (!FeaturePolicyUtils::IsSupportedFeature(featureTokens[0])) {
       ReportToConsoleUnsupportedFeature(aDocument, featureTokens[0]);
       continue;
     }
 
     Feature feature(featureTokens[0]);
 
-    // we gotta start at 1 here
-    for (uint32_t i = 1; i < featureTokens.Length(); ++i) {
-      const nsString& curVal = featureTokens[i];
-      if (curVal.LowerCaseEqualsASCII("'none'")) {
-      	feature.SetAllowsNone();
-        break;
+    if (featureTokens.Length() == 1) {
+      if (aSrcEnabled) {
+        // Note that this src origin can be empty if opaque.
+        feature.AppendOriginToWhiteList(aSrcOrigin);
+      } else {
+        ReportToConsoleInvalidEmptyAllowValue(aDocument, featureTokens[0]);
+        continue;
       }
+    } else {
+      // we gotta start at 1 here
+      for (uint32_t i = 1; i < featureTokens.Length(); ++i) {
+        const nsString& curVal = featureTokens[i];
+        if (curVal.LowerCaseEqualsASCII("'none'")) {
+          feature.SetAllowsNone();
+          break;
+        }
 
-      if (curVal.EqualsLiteral("*")) {
-      	feature.SetAllowsAll();
+        if (curVal.EqualsLiteral("*")) {
+          feature.SetAllowsAll();
+          break;
+        }
+
+        if (curVal.LowerCaseEqualsASCII("'self'")) {
+          // Opaque origins are passed as empty string.
+          if (!aSelfOrigin.IsEmpty()) {
+            feature.AppendOriginToWhiteList(aSelfOrigin);
+          }
+          continue;
+        }
+
+        if (aSrcEnabled && curVal.LowerCaseEqualsASCII("'src'")) {
+          // Opaque origins are passed as empty string.
+          if (!aSrcOrigin.IsEmpty()) {
+            feature.AppendOriginToWhiteList(aSrcOrigin);
+          }
+          continue;
+        }
+
+        nsCOMPtr<nsIURI> uri;
+        nsresult rv = NS_NewURI(getter_AddRefs(uri), curVal);
+        if (NS_FAILED(rv)) {
+          ReportToConsoleInvalidAllowValue(aDocument, curVal);
+          continue;
+        }
+
+        nsAutoString origin;
+        rv = nsContentUtils::GetUTFOrigin(uri, origin);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          ReportToConsoleInvalidAllowValue(aDocument, curVal);
+          continue;
+        }
+
+        feature.AppendOriginToWhiteList(origin);
+      }
+    }
+
+    // No duplicate!
+    bool found = false;
+    for (const Feature& parsedFeature : parsedFeatures) {
+      if (parsedFeature.Name() == feature.Name()) {
+        found = true;
         break;
       }
-
-      if (curVal.LowerCaseEqualsASCII("'self'")) {
-        feature.AppendURIToWhiteList(aSelfURI);
-        continue;
-      }
-
-      nsCOMPtr<nsIURI> uri;
-      nsresult rv = NS_NewURI(getter_AddRefs(uri), curVal);
-      if (NS_FAILED(rv)) {
-        ReportToConsoleInvalidAllowValue(aDocument, curVal);
-        continue;
-      }
-
-      feature.AppendURIToWhiteList(uri);
     }
 
-    parsedFeatures.AppendElement(feature);
+    if (!found) {
+      parsedFeatures.AppendElement(feature);
+    }
   }
 
   aParsedFeatures.SwapElements(parsedFeatures);
   return true;
 }
--- a/dom/security/featurepolicy/FeaturePolicyParser.h
+++ b/dom/security/featurepolicy/FeaturePolicyParser.h
@@ -15,19 +15,23 @@ class nsIURI;
 namespace mozilla {
 namespace dom {
 
 class Feature;
 
 class FeaturePolicyParser final
 {
 public:
+  // aSelfOrigin must not be empty. if aSrcOrigin is empty, the parsing will not
+  // support 'src' as valid allow directive value.
   static bool
   ParseString(const nsAString& aPolicy,
               nsIDocument* aDocument,
-              nsIURI* aSelfURI,
+              const nsAString& aSelfOrigin,
+              const nsAString& aSrcOrigin,
+              bool aSrcEnabled,
               nsTArray<Feature>& aParsedFeatures);
 };
 
 } // dom namespace
 } // mozilla namespace
 
 #endif // mozilla_dom_FeaturePolicyParser_h
--- a/dom/security/featurepolicy/FeaturePolicyUtils.cpp
+++ b/dom/security/featurepolicy/FeaturePolicyUtils.cpp
@@ -1,27 +1,148 @@
 /* -*- 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 "FeaturePolicyUtils.h"
+#include "mozilla/dom/FeaturePolicy.h"
+#include "mozilla/StaticPrefs.h"
+#include "nsIDocument.h"
 
 using namespace mozilla::dom;
 
-static const char* sSupportedFeatures[] = {
-  "camera",
-  "geolocation",
-  "microphone",
+struct FeatureMap {
+  const char* mFeatureName;
+
+  enum {
+    eAll,
+    eSelf,
+  } mDefaultAllowList;
+};
+
+/*
+ * IMPORTANT: Do not change this list without review from a DOM peer _AND_ a
+ * DOM Security peer!
+ */
+static FeatureMap sSupportedFeatures[] = {
+  // TODO: not supported yet!!!
+  { "accelerometer", FeatureMap::eSelf },
+  // TODO: not supported yet!!!
+  { "ambient-light-sensor", FeatureMap::eSelf },
+  // TODO: not supported yet!!!
+  { "autoplay", FeatureMap::eSelf },
+  // TODO: not supported yet!!!
+  { "camera", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "encrypted-media", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "fullscreen", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "geolocation", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "gyroscope", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "magnetometer", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "microphone", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "midi", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "payment", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "picture-in-picture", FeatureMap::eAll  },
+  // TODO: not supported yet!!!
+  { "speaker", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "usb", FeatureMap::eSelf  },
+  // TODO: not supported yet!!!
+  { "vr", FeatureMap::eSelf  },
 };
 
 /* static */ bool
 FeaturePolicyUtils::IsSupportedFeature(const nsAString& aFeatureName)
 {
   uint32_t numFeatures = (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0]));
-  for (uint32_t i = 0; i < numFeatures; i++) {
-    if (aFeatureName.LowerCaseEqualsASCII(sSupportedFeatures[i])) {
+  for (uint32_t i = 0; i < numFeatures; ++i) {
+    if (aFeatureName.LowerCaseEqualsASCII(sSupportedFeatures[i].mFeatureName)) {
       return true;
     }
   }
   return false;
 }
+
+/* static */ void
+FeaturePolicyUtils::ForEachFeature(const std::function<void(const char*)>& aCallback)
+{
+  uint32_t numFeatures = (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0]));
+  for (uint32_t i = 0; i < numFeatures; ++i) {
+    aCallback(sSupportedFeatures[i].mFeatureName);
+  }
+}
+
+/* static */ void
+FeaturePolicyUtils::DefaultAllowListFeature(const nsAString& aFeatureName,
+                                            const nsAString& aDefaultOrigin,
+                                            nsAString& aDefaultAllowList)
+{
+  uint32_t numFeatures = (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0]));
+  for (uint32_t i = 0; i < numFeatures; ++i) {
+    if (aFeatureName.LowerCaseEqualsASCII(sSupportedFeatures[i].mFeatureName)) {
+      switch (sSupportedFeatures[i].mDefaultAllowList) {
+        case FeatureMap::eAll:
+          aDefaultAllowList.AppendASCII("*");
+          return;
+
+        case FeatureMap::eSelf:
+          aDefaultAllowList = aDefaultOrigin;
+          return;
+
+        default:
+          MOZ_CRASH("Unknown default value");
+      }
+    }
+  }
+}
+
+/* static */ bool
+FeaturePolicyUtils::AllowDefaultFeature(const nsAString& aFeatureName,
+                                        const nsAString& aDefaultOrigin,
+                                        const nsAString& aOrigin)
+{
+  uint32_t numFeatures = (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0]));
+  for (uint32_t i = 0; i < numFeatures; ++i) {
+    if (aFeatureName.LowerCaseEqualsASCII(sSupportedFeatures[i].mFeatureName)) {
+      switch (sSupportedFeatures[i].mDefaultAllowList) {
+        case FeatureMap::eAll:
+          return true;
+        case FeatureMap::eSelf:
+          return aDefaultOrigin == aOrigin;
+        default:
+          MOZ_CRASH("Unknown default value");
+      }
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/* static */ bool
+FeaturePolicyUtils::IsFeatureAllowed(nsIDocument* aDocument,
+                                     const nsAString& aFeatureName)
+{
+  MOZ_ASSERT(aDocument);
+
+  if (!StaticPrefs::dom_security_featurePolicy_enabled()) {
+    return true;
+  }
+
+  if (!aDocument->IsHTMLDocument()) {
+    return true;
+  }
+
+  FeaturePolicy* policy = aDocument->Policy();
+  MOZ_ASSERT(policy);
+
+  return policy->AllowsFeatureInternal(aFeatureName, policy->DefaultOrigin());
+}
--- a/dom/security/featurepolicy/FeaturePolicyUtils.h
+++ b/dom/security/featurepolicy/FeaturePolicyUtils.h
@@ -3,25 +3,43 @@
 /* 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/. */
 
 #ifndef mozilla_dom_FeaturePolicyUtils_h
 #define mozilla_dom_FeaturePolicyUtils_h
 
 #include "nsString.h"
-#include "nsTArray.h"
-#include "nsCOMPtr.h"
+#include <functional>
+
+class nsIDocument;
 
 namespace mozilla {
 namespace dom {
 
 class FeaturePolicyUtils final
 {
 public:
   static bool
+  IsFeatureAllowed(nsIDocument* aDocument,
+                   const nsAString& aFeatureName);
+
+  static bool
   IsSupportedFeature(const nsAString& aFeatureName);
+
+  static void
+  ForEachFeature(const std::function<void(const char*)>& aCallback);
+
+  static void
+  DefaultAllowListFeature(const nsAString& aFeatureName,
+                          const nsAString& aDefaultOrigin,
+                          nsAString& aDefaultAllowList);
+
+  static bool
+  AllowDefaultFeature(const nsAString& aFeatureName,
+                      const nsAString& aDefaultOrigin,
+                      const nsAString& aOrigin);
 };
 
 } // dom namespace
 } // mozilla namespace
 
 #endif // mozilla_dom_FeaturePolicyUtils_h
--- a/dom/security/featurepolicy/moz.build
+++ b/dom/security/featurepolicy/moz.build
@@ -3,24 +3,27 @@
 # 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/.
 
 with Files("**"):
     BUG_COMPONENT = ('Core', 'DOM: Security')
 
 TEST_DIRS += [ 'test/gtest' ]
+MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
 
 EXPORTS.mozilla.dom += [
   'Feature.h',
+  'FeaturePolicy.h',
   'FeaturePolicyParser.h',
   'FeaturePolicyUtils.h',
 ]
 
-SOURCES += [
+UNIFIED_SOURCES += [
   'Feature.cpp',
+  'FeaturePolicy.cpp',
   'FeaturePolicyParser.cpp',
   'FeaturePolicyUtils.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
--- a/dom/security/featurepolicy/test/gtest/TestFeaturePolicyParser.cpp
+++ b/dom/security/featurepolicy/test/gtest/TestFeaturePolicyParser.cpp
@@ -7,51 +7,38 @@
 #include "gtest/gtest.h"
 #include "mozilla/dom/Feature.h"
 #include "mozilla/dom/FeaturePolicyParser.h"
 #include "nsNetUtil.h"
 #include "nsTArray.h"
 
 using namespace mozilla::dom;
 
+#define URL_SELF NS_LITERAL_STRING("https://example.com")
+#define URL_EXAMPLE_COM NS_LITERAL_STRING("http://example.com")
+#define URL_EXAMPLE_NET NS_LITERAL_STRING("http://example.net")
+
 void
 CheckParser(const nsAString& aInput, bool aExpectedResults,
             uint32_t aExpectedFeatures, nsTArray<Feature>& aParsedFeatures)
 {
-  nsCOMPtr<nsIURI> uri;
-  nsresult rv = NS_NewURI(getter_AddRefs(uri),
-                          NS_LITERAL_STRING("https://example.com"));
-  ASSERT_EQ(NS_OK, rv) << "NS_NewURI works.";
-
   nsTArray<Feature> parsedFeatures;
-  ASSERT_TRUE(FeaturePolicyParser::ParseString(aInput, nullptr, uri,
-                                               parsedFeatures) ==
-                aExpectedResults);
+  ASSERT_TRUE(FeaturePolicyParser::ParseString(aInput,
+                                               nullptr,
+                                               URL_SELF,
+                                               EmptyString(),
+                                               true, // 'src' enabled
+                                               parsedFeatures) == aExpectedResults);
   ASSERT_TRUE(parsedFeatures.Length() == aExpectedFeatures);
 
   parsedFeatures.SwapElements(aParsedFeatures);
 }
 
 TEST(FeaturePolicyParser, Basic)
 {
-  nsCOMPtr<nsIURI> uriSelf;
-  nsresult rv = NS_NewURI(getter_AddRefs(uriSelf),
-                          NS_LITERAL_STRING("https://example.com"));
-  ASSERT_EQ(NS_OK, rv) << "NS_NewURI works.";
-
-  nsCOMPtr<nsIURI> uriExampleCom;
-  rv = NS_NewURI(getter_AddRefs(uriExampleCom),
-                 NS_LITERAL_STRING("http://example.com"));
-  ASSERT_EQ(NS_OK, rv) << "NS_NewURI works.";
-
-  nsCOMPtr<nsIURI> uriExampleNet;
-  rv = NS_NewURI(getter_AddRefs(uriExampleNet),
-                 NS_LITERAL_STRING("http://example.net"));
-  ASSERT_EQ(NS_OK, rv) << "NS_NewURI works.";
-
   nsTArray<Feature> parsedFeatures;
 
   // Empty string is a valid policy.
   CheckParser(EmptyString(), true, 0, parsedFeatures);
 
   // Empty string with spaces is still valid.
   CheckParser(NS_LITERAL_STRING("   "), true, 0, parsedFeatures);
 
@@ -102,36 +89,36 @@ TEST(FeaturePolicyParser, Basic)
   ASSERT_TRUE(parsedFeatures[0].IsWhiteList());
   ASSERT_TRUE(parsedFeatures[1].Name().Equals(NS_LITERAL_STRING("microphone")));
   ASSERT_TRUE(parsedFeatures[1].IsWhiteList());
 
   // Multiple spaces around the value
   CheckParser(NS_LITERAL_STRING("camera      'self'"), true, 1,
               parsedFeatures);
   ASSERT_TRUE(parsedFeatures[0].Name().Equals(NS_LITERAL_STRING("camera")));
-  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(uriSelf));
+  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(URL_SELF));
 
   // Multiple spaces around the value
   CheckParser(NS_LITERAL_STRING("camera      'self'    "), true, 1,
               parsedFeatures);
   ASSERT_TRUE(parsedFeatures[0].Name().Equals(NS_LITERAL_STRING("camera")));
-  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(uriSelf));
+  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(URL_SELF));
 
   // No final '
   CheckParser(NS_LITERAL_STRING("camera      'self"), true, 1,
               parsedFeatures);
   ASSERT_TRUE(parsedFeatures[0].Name().Equals(NS_LITERAL_STRING("camera")));
   ASSERT_TRUE(parsedFeatures[0].IsWhiteList());
-  ASSERT_TRUE(!parsedFeatures[0].WhiteListContains(uriSelf));
+  ASSERT_TRUE(!parsedFeatures[0].WhiteListContains(URL_SELF));
 
   // Lowercase/Uppercase
   CheckParser(NS_LITERAL_STRING("camera      'selF'"), true, 1,
               parsedFeatures);
   ASSERT_TRUE(parsedFeatures[0].Name().Equals(NS_LITERAL_STRING("camera")));
-  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(uriSelf));
+  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(URL_SELF));
 
   // Lowercase/Uppercase
   CheckParser(NS_LITERAL_STRING("camera * 'self' none' a.com 123"), true, 1,
               parsedFeatures);
   ASSERT_TRUE(parsedFeatures[0].Name().Equals(NS_LITERAL_STRING("camera")));
   ASSERT_TRUE(parsedFeatures[0].AllowsAll());
 
   // After a 'none' we don't continue the parsing.
@@ -144,32 +131,32 @@ TEST(FeaturePolicyParser, Basic)
   CheckParser(NS_LITERAL_STRING("camera * a.com b.org c.net d.co.uk"), true, 1,
               parsedFeatures);
   ASSERT_TRUE(parsedFeatures[0].Name().Equals(NS_LITERAL_STRING("camera")));
   ASSERT_TRUE(parsedFeatures[0].AllowsAll());
 
   // 'self'
   CheckParser(NS_LITERAL_STRING("camera 'self'"), true, 1, parsedFeatures);
   ASSERT_TRUE(parsedFeatures[0].Name().Equals(NS_LITERAL_STRING("camera")));
-  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(uriSelf));
+  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(URL_SELF));
 
   // A couple of URLs
   CheckParser(NS_LITERAL_STRING("camera http://example.com http://example.net"), true, 1,
               parsedFeatures);
   ASSERT_TRUE(parsedFeatures[0].Name().Equals(NS_LITERAL_STRING("camera")));
-  ASSERT_TRUE(!parsedFeatures[0].WhiteListContains(uriSelf));
-  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(uriExampleCom));
-  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(uriExampleNet));
+  ASSERT_TRUE(!parsedFeatures[0].WhiteListContains(URL_SELF));
+  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(URL_EXAMPLE_COM));
+  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(URL_EXAMPLE_NET));
 
   // A couple of URLs + self
   CheckParser(NS_LITERAL_STRING("camera http://example.com 'self' http://example.net"), true, 1,
               parsedFeatures);
   ASSERT_TRUE(parsedFeatures[0].Name().Equals(NS_LITERAL_STRING("camera")));
-  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(uriSelf));
-  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(uriExampleCom));
-  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(uriExampleNet));
+  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(URL_SELF));
+  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(URL_EXAMPLE_COM));
+  ASSERT_TRUE(parsedFeatures[0].WhiteListContains(URL_EXAMPLE_NET));
 
   // A couple of URLs but then *
   CheckParser(NS_LITERAL_STRING("camera http://example.com 'self' http://example.net *"), true, 1,
               parsedFeatures);
   ASSERT_TRUE(parsedFeatures[0].Name().Equals(NS_LITERAL_STRING("camera")));
   ASSERT_TRUE(parsedFeatures[0].AllowsAll());
 }
new file mode 100644
--- /dev/null
+++ b/dom/security/featurepolicy/test/mochitest/empty.html
@@ -0,0 +1,1 @@
+Nothing here
new file mode 100644
--- /dev/null
+++ b/dom/security/featurepolicy/test/mochitest/mochitest.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+prefs =
+  dom.security.featurePolicy.enabled=true
+support-files =
+  empty.html
+  test_parser.html^headers^
+
+[test_parser.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/featurepolicy/test/mochitest/test_parser.html
@@ -0,0 +1,220 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test feature policy - parsing</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe src="empty.html" id="ifr"></iframe>
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function test_document() {
+  info("Checking document.policy");
+  ok("policy" in document, "We have document.policy");
+
+  ok(!document.policy.allowsFeature("foobar"), "Random feature");
+  ok(!document.policy.allowsFeature("foobar", "http://www.something.net"), "Random feature");
+
+  ok(document.policy.allowsFeature("camera"), "Camera is always enabled");
+  ok(document.policy.allowsFeature("camera", "http://foo.bar"), "Camera is always enabled");
+  let allowed = document.policy.getAllowlistForFeature("camera");
+  is(allowed.length, 1, "Only 1 entry in allowlist for camera");
+  is(allowed[0], "*", "allowlist is *");
+
+  ok(document.policy.allowsFeature("geolocation"), "Geolocation is enabled for self");
+  ok(document.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for self");
+  ok(!document.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is not enabled for anything else");
+  allowed = document.policy.getAllowlistForFeature("geolocation");
+  is(allowed.length, 1, "Only 1 entry in allowlist for geolocation");
+  is(allowed[0], location.origin, "allowlist is self");
+
+  ok(!document.policy.allowsFeature("microphone"), "Microphone is disabled for self");
+  ok(!document.policy.allowsFeature("microphone", location.origin), "Microphone is disabled for self");
+  ok(!document.policy.allowsFeature("microphone", "http://foo.bar"), "Microphone is disabled for foo.bar");
+  ok(document.policy.allowsFeature("microphone", "http://example.com"), "Microphone is enabled for example.com");
+  ok(document.policy.allowsFeature("microphone", "http://example.org"), "Microphone is enabled for example.org");
+  allowed = document.policy.getAllowlistForFeature("microphone");
+  is(allowed.length, 0, "No allowlist for microphone");
+
+  ok(!document.policy.allowsFeature("vr"), "Vibrate is disabled for self");
+  ok(!document.policy.allowsFeature("vr", location.origin), "Vibrate is disabled for self");
+  ok(!document.policy.allowsFeature("vr", "http://foo.bar"), "Vibrate is disabled for foo.bar");
+  allowed = document.policy.getAllowlistForFeature("vr");
+  is(allowed.length, 0, "No allowlist for vr");
+
+  allowed = document.policy.allowedFeatures();
+  // microphone is disabled for this origin, vr is disabled everywhere.
+  let camera = false;
+  let geolocation = false;
+  allowed.forEach(a => {
+    if (a == "camera") camera = true;
+    if (a == "geolocation") geolocation = true;
+  });
+
+  ok(camera, "Camera is always allowed");
+  ok(geolocation, "Geolocation is allowed only for self");
+
+  next();
+}
+
+function test_iframe_without_allow() {
+  info("Checking HTMLIFrameElement.policy");
+  let ifr = document.getElementById("ifr");
+  ok("policy" in ifr, "HTMLIFrameElement.policy exists");
+
+  ok(!ifr.policy.allowsFeature("foobar"), "Random feature");
+  ok(!ifr.policy.allowsFeature("foobar", "http://www.something.net"), "Random feature");
+
+  ok(ifr.policy.allowsFeature("camera"), "Camera is always enabled for self");
+  ok(ifr.policy.allowsFeature("camera", location.origin), "Camera is allowed for self");
+  ok(!ifr.policy.allowsFeature("camera", "http://foo.bar"), "Camera is not allowed for a random URL");
+  let allowed = ifr.policy.getAllowlistForFeature("camera");
+  is(allowed.length, 1, "Only 1 entry in allowlist for camera");
+  is(allowed[0], location.origin, "allowlist is 'self'");
+
+  ok(ifr.policy.allowsFeature("geolocation"), "Geolocation is enabled for self");
+  ok(ifr.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for self");
+  ok(!ifr.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is not enabled for anything else");
+  allowed = ifr.policy.getAllowlistForFeature("geolocation");
+  is(allowed.length, 1, "Only 1 entry in allowlist for geolocation");
+  is(allowed[0], location.origin, "allowlist is self");
+
+  ok(!ifr.policy.allowsFeature("microphone"), "Microphone is disabled for self");
+  ok(!ifr.policy.allowsFeature("microphone", location.origin), "Microphone is disabled for self");
+  ok(!ifr.policy.allowsFeature("microphone", "http://foo.bar"), "Microphone is disabled for foo.bar");
+  ok(!ifr.policy.allowsFeature("microphone", "http://example.com"), "Microphone is disabled for example.com");
+  ok(!ifr.policy.allowsFeature("microphone", "http://example.org"), "Microphone is disabled for example.org");
+  allowed = ifr.policy.getAllowlistForFeature("microphone");
+  is(allowed.length, 0, "No allowlist for microphone");
+
+  ok(!ifr.policy.allowsFeature("vr"), "Vibrate is disabled for self");
+  ok(!ifr.policy.allowsFeature("vr", location.origin), "Vibrate is disabled for self");
+  ok(!ifr.policy.allowsFeature("vr", "http://foo.bar"), "Vibrate is disabled for foo.bar");
+  allowed = ifr.policy.getAllowlistForFeature("vr");
+  is(allowed.length, 0, "No allowlist for vr");
+
+  ok(ifr.policy.allowedFeatures().includes("camera"), "Camera is allowed");
+  ok(ifr.policy.allowedFeatures().includes("geolocation"), "Geolocation is allowed");
+  // microphone is disabled for this origin
+  ok(!ifr.policy.allowedFeatures().ncludes("microphone"), "microphone is not allowed");
+  // vr is disabled everywhere.
+  ok(!ifr.policy.allowedFeatures().includes("vr"), "VR is not allowed");
+
+  next();
+}
+
+function test_iframe_with_allow() {
+  info("Checking HTMLIFrameElement.policy");
+  let ifr = document.getElementById("ifr");
+  ok("policy" in ifr, "HTMLIFrameElement.policy exists");
+
+  ifr.setAttribute("allow", "camera 'none'");
+
+  ok(!ifr.policy.allowsFeature("foobar"), "Random feature");
+  ok(!ifr.policy.allowsFeature("foobar", "http://www.something.net"), "Random feature");
+
+  ok(!ifr.policy.allowsFeature("camera"), "Camera is not enabled");
+  let allowed = ifr.policy.getAllowlistForFeature("camera");
+  is(allowed.length, 0, "Camera has an empty allowlist");
+
+  ok(ifr.policy.allowsFeature("geolocation"), "Geolocation is enabled for self");
+  ok(ifr.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for self");
+  ok(!ifr.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is not enabled for anything else");
+  allowed = ifr.policy.getAllowlistForFeature("geolocation");
+  is(allowed.length, 1, "Only 1 entry in allowlist for geolocation");
+  is(allowed[0], location.origin, "allowlist is self");
+
+  ok(!ifr.policy.allowsFeature("microphone"), "Microphone is disabled for self");
+  ok(!ifr.policy.allowsFeature("microphone", location.origin), "Microphone is disabled for self");
+  ok(!ifr.policy.allowsFeature("microphone", "http://foo.bar"), "Microphone is disabled for foo.bar");
+  ok(!ifr.policy.allowsFeature("microphone", "http://example.com"), "Microphone is disabled for example.com");
+  ok(!ifr.policy.allowsFeature("microphone", "http://example.org"), "Microphone is disabled for example.org");
+  allowed = ifr.policy.getAllowlistForFeature("microphone");
+  is(allowed.length, 0, "No allowlist for microphone");
+
+  ok(!ifr.policy.allowsFeature("vr"), "Vibrate is disabled for self");
+  ok(!ifr.policy.allowsFeature("vr", location.origin), "Vibrate is disabled for self");
+  ok(!ifr.policy.allowsFeature("vr", "http://foo.bar"), "Vibrate is disabled for foo.bar");
+  allowed = ifr.policy.getAllowlistForFeature("vr");
+  is(allowed.length, 0, "No allowlist for vr");
+
+  ok(ifr.policy.allowedFeatures().includes("geolocation"), "Geolocation is allowed only for self");
+
+  next();
+}
+
+function test_iframe_contentDocument() {
+  info("Checking iframe document.policy");
+
+  let ifr = document.createElement("iframe");
+  ifr.setAttribute("src", "empty.html");
+  ifr.onload = function() {
+    ok("policy" in ifr.contentDocument, "We have ifr.contentDocument.policy");
+
+    ok(!ifr.contentDocument.policy.allowsFeature("foobar"), "Random feature");
+    ok(!ifr.contentDocument.policy.allowsFeature("foobar", "http://www.something.net"), "Random feature");
+
+    ok(ifr.contentDocument.policy.allowsFeature("camera"), "Camera is always enabled for self");
+    ok(!ifr.contentDocument.policy.allowsFeature("camera", "http://foo.bar"), "Camera is not allowed for a random URL");
+    let allowed = ifr.contentDocument.policy.getAllowlistForFeature("camera");
+    is(allowed.length, 1, "Only 1 entry in allowlist for camera");
+    is(allowed[0], location.origin, "allowlist is self");
+
+    ok(ifr.contentDocument.policy.allowsFeature("geolocation"), "Geolocation is enabled for self");
+    ok(ifr.contentDocument.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for self");
+    ok(!ifr.contentDocument.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is not enabled for anything else");
+    allowed = ifr.contentDocument.policy.getAllowlistForFeature("geolocation");
+    is(allowed.length, 1, "Only 1 entry in allowlist for geolocation");
+    is(allowed[0], location.origin, "allowlist is self");
+
+    ok(!ifr.contentDocument.policy.allowsFeature("microphone"), "Microphone is disabled for self");
+    ok(!ifr.contentDocument.policy.allowsFeature("microphone", location.origin), "Microphone is disabled for self");
+    ok(!ifr.contentDocument.policy.allowsFeature("microphone", "http://foo.bar"), "Microphone is disabled for foo.bar");
+    ok(!ifr.contentDocument.policy.allowsFeature("microphone", "http://example.com"), "Microphone is enabled for example.com");
+    ok(!ifr.contentDocument.policy.allowsFeature("microphone", "http://example.org"), "Microphone is enabled for example.org");
+    allowed = ifr.contentDocument.policy.getAllowlistForFeature("microphone");
+    is(allowed.length, 0, "No allowlist for microphone");
+
+    ok(!ifr.contentDocument.policy.allowsFeature("vr"), "Vibrate is disabled for self");
+    ok(!ifr.contentDocument.policy.allowsFeature("vr", location.origin), "Vibrate is disabled for self");
+    ok(!ifr.contentDocument.policy.allowsFeature("vr", "http://foo.bar"), "Vibrate is disabled for foo.bar");
+    allowed = ifr.contentDocument.policy.getAllowlistForFeature("vr");
+    is(allowed.length, 0, "No allowlist for vr");
+
+    ok(ifr.contentDocument.policy.allowedFeatures().includes("camera"), "Camera is allowed");
+    ok(ifr.contentDocument.policy.allowedFeatures().includes("geolocation"), "Geolocation is allowed");
+    // microphone is disabled for this origin
+    ok(!ifr.contentDocument.policy.allowedFeatures().includes("microphone"), "Microphone is not allowed");
+    // vr is disabled everywhere.
+    ok(!ifr.contentDocument.policy.allowedFeatures().includes("vr"), "VR is not allowed");
+
+    next();
+  };
+  document.body.appendChild(ifr);
+}
+
+var tests = [
+  test_document,
+  test_iframe_without_allow,
+  test_iframe_with_allow,
+  test_iframe_contentDocument,
+];
+
+function next() {
+  if (tests.length == 0) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+next();
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/featurepolicy/test/mochitest/test_parser.html^headers^
@@ -0,0 +1,1 @@
+Feature-Policy: camera *; geolocation 'self'; microphone http://example.com http://example.org; vr 'none'
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -7,16 +7,17 @@
  * https://html.spec.whatwg.org/multipage/dom.html#the-document-object
  * https://html.spec.whatwg.org/multipage/obsolete.html#other-elements%2C-attributes-and-apis
  * https://fullscreen.spec.whatwg.org/#api
  * https://w3c.github.io/pointerlock/#extensions-to-the-document-interface
  * https://w3c.github.io/pointerlock/#extensions-to-the-documentorshadowroot-mixin
  * https://w3c.github.io/page-visibility/#extensions-to-the-document-interface
  * https://drafts.csswg.org/cssom/#extensions-to-the-document-interface
  * https://drafts.csswg.org/cssom-view/#extensions-to-the-document-interface
+ * https://wicg.github.io/feature-policy/#policy
  */
 
 interface WindowProxy;
 interface nsISupports;
 interface URI;
 interface nsIDocShell;
 interface nsILoadGroup;
 
@@ -541,8 +542,14 @@ Document implements XPathEvaluator;
 Document implements GlobalEventHandlers;
 Document implements DocumentAndElementEventHandlers;
 Document implements TouchEventHandlers;
 Document implements ParentNode;
 Document implements OnErrorEventHandlerForNodes;
 Document implements GeometryUtils;
 Document implements FontFaceSource;
 Document implements DocumentOrShadowRoot;
+
+// https://wicg.github.io/feature-policy/#policy
+partial interface Document {
+    [SameObject, Pref="dom.security.featurePolicy.enabled"]
+    readonly attribute Policy policy;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/FeaturePolicy.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * For more information on this interface, please see
+ * https://wicg.github.io/feature-policy/#policy
+ */
+
+[NoInterfaceObject]
+interface Policy {
+  boolean allowsFeature(DOMString feature, optional DOMString origin);
+  sequence<DOMString> allowedFeatures();
+  sequence<DOMString> getAllowlistForFeature(DOMString feature);
+};
--- a/dom/webidl/HTMLIFrameElement.webidl
+++ b/dom/webidl/HTMLIFrameElement.webidl
@@ -1,16 +1,18 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/.
  *
  * The origin of this IDL file is
  * http://www.whatwg.org/specs/web-apps/current-work/#the-iframe-element
  * http://www.whatwg.org/specs/web-apps/current-work/#other-elements,-attributes-and-apis
+ * https://wicg.github.io/feature-policy/#policy
+ *
  * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
  * Opera Software ASA. You are granted a license to use, reproduce
  * and create derivative works of this document.
  */
 
 [HTMLConstructor]
 interface HTMLIFrameElement : HTMLElement {
   [CEReactions, SetterNeedsSubjectPrincipal=NonSystem, SetterThrows, Pure]
@@ -62,8 +64,17 @@ partial interface HTMLIFrameElement {
 partial interface HTMLIFrameElement {
   // nsIDOMMozBrowserFrame
   [ChromeOnly,SetterThrows]
            attribute boolean mozbrowser;
 };
 
 HTMLIFrameElement implements MozFrameLoaderOwner;
 HTMLIFrameElement implements BrowserElement;
+
+// https://wicg.github.io/feature-policy/#policy
+partial interface HTMLIFrameElement {
+  [SameObject, Pref="dom.security.featurePolicy.enabled"]
+  readonly attribute Policy policy;
+
+  [CEReactions, SetterThrows, Pure, Pref="dom.security.featurePolicy.enabled"]
+           attribute DOMString allow;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -95,16 +95,19 @@ with Files("DelayNode.webidl"):
     BUG_COMPONENT = ("Core", "Web Audio")
 
 with Files("DynamicsCompressorNode.webidl"):
     BUG_COMPONENT = ("Core", "Web Audio")
 
 with Files("FakePluginTagInit.webidl"):
     BUG_COMPONENT = ("Core", "Plug-ins")
 
+with Files("FeaturePolicy.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Security")
+
 with Files("Flex.webidl"):
     BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
 
 with Files("FocusEvent.webidl"):
     BUG_COMPONENT = ("Core", "DOM: Events")
 
 with Files("Font*"):
     BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
@@ -492,16 +495,17 @@ WEBIDL_FILES = [
     'Event.webidl',
     'EventHandler.webidl',
     'EventListener.webidl',
     'EventSource.webidl',
     'EventTarget.webidl',
     'ExtendableEvent.webidl',
     'ExtendableMessageEvent.webidl',
     'FakePluginTagInit.webidl',
+    'FeaturePolicy.webidl',
     'Fetch.webidl',
     'FetchEvent.webidl',
     'FetchObserver.webidl',
     'File.webidl',
     'FileList.webidl',
     'FileMode.webidl',
     'FileReader.webidl',
     'FileReaderSync.webidl',
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1715,12 +1715,22 @@ VARCACHE_PREF(
 
 VARCACHE_PREF(
   "devtools.enabled",
    devtools_enabled,
   RelaxedAtomicBool, false
 )
 
 //---------------------------------------------------------------------------
+// Feature-Policy prefs
+//---------------------------------------------------------------------------
+
+VARCACHE_PREF(
+  "dom.security.featurePolicy.enabled",
+   dom_security_featurePolicy_enabled,
+  bool, false
+)
+
+//---------------------------------------------------------------------------
 // End of prefs
 //---------------------------------------------------------------------------
 
 // clang-format on
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -58,16 +58,17 @@ STATIC_ATOMS = [
     Atom("action", "action"),
     Atom("active", "active"),
     Atom("activateontab", "activateontab"),
     Atom("actuate", "actuate"),
     Atom("address", "address"),
     Atom("after", "after"),
     Atom("align", "align"),
     Atom("alink", "alink"),
+    Atom("allow", "allow"),
     Atom("allowdirs", "allowdirs"),
     Atom("allowevents", "allowevents"),
     Atom("allowforms", "allow-forms"),
     Atom("allowfullscreen", "allowfullscreen"),
     Atom("allowmodals", "allow-modals"),
     Atom("alloworientationlock", "allow-orientation-lock"),
     Atom("allowpaymentrequest", "allowpaymentrequest"),
     Atom("allowpointerlock", "allow-pointer-lock"),