Bug 1322235: Part 5 - Add an ExtensionPolicyService singleton class to track active extension policies. r?billm,mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Tue, 23 May 2017 19:15:10 -0700
changeset 584188 4d660303f98a642d3af3759e5b952989161053a3
parent 584187 d5f2a4c762e229101573d4459ba3600688bd877e
child 584189 56249f0a50073a8110d77ec8ba2348435583e6e0
push id60645
push usermaglione.k@gmail.com
push dateThu, 25 May 2017 00:12:26 +0000
reviewersbillm, mixedpuppy
bugs1322235
milestone55.0a1
Bug 1322235: Part 5 - Add an ExtensionPolicyService singleton class to track active extension policies. r?billm,mixedpuppy Bill, can you please review the binding changes? Shane, can you please review the policy service? This is the first step to making extension policy data directly available to C++ code without any COM overhead. It tracks the set of currently active extensions, and how they map to add-on IDs and URIs. MozReview-Commit-ID: 9Z61AXFll3P
dom/base/nsGkAtomList.h
dom/webidl/WebExtensionPolicy.webidl
toolkit/components/extensions/ExtensionPolicyService.cpp
toolkit/components/extensions/ExtensionPolicyService.h
toolkit/components/extensions/WebExtensionPolicy.cpp
toolkit/components/extensions/WebExtensionPolicy.h
toolkit/components/extensions/moz.build
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2003,16 +2003,17 @@ GK_ATOM(ondevicelight, "ondevicelight")
 
 // MediaDevices device change event
 GK_ATOM(ondevicechange, "ondevicechange")
 
 // HTML element attributes that only exposed to XBL and chrome content
 GK_ATOM(mozinputrangeignorepreventdefault, "mozinputrangeignorepreventdefault")
 
 // WebExtensions
+GK_ATOM(moz_extension, "moz-extension")
 GK_ATOM(http, "http")
 GK_ATOM(https, "https")
 
 //---------------------------------------------------------------------------
 // Special atoms
 //---------------------------------------------------------------------------
 
 // Node types
