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 585207 0fd6958c184ead32d050accf749d6da7d59fd2ab
parent 585206 968e0a13e9bda46e1c98b24ebf7979247530ac09
child 585208 7f6913f036073d3870c3919a64cf4c6ad7bd4be3
push id61052
push usermaglione.k@gmail.com
push dateFri, 26 May 2017 17:14:32 +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']