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 442814 4f1fd54c853f
parent 442813 cc08f0f9cd01
child 442815 755d616d0cb9
push id34924
push userrgurzau@mozilla.com
push dateWed, 24 Oct 2018 21:58:52 +0000
treeherdermozilla-central@8ca56f27dc58 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1488973
milestone65.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 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'