--- a/dom/webidl/WebExtensionPolicy.webidl
+++ b/dom/webidl/WebExtensionPolicy.webidl
@@ -93,16 +93,42 @@ interface WebExtensionPolicy {
    */
   DOMString localize(DOMString unlocalizedText);
 
   /**
    * Returns the moz-extension: URL for the given path.
    */
   [Throws]
   DOMString getURL(optional DOMString path = "");
+
+
+  /**
+   * Returns the list of currently active extension policies.
+   */
+  static sequence<WebExtensionPolicy> getActiveExtensions();
+
+  /**
+   * Returns the currently-active policy for the extension with the given ID,
+   * or null if no policy is active for that ID.
+   */
+  static WebExtensionPolicy? getByID(DOMString id);
+
+  /**
+   * Returns the currently-active policy for the extension with the given
+   * moz-extension: hostname, or null if no policy is active for that
+   * hostname.
+   */
+  static WebExtensionPolicy? getByHostname(ByteString hostname);
+
+  /**
+   * Returns the currently-active policy for the extension extension URI, or
+   * null if the URI is not an extension URI, or no policy is currently active
+   * for it.
+   */
+  static WebExtensionPolicy? getByURI(URI uri);
 };
 
 dictionary WebExtensionInit {
   required DOMString id;
 
   required ByteString mozExtensionHostname;
 
   required DOMString baseURL;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ExtensionPolicyService.cpp
@@ -0,0 +1,121 @@
+/* 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/ExtensionPolicyService.h"
+#include "mozilla/extensions/WebExtensionPolicy.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "nsGkAtoms.h"
+
+namespace mozilla {
+
+using namespace extensions;
+
+#define DEFAULT_BASE_CSP \
+    "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " \
+    "object-src 'self' https://* moz-extension: blob: filesystem:;"
+
+#define DEFAULT_DEFAULT_CSP \
+    "script-src 'self'; object-src 'self';"
+
+
+/*****************************************************************************
+ * ExtensionPolicyService
+ *****************************************************************************/
+
+/* static */ ExtensionPolicyService&
+ExtensionPolicyService::GetSingleton()
+{
+  static RefPtr<ExtensionPolicyService> sExtensionPolicyService;
+
+  if (MOZ_UNLIKELY(!sExtensionPolicyService)) {
+    sExtensionPolicyService = new ExtensionPolicyService();
+    ClearOnShutdown(&sExtensionPolicyService);
+  }
+  return *sExtensionPolicyService.get();
+}
+
+
+WebExtensionPolicy*
+ExtensionPolicyService::GetByURL(const URLInfo& aURL)
+{
+  if (aURL.Scheme() == nsGkAtoms::moz_extension) {
+    return GetByHost(aURL.Host());
+  }
+  return nullptr;
+}
+
+void
+ExtensionPolicyService::GetAll(nsTArray<RefPtr<WebExtensionPolicy>>& aResult)
+{
+  for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) {
+    aResult.AppendElement(iter.Data());
+  }
+}
+
+bool
+ExtensionPolicyService::RegisterExtension(WebExtensionPolicy& aPolicy)
+{
+  bool ok = (!GetByID(aPolicy.Id()) &&
+             !GetByHost(aPolicy.MozExtensionHostname()));
+  MOZ_ASSERT(ok);
+
+  if (!ok) {
+    return false;
+  }
+
+  mExtensions.Put(aPolicy.Id(), &aPolicy);
+  mExtensionHosts.Put(aPolicy.MozExtensionHostname(), &aPolicy);
+  return true;
+}
+
+bool
+ExtensionPolicyService::UnregisterExtension(WebExtensionPolicy& aPolicy)
+{
+  bool ok = (GetByID(aPolicy.Id()) == &aPolicy &&
+             GetByHost(aPolicy.MozExtensionHostname()) == &aPolicy);
+  MOZ_ASSERT(ok);
+
+  if (!ok) {
+    return false;
+  }
+
+  mExtensions.Remove(aPolicy.Id());
+  mExtensionHosts.Remove(aPolicy.MozExtensionHostname());
+  return true;
+}
+
+
+void
+ExtensionPolicyService::BaseCSP(nsAString& aBaseCSP) const
+{
+  nsresult rv;
+
+  rv = Preferences::GetString("extensions.webextensions.base-content-security-policy", &aBaseCSP);
+  if (NS_FAILED(rv)) {
+    aBaseCSP.AssignLiteral(DEFAULT_BASE_CSP);
+  }
+}
+
+void
+ExtensionPolicyService::DefaultCSP(nsAString& aDefaultCSP) const
+{
+  nsresult rv;
+
+  rv = Preferences::GetString("extensions.webextensions.default-content-security-policy", &aDefaultCSP);
+  if (NS_FAILED(rv)) {
+    aDefaultCSP.AssignLiteral(DEFAULT_DEFAULT_CSP);
+  }
+}
+
+NS_IMPL_CYCLE_COLLECTION(ExtensionPolicyService, mExtensions, mExtensionHosts)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPolicyService)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionPolicyService)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionPolicyService)
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ExtensionPolicyService.h
@@ -0,0 +1,69 @@
+/* -*-  Mode: C++; 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/. */
+
+#ifndef mozilla_ExtensionPolicyService_h
+#define mozilla_ExtensionPolicyService_h
+
+#include "mozilla/extensions/WebExtensionPolicy.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsHashKeys.h"
+#include "nsIAtom.h"
+#include "nsISupports.h"
+#include "nsPointerHashKeys.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+
+using extensions::WebExtensionPolicy;
+
+class ExtensionPolicyService final : public nsISupports
+{
+public:
+  NS_DECL_CYCLE_COLLECTION_CLASS(ExtensionPolicyService)
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+  static ExtensionPolicyService& GetSingleton();
+
+  WebExtensionPolicy*
+  GetByID(const nsIAtom* aAddonId)
+  {
+    return mExtensions.GetWeak(aAddonId);
+  }
+
+  WebExtensionPolicy* GetByID(const nsAString& aAddonId)
+  {
+    nsCOMPtr<nsIAtom> atom = NS_AtomizeMainThread(aAddonId);
+    return GetByID(atom);
+  }
+
+  WebExtensionPolicy* GetByURL(const extensions::URLInfo& aURL);
+
+  WebExtensionPolicy* GetByHost(const nsACString& aHost) const
+  {
+    return mExtensionHosts.GetWeak(aHost);
+  }
+
+  void GetAll(nsTArray<RefPtr<WebExtensionPolicy>>& aResult);
+
+  bool RegisterExtension(WebExtensionPolicy& aPolicy);
+  bool UnregisterExtension(WebExtensionPolicy& aPolicy);
+
+  void BaseCSP(nsAString& aDefaultCSP) const;
+  void DefaultCSP(nsAString& aDefaultCSP) const;
+
+protected:
+  virtual ~ExtensionPolicyService() = default;
+
+private:
+  ExtensionPolicyService() = default;
+
+  nsRefPtrHashtable<nsPtrHashKey<const nsIAtom>, WebExtensionPolicy> mExtensions;
+  nsRefPtrHashtable<nsCStringHashKey, WebExtensionPolicy> mExtensionHosts;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ExtensionPolicyService_h
--- a/toolkit/components/extensions/WebExtensionPolicy.cpp
+++ b/toolkit/components/extensions/WebExtensionPolicy.cpp
@@ -53,16 +53,22 @@ class EscapeHTML final : public nsAdopti
 {
 public:
   EscapeHTML(const nsACString& str)
     : nsAdoptingCString(nsEscapeHTML(str.BeginReading()))
   {}
 };
 
 
+static inline ExtensionPolicyService&
+EPS()
+{
+  return ExtensionPolicyService::GetSingleton();
+}
+
 static nsISubstitutingProtocolHandler*
 Proto()
 {
   static nsCOMPtr<nsISubstitutingProtocolHandler> sHandler;
 
   if (MOZ_UNLIKELY(!sHandler)) {
     nsCOMPtr<nsIIOService> ios = do_GetIOService();
     MOZ_RELEASE_ASSERT(ios);
@@ -95,16 +101,20 @@ WebExtensionPolicy::WebExtensionPolicy(G
   , mHostPermissions(aInit.mAllowedOrigins)
 {
   mWebAccessiblePaths.AppendElements(aInit.mWebAccessibleResources);
 
   if (!aInit.mBackgroundScripts.IsNull()) {
     mBackgroundScripts.SetValue().AppendElements(aInit.mBackgroundScripts.Value());
   }
 
+  if (mContentSecurityPolicy.IsVoid()) {
+    EPS().DefaultCSP(mContentSecurityPolicy);
+  }
+
   nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 }
 
 already_AddRefed<WebExtensionPolicy>
 WebExtensionPolicy::Constructor(GlobalObject& aGlobal,
@@ -114,16 +124,45 @@ WebExtensionPolicy::Constructor(GlobalOb
   RefPtr<WebExtensionPolicy> policy = new WebExtensionPolicy(aGlobal, aInit, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
   return policy.forget();
 }
 
 
+/* static */ void
+WebExtensionPolicy::GetActiveExtensions(dom::GlobalObject& aGlobal,
+                                        nsTArray<RefPtr<WebExtensionPolicy>>& aResults)
+{
+  EPS().GetAll(aResults);
+}
+
+/* static */ already_AddRefed<WebExtensionPolicy>
+WebExtensionPolicy::GetByID(dom::GlobalObject& aGlobal, const nsAString& aID)
+{
+  RefPtr<WebExtensionPolicy> result = EPS().GetByID(aID);
+  return result.forget();
+}
+
+/* static */ already_AddRefed<WebExtensionPolicy>
+WebExtensionPolicy::GetByHostname(dom::GlobalObject& aGlobal, const nsACString& aHostname)
+{
+  RefPtr<WebExtensionPolicy> result = EPS().GetByHost(aHostname);
+  return result.forget();
+}
+
+/* static */ already_AddRefed<WebExtensionPolicy>
+WebExtensionPolicy::GetByURI(dom::GlobalObject& aGlobal, nsIURI* aURI)
+{
+  RefPtr<WebExtensionPolicy> result = EPS().GetByURL(aURI);
+  return result.forget();
+}
+
+
 void
 WebExtensionPolicy::SetActive(bool aActive, ErrorResult& aRv)
 {
   if (aActive == mActive) {
     return;
   }
 
   bool ok = aActive ? Enable() : Disable();
@@ -133,26 +172,35 @@ WebExtensionPolicy::SetActive(bool aActi
   }
 }
 
 bool
 WebExtensionPolicy::Enable()
 {
   MOZ_ASSERT(!mActive);
 
+  if (!EPS().RegisterExtension(*this)) {
+    return false;
+  }
+
   Unused << Proto()->SetSubstitution(MozExtensionHostname(), mBaseURI);
 
   mActive = true;
   return true;
 }
 
 bool
 WebExtensionPolicy::Disable()
 {
   MOZ_ASSERT(mActive);
+  MOZ_ASSERT(EPS().GetByID(Id()) == this);
+
+  if (!EPS().UnregisterExtension(*this)) {
+    return false;
+  }
 
   Unused << Proto()->SetSubstitution(MozExtensionHostname(), nullptr);
 
   mActive = false;
   return true;
 }
 
 void
--- a/toolkit/components/extensions/WebExtensionPolicy.h
+++ b/toolkit/components/extensions/WebExtensionPolicy.h
@@ -106,16 +106,29 @@ public:
     mPermissions = new AtomSet(aPermissions);
   }
 
 
   bool Active() const { return mActive; }
   void SetActive(bool aActive, ErrorResult& aRv);
 
 
+  static void
+  GetActiveExtensions(dom::GlobalObject& aGlobal, nsTArray<RefPtr<WebExtensionPolicy>>& aResults);
+
+  static already_AddRefed<WebExtensionPolicy>
+  GetByID(dom::GlobalObject& aGlobal, const nsAString& aID);
+
+  static already_AddRefed<WebExtensionPolicy>
+  GetByHostname(dom::GlobalObject& aGlobal, const nsACString& aHostname);
+
+  static already_AddRefed<WebExtensionPolicy>
+  GetByURI(dom::GlobalObject& aGlobal, nsIURI* aURI);
+
+
   nsISupports* GetParentObject() const { return mParent; }
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
 
 protected:
   virtual ~WebExtensionPolicy() = default;
 
 private:
--- a/toolkit/components/extensions/moz.build
+++ b/toolkit/components/extensions/moz.build
@@ -39,23 +39,28 @@ TESTING_JS_MODULES += [
     'ExtensionXPCShellUtils.jsm',
 ]
 
 DIRS += [
     'schemas',
     'webrequest',
 ]
 
+EXPORTS.mozilla = [
+    'ExtensionPolicyService.h',
+]
+
 EXPORTS.mozilla.extensions = [
     'MatchGlob.h',
     'MatchPattern.h',
     'WebExtensionPolicy.h',
 ]
 
 UNIFIED_SOURCES += [
+    'ExtensionPolicyService.cpp',
     'MatchPattern.cpp',
     'WebExtensionPolicy.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 
 JAR_MANIFESTS += ['jar.mn']