Bug 1488973: Wrap privileged promises in a promise for the document we`re returning to. r=smaug
authorZibi Braniecki <zbraniecki@mozilla.com>
Wed, 24 Oct 2018 17:41:46 +0000
changeset 491177 4f1fd54c853fd655e91975b364dd5640559f5f6e
parent 491176 cc08f0f9cd01231b0b55f77cfc26b2b18c9d23bb
child 491178 755d616d0cb9b7d43c6f587999ed53c38130e917
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerssmaug
bugs1488973
milestone65.0a1
Bug 1488973: Wrap privileged promises in a promise for the document we`re returning to. r=smaug Differential Revision: https://phabricator.services.mozilla.com/D7961
dom/base/nsDocument.cpp
dom/base/nsINode.cpp
intl/l10n/DocumentL10n.cpp
intl/l10n/DocumentL10n.h
intl/l10n/moz.build
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2227,30 +2227,45 @@ nsIDocument::Reset(nsIChannel* aChannel,
       mChromeXHRDocBaseURI = nullptr;
     }
   }
 
   mChannel = aChannel;
 }
 
 /**
- * DocumentL10n is currently allowed for system
- * principal.
- *
- * In the future we'll want to expose it to non-web-exposed
- * about:* pages.
+ * Determine whether the principal is allowed access to the localization system.
+ * We don't want the web to ever see this but all our UI including in content
+ * pages should pass this test.
  */
 bool
 PrincipalAllowsL10n(nsIPrincipal* principal)
 {
+  // The system principal is always allowed.
   if (nsContentUtils::IsSystemPrincipal(principal)) {
     return true;
   }
 
-  return false;
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = principal->GetURI(getter_AddRefs(uri));
+  NS_ENSURE_SUCCESS(rv, false);
+
+  bool hasFlags;
+
+  // Allow access to uris that cannot be loaded by web content.
+  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD, &hasFlags);
+  NS_ENSURE_SUCCESS(rv, false);
+  if (hasFlags) {
+    return true;
+  }
+
+  // UI resources also get access.
+  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &hasFlags);
+  NS_ENSURE_SUCCESS(rv, false);
+  return hasFlags;
 }
 
 void
 nsIDocument::ResetToURI(nsIURI* aURI,
                         nsILoadGroup* aLoadGroup,
                         nsIPrincipal* aPrincipal)
 {
   MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
@@ -3395,17 +3410,18 @@ DocumentL10n*
 nsIDocument::GetL10n()
 {
   return mDocumentL10n;
 }
 
 bool
 nsDocument::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject)
 {
-  return PrincipalAllowsL10n(nsContentUtils::SubjectPrincipal(aCx));
+  nsCOMPtr<nsIPrincipal> callerPrincipal = nsContentUtils::SubjectPrincipal(aCx);
+  return PrincipalAllowsL10n(callerPrincipal);
 }
 
 void
 nsIDocument::LocalizationLinkAdded(Element* aLinkElement)
 {
   if (!PrincipalAllowsL10n(NodePrincipal())) {
     return;
   }
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -102,16 +102,17 @@
 #include <algorithm>
 #include "nsGlobalWindow.h"
 #include "nsDOMMutationObserver.h"
 #include "GeometryUtils.h"
 #include "nsIAnimationObserver.h"
 #include "nsChildContentList.h"
 #include "mozilla/dom/NodeBinding.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "xpcprivate.h"
 
 #include "XPathGenerator.h"
 
 #ifdef ACCESSIBILITY
 #include "mozilla/dom/AccessibleNode.h"
 #endif
 
 using namespace mozilla;
@@ -3139,17 +3140,28 @@ public:
         }
 
         if (!JS_DefineElement(aCx, untranslatedElements, i, wrappedElem, JSPROP_ENUMERATE)) {
           mReturnValuePromise->MaybeRejectWithUndefined();
           return;
         }
       }
     }
-    mReturnValuePromise->MaybeResolve(untranslatedElements);
+
+    JS::RootedObject sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
+
+    AutoEntryScript aes(mReturnValuePromise->GetParentObject(), "Promise resolution");
+    JSContext* cx = aes.cx();
+    JS::Rooted<JS::Value> result(cx, JS::ObjectValue(*untranslatedElements));
+
+    xpc::StackScopedCloneOptions options;
+    options.wrapReflectors = true;
+    StackScopedClone(cx, options, sourceScope, &result);
+
+    mReturnValuePromise->MaybeResolve(result);
   }
 
   virtual void
   RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
     mReturnValuePromise->MaybeRejectWithUndefined();
   }
 
--- a/intl/l10n/DocumentL10n.cpp
+++ b/intl/l10n/DocumentL10n.cpp
@@ -9,20 +9,70 @@
 #include "mozilla/dom/DocumentL10nBinding.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "nsQueryObject.h"
 #include "nsISupports.h"
 #include "nsContentUtils.h"
+#include "xpcprivate.h"
 
 namespace mozilla {
 namespace dom {
 
+NS_INTERFACE_MAP_BEGIN(PromiseResolver)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(PromiseResolver)
+NS_IMPL_RELEASE(PromiseResolver)
+
+PromiseResolver::PromiseResolver(Promise* aPromise)
+{
+  mPromise = aPromise;
+}
+
+void
+PromiseResolver::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  JS::RootedObject sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
+
+  AutoEntryScript aes(mPromise->GetParentObject(), "Promise resolution");
+  JSContext* cx = aes.cx();
+  JS::Rooted<JS::Value> value(cx, aValue);
+
+  xpc::StackScopedCloneOptions options;
+  StackScopedClone(cx, options, sourceScope, &value);
+
+  mPromise->MaybeResolve(cx, value);
+  mPromise = nullptr;
+}
+
+void
+PromiseResolver::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  JS::RootedObject sourceScope(aCx, JS::CurrentGlobalOrNull(aCx));
+
+  AutoEntryScript aes(mPromise->GetParentObject(), "Promise rejection");
+  JSContext* cx = aes.cx();
+  JS::Rooted<JS::Value> value(cx, aValue);
+
+  xpc::StackScopedCloneOptions options;
+  StackScopedClone(cx, options, sourceScope, &value);
+
+  mPromise->MaybeReject(cx, value);
+  mPromise = nullptr;
+}
+
+PromiseResolver::~PromiseResolver()
+{
+  mPromise = nullptr;
+}
+
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentL10n)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(DocumentL10n)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(DocumentL10n)
@@ -75,16 +125,41 @@ DocumentL10n::Init(nsTArray<nsString>& a
 }
 
 JSObject*
 DocumentL10n::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return DocumentL10n_Binding::Wrap(aCx, this, aGivenProto);
 }
 
+already_AddRefed<Promise>
+DocumentL10n::MaybeWrapPromise(Promise* aInnerPromise)
+{
+  // For system principal we don't need to wrap the
+  // result promise at all.
+  if (nsContentUtils::IsSystemPrincipal(mDocument->NodePrincipal())) {
+    return RefPtr<Promise>(aInnerPromise).forget();
+  }
+
+  nsIGlobalObject* global = mDocument->GetScopeObject();
+  if (!global) {
+    return nullptr;
+  }
+
+  ErrorResult result;
+  RefPtr<Promise> docPromise = Promise::Create(global, result);
+  if (result.Failed()) {
+    return nullptr;
+  }
+
+  RefPtr<PromiseResolver> resolver = new PromiseResolver(docPromise);
+  aInnerPromise->AppendNativeHandler(resolver);
+  return docPromise.forget();
+}
+
 NS_IMETHODIMP
 DocumentL10n::HandleEvent(Event* aEvent)
 {
 #ifdef DEBUG
   nsAutoString eventType;
   aEvent->GetType(eventType);
   MOZ_ASSERT(eventType.EqualsLiteral("MozBeforeInitialXULLayout"));
 #endif
@@ -131,17 +206,17 @@ DocumentL10n::FormatMessages(JSContext* 
   }
 
   RefPtr<Promise> promise;
   aRv = mDOMLocalization->FormatMessages(jsKeys, getter_AddRefs(promise));
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  return promise.forget();
+  return MaybeWrapPromise(promise);
 }
 
 already_AddRefed<Promise>
 DocumentL10n::FormatValues(JSContext* aCx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv)
 {
   nsTArray<JS::Value> jsKeys;
   SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
   for (auto& key : aKeys) {
@@ -154,17 +229,17 @@ DocumentL10n::FormatValues(JSContext* aC
   }
 
   RefPtr<Promise> promise;
   aRv = mDOMLocalization->FormatValues(jsKeys, getter_AddRefs(promise));
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  return promise.forget();
+  return MaybeWrapPromise(promise);
 }
 
 already_AddRefed<Promise>
 DocumentL10n::FormatValue(JSContext* aCx, const nsAString& aId, const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv)
 {
   JS::Rooted<JS::Value> args(aCx);
 
   if (aArgs.WasPassed()) {
@@ -174,17 +249,17 @@ DocumentL10n::FormatValue(JSContext* aCx
   }
 
   RefPtr<Promise> promise;
   nsresult rv = mDOMLocalization->FormatValue(aId, args, getter_AddRefs(promise));
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
-  return promise.forget();
+  return MaybeWrapPromise(promise);
 }
 
 void
 DocumentL10n::SetAttributes(JSContext* aCx, Element& aElement, const nsAString& aId, const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv)
 {
   aElement.SetAttribute(NS_LITERAL_STRING("data-l10n-id"), aId, aRv);
   if (aRv.Failed()) {
     return;
@@ -230,34 +305,34 @@ already_AddRefed<Promise>
 DocumentL10n::TranslateFragment(nsINode& aNode, ErrorResult& aRv)
 {
   RefPtr<Promise> promise;
   nsresult rv = mDOMLocalization->TranslateFragment(&aNode, getter_AddRefs(promise));
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
-  return promise.forget();
+  return MaybeWrapPromise(promise);
 }
 
 already_AddRefed<Promise>
 DocumentL10n::TranslateElements(const Sequence<OwningNonNull<Element>>& aElements, ErrorResult& aRv)
 {
   AutoTArray<RefPtr<Element>, 10> elements;
   elements.SetCapacity(aElements.Length());
   for (auto& element : aElements) {
     elements.AppendElement(element);
   }
   RefPtr<Promise> promise;
   aRv = mDOMLocalization->TranslateElements(
       elements, getter_AddRefs(promise));
   if (aRv.Failed()) {
     return nullptr;
   }
-  return promise.forget();
+  return MaybeWrapPromise(promise);
 }
 
 class L10nReadyHandler final : public PromiseNativeHandler
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(L10nReadyHandler)
 
--- a/intl/l10n/DocumentL10n.h
+++ b/intl/l10n/DocumentL10n.h
@@ -12,24 +12,40 @@
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsIDOMEventListener.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "nsIDocument.h"
 #include "nsINode.h"
 #include "mozIDOMLocalization.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
 
 namespace mozilla {
 namespace dom {
 
 class Element;
-class Promise;
 struct L10nKey;
 
+class PromiseResolver final : public PromiseNativeHandler
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit PromiseResolver(Promise* aPromise);
+  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+protected:
+  virtual ~PromiseResolver();
+
+  RefPtr<Promise> mPromise;
+};
+
 enum class DocumentL10nState
 {
   Initialized = 0,
   InitialTranslationTriggered
 };
 
 /**
  * This class maintains localization status of the nsDocument.
@@ -56,16 +72,18 @@ public:
 protected:
   virtual ~DocumentL10n();
 
   nsCOMPtr<nsIDocument> mDocument;
   RefPtr<Promise> mReady;
   DocumentL10nState mState;
   nsCOMPtr<mozIDOMLocalization> mDOMLocalization;
 
+  already_AddRefed<Promise> MaybeWrapPromise(Promise* aPromise);
+
 public:
   nsIDocument* GetParentObject() const { return mDocument; };
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   /**
    * A method for adding resources to the localization context.
    *
--- a/intl/l10n/moz.build
+++ b/intl/l10n/moz.build
@@ -27,16 +27,17 @@ EXPORTS.mozilla.dom += [
 ]
 
 UNIFIED_SOURCES += [
     'DocumentL10n.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
+    '/js/xpconnect/src',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
 
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 SPHINX_TREES['l10n'] = 'docs'