Bug 1334550 - Part 2 - Add nsISubstitutionObserver and update test_extensionURL.html; r=jimm
authorHaik Aftandilian <haftandilian@mozilla.com>
Wed, 21 Jun 2017 16:13:23 -0700
changeset 366107 3358bef7a047d347e576bbb83d1461b3776fc5fb
parent 366106 69cb0b4c0b2be646cfd682876fc65d2ddc8ecc6e
child 366108 4b9a7375ccb6f6f21678cfdc117edf7faf0d727a
push id45500
push userhaftandilian@mozilla.com
push dateTue, 27 Jun 2017 05:35:56 +0000
treeherderautoland@3358bef7a047 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm
bugs1334550
milestone56.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 1334550 - Part 2 - Add nsISubstitutionObserver and update test_extensionURL.html; r=jimm Adds nsISubstitutionObserver so that substitutions set on a parent nsISubstitutingProtocolHandler which are then propagated to child processes can be observed in the child. Updates test_extensionURL.html to set substitutions on the parent ExtensionProtocolHandler before trying to load moz-extension URI's using those substitutions. MozReview-Commit-ID: JaW1A3uZpoO
caps/tests/mochitest/test_extensionURL.html
netwerk/protocol/res/SubstitutingProtocolHandler.cpp
netwerk/protocol/res/SubstitutingProtocolHandler.h
netwerk/protocol/res/moz.build
netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
netwerk/protocol/res/nsISubstitutionObserver.idl
netwerk/protocol/res/nsResProtocolHandler.h
--- a/caps/tests/mochitest/test_extensionURL.html
+++ b/caps/tests/mochitest/test_extensionURL.html
@@ -13,51 +13,166 @@ https://bugzilla.mozilla.org/show_bug.cg
   /** Test for Bug 1161831 **/
   SimpleTest.waitForExplicitFinish();
 
   let module = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm", {});
   var {MatchGlob, MatchPatternSet, WebExtensionPolicy} = module;
 
   var policy1, policy2;
 
+  var XPCOMUtils = SpecialPowers.Cu.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
   var resourceHandler = SpecialPowers.Services.io.getProtocolHandler("resource")
                                      .QueryInterface(SpecialPowers.Ci.nsISubstitutingProtocolHandler);
   var extensionHandler = SpecialPowers.Services.io.getProtocolHandler("moz-extension")
                                      .QueryInterface(SpecialPowers.Ci.nsISubstitutingProtocolHandler);
+  var fileHandler = SpecialPowers.Cc["@mozilla.org/network/protocol;1?name=file"]
+                                  .getService(SpecialPowers.Ci.nsIFileProtocolHandler);
+
+  // Chrome script that adds handles for inserting substitutions and
+  // resolving symlinked paths in the parent process.
+  var script = SpecialPowers.loadChromeScript(() => {
+    const Ci = Components.interfaces;
+    const Cc = Components.classes;
+    const Cu = Components.utils;
+    Cu.import("resource://gre/modules/Services.jsm");
+
+    // Sets up a substitution in the parent process
+    this.addMessageListener("SetSubstitution", ({from, to}) => {
+      // Convert the passed |to| string to a URI.
+      // A null |to| value clears the substitution
+      if (to != null) {
+        var uri = Services.io.newURI(to);
+      }
+      Services.io.getProtocolHandler("moz-extension")
+                  .QueryInterface(Ci.nsISubstitutingProtocolHandler)
+                  .setSubstitution(from, uri);
+    });
+
+    // Gets a normalized (de-symlinked) path in the parent process
+    this.addMessageListener("ResolvePath", (path) => {
+      let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+      file.initWithPath(path);
+      file.normalize();
+      return file.path;
+    });
+  });
+
+  // An array of objects each containing a callback and a host
+  // for a substitution that has been set in either the parent
+  // or local child process and for which we are waiting for the
+  // observer notification in the child process.
+  var pendingSubstitutions = [];
+
+  // Adds a new callback to |pendingSubstitutions|.
+  function pushSubstitutionCallback(callback, host) {
+    let entry = {callback, host};
+    pendingSubstitutions.push(entry);
+  }
+
+  // Invoke the first callback found in |pendingSubstitutions|
+  // with a matching host.
+  function popSubstitutionCallback(host) {
+    for (let i = 0; i < pendingSubstitutions.length; i++) {
+      let entry = pendingSubstitutions[i];
+      if (host === entry.host) {
+        entry.callback();
+        pendingSubstitutions.splice(i, 1);
+        return;
+      }
+    }
+    // This indicates we installed a mapping in either the
+    // parent or the local child process, but never received an
+    // observer notification in the child for a mapping with
+    // a matching host.
+    ok(false, `popSubstitutionCallback(${host}) no match found`);
+  }
+
+  // Define an implementation of nsISubstitutionObserver and add it
+  // to this process' ExtensionProtocolHandler to observe substitutions
+  // as they are propagated to the child.
+  function SubstitutionObserver() {}
+  SubstitutionObserver.prototype = {
+    onSetSubstitution: SpecialPowers.wrapCallback(function(root, uri) {
+      popSubstitutionCallback(root);
+    }),
+    QueryInterface:
+      XPCOMUtils.generateQI([SpecialPowers.Ci.nsISupports,
+          SpecialPowers.Ci.nsISubstitutionObserver]),
+  };
+  var observer = new SubstitutionObserver();
+  var wrappedObserver = SpecialPowers.wrap(observer);
+  extensionHandler.addObserver(wrappedObserver);
+
+  // Set a substitution in the parent. The parent
+  // propagates all substitutions to child processes.
+  function globalSetSubstitution(chromeScript, from, to) {
+    var p = new Promise(function(resolve, reject) {
+      pushSubstitutionCallback(resolve, from);
+      chromeScript.sendSyncMessage("SetSubstitution", {from, to});
+    });
+    return p;
+  }
 
   SimpleTest.registerCleanupFunction(function() {
+      extensionHandler.removeObserver(wrappedObserver);
       policy1.active = false;
       policy2.active = false;
+      script.sendSyncMessage("SetSubstitution", {from: "cherise", to: null});
+      script.sendSyncMessage("SetSubstitution", {from: "liebchen", to: null});
   });
 
   addLoadEvent(function() {
 
     // First, get a file:// URI to something - open to suggestions on how to do
     //  this more easily.
     var resURI = SpecialPowers.Services.io.newURI("resource://testing-common/resource_test_file.html");
     var filePath = resourceHandler.resolveURI(resURI);
     ok(filePath.startsWith("file://"), "resource:// URI resolves where we expect: " + filePath);
-    var fileURI = SpecialPowers.Services.io.newURI(filePath);
+    var resFile = fileHandler.getFileFromURLSpec(filePath);
+
+    // Get normalized path to the test file. We already have a file object
+    // |resFile|, but its underlying path may contain a symlink we can't
+    // resolve in the child process.
+    let resolvedPath = script.sendSyncMessage("ResolvePath", resFile.path);
+    let file = SpecialPowers.Cc["@mozilla.org/file/local;1"]
+                            .createInstance(SpecialPowers.Ci.nsILocalFile);
+    info(`resolved test file path: ${resolvedPath}`);
+    file.initWithPath(resolvedPath);
+
+    // Setup the base directory URI string and a URI string to refer to
+    // the test file within that directory.
+    let cheriseURIStr = "moz-extension://cherise/" + file.leafName;
+    let liebchenURIStr = "moz-extension://liebchen/" + file.leafName;
+    let cheriseBaseDirURIStr = "file://" + file.parent.path + "/";
+    info(`cheriseURIStr: ${cheriseURIStr}`);
+    info(`liebchenURIStr: ${liebchenURIStr}`);
+    info(`cheriseBaseDirURIStr: ${cheriseBaseDirURIStr}`);
 
     function StubPolicy(id, accessible) {
       let policy = new WebExtensionPolicy(SpecialPowers.Cu.cloneInto({
         id: `imaginaryaddon-${id[0]}`,
         mozExtensionHostname: id,
-        baseURL: fileURI.spec,
+        baseURL: cheriseBaseDirURIStr,
 
         allowedOrigins: SpecialPowers.unwrap(new MatchPatternSet([])),
         webAccessibleResources: accessible ? [SpecialPowers.unwrap(new MatchGlob("*"))] : [],
         localizeCallback(string) {},
       }, module, {cloneFunctions: true, wrapReflectors: true}));
 
+      // Activating the policy results in a substitution being added,
+      // which triggers SubstitutionObserver.onSetSubstitution().
+      // All observer notifications must be accounted for (in this
+      // test to validate they are working correctly) so ignore this
+      // substitution when the observer gets notified.
+      pushSubstitutionCallback(() => {}, id);
       policy.active = true;
       return policy;
     }
 
-    // Register a moz-extension:// URI.
+    // Register a moz-extension:// URI locally.
     policy1 = StubPolicy("cherise", false);
     policy2 = StubPolicy("liebchen", false);
 
     //
     // Make sure that non-file:// URIs don't work.
     //
 
     // resource://
@@ -84,17 +199,20 @@ https://bugzilla.mozilla.org/show_bug.cg
       SpecialPowers.wrap(ifr).contentWindow
                    .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
                    .getInterface(SpecialPowers.Ci.nsIWebNavigation)
                    .loadURI(url, 0, null, null, null);
     }
 
 
     function setWhitelistCallback(paths) {
+      pushSubstitutionCallback(() => {}, policy1.mozExtensionHostname);
       policy1.active = false;
+
+      pushSubstitutionCallback(() => {}, policy2.mozExtensionHostname);
       policy2.active = false;
 
       policy1 = StubPolicy("cherise", paths.includes("cherise"));
       policy2 = StubPolicy("liebchen", paths.includes("liebchen"));
     }
 
     function testLoad(url, navigate, shouldThrow) {
       var ifr = document.createElement("iframe");
@@ -142,29 +260,31 @@ https://bugzilla.mozilla.org/show_bug.cg
         xhr.open("GET", url, true);
         xhr.send();
       });
     }
 
     //
     // Perform some loads and make sure they work correctly.
     //
-    testLoad.bind(null, "moz-extension://cherise", navigateFromChromeWithLocation)()
-    .then(testLoad.bind(null, "moz-extension://cherise", navigateFromChromeWithWebNav))
-    .then(testLoad.bind(null, "moz-extension://cherise", navigateWithLocation, /* shouldThrow = */ true))
-    .then(testXHR.bind(null, "moz-extension://cherise", /* shouldError = */ true))
+    globalSetSubstitution(script, "cherise", cheriseBaseDirURIStr)
+    .then(testLoad.bind(null, cheriseURIStr, navigateFromChromeWithLocation))
+    .then(testLoad.bind(null, cheriseURIStr, navigateFromChromeWithWebNav))
+    .then(testLoad.bind(null, cheriseURIStr, navigateWithLocation, /* shouldThrow = */ true))
+    .then(testXHR.bind(null, cheriseURIStr, /* shouldError = */ true))
     .then(setWhitelistCallback.bind(null, ["cherise"]))
-    .then(testLoad.bind(null, "moz-extension://cherise", navigateWithLocation))
-    .then(testXHR.bind(null, "moz-extension://cherise"))
-    .then(testLoad.bind(null, "moz-extension://liebchen", navigateWithLocation, /* shouldThrow = */ true))
-    .then(testXHR.bind(null, "moz-extension://liebchen", /* shouldError = */ true))
+    .then(testLoad.bind(null, cheriseURIStr, navigateWithLocation))
+    .then(testXHR.bind(null, cheriseURIStr))
+    .then(globalSetSubstitution(script, "liebchen", "moz-extension://cherise"))
+    .then(testLoad.bind(null, liebchenURIStr, navigateWithLocation, /* shouldThrow = */ true))
+    .then(testXHR.bind(null, liebchenURIStr, /* shouldError = */ true))
     .then(setWhitelistCallback.bind(null, ["cherise", "liebchen"]))
-    .then(testLoad.bind(null, "moz-extension://liebchen", navigateWithLocation))
-    .then(testLoad.bind(null, "moz-extension://liebchen", navigateWithSrc))
-    .then(testLoad.bind(null, "moz-extension://cherise", navigateWithSrc))
+    .then(testLoad.bind(null, liebchenURIStr, navigateWithLocation))
+    .then(testLoad.bind(null, liebchenURIStr, navigateWithSrc))
+    .then(testLoad.bind(null, cheriseURIStr, navigateWithSrc))
     .then(testLoad.bind(null, "moz-extension://cherise/_blank.html", navigateWithSrc))
     .then(SimpleTest.finish.bind(SimpleTest),
           function(e) { ok(false, "rejected promise: " + e); SimpleTest.finish() }
     );
   });
 
   </script>
 </head>
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -292,44 +292,47 @@ SubstitutingProtocolHandler::AllowPort(i
 // nsISubstitutingProtocolHandler
 //----------------------------------------------------------------------------
 
 nsresult
 SubstitutingProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI)
 {
   if (!baseURI) {
     mSubstitutions.Remove(root);
+    NotifyObservers(root, baseURI);
     return SendSubstitution(root, baseURI);
   }
 
   // If baseURI isn't a same-scheme URI, we can set the substitution immediately.
   nsAutoCString scheme;
   nsresult rv = baseURI->GetScheme(scheme);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!scheme.Equals(mScheme)) {
     if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")
         && !scheme.EqualsLiteral("app")) {
       NS_WARNING("Refusing to create substituting URI to non-file:// target");
       return NS_ERROR_INVALID_ARG;
     }
 
     mSubstitutions.Put(root, baseURI);
+    NotifyObservers(root, baseURI);
     return SendSubstitution(root, baseURI);
   }
 
   // baseURI is a same-type substituting URI, let's resolve it first.
   nsAutoCString newBase;
   rv = ResolveURI(baseURI, newBase);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIURI> newBaseURI;
   rv = mIOService->NewURI(newBase, nullptr, nullptr, getter_AddRefs(newBaseURI));
   NS_ENSURE_SUCCESS(rv, rv);
 
   mSubstitutions.Put(root, newBaseURI);
+  NotifyObservers(root, baseURI);
   return SendSubstitution(root, newBaseURI);
 }
 
 nsresult
 SubstitutingProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result)
 {
   NS_ENSURE_ARG_POINTER(result);
 
@@ -403,10 +406,43 @@ SubstitutingProtocolHandler::ResolveURI(
   if (MOZ_LOG_TEST(gResLog, LogLevel::Debug)) {
     nsAutoCString spec;
     uri->GetAsciiSpec(spec);
     MOZ_LOG(gResLog, LogLevel::Debug, ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get()));
   }
   return rv;
 }
 
+nsresult
+SubstitutingProtocolHandler::AddObserver(nsISubstitutionObserver* aObserver)
+{
+  NS_ENSURE_ARG(aObserver);
+  if (mObservers.Contains(aObserver)) {
+    return NS_ERROR_DUPLICATE_HANDLE;
+  }
+
+  mObservers.AppendElement(aObserver);
+  return NS_OK;
+}
+
+nsresult
+SubstitutingProtocolHandler::RemoveObserver(nsISubstitutionObserver* aObserver)
+{
+  NS_ENSURE_ARG(aObserver);
+  if (!mObservers.Contains(aObserver)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  mObservers.RemoveElement(aObserver);
+  return NS_OK;
+}
+
+void
+SubstitutingProtocolHandler::NotifyObservers(const nsACString& aRoot,
+                                             nsIURI* aBaseURI)
+{
+  for (size_t i = 0; i < mObservers.Length(); ++i) {
+    mObservers[i]->OnSetSubstitution(aRoot, aBaseURI);
+  }
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.h
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -6,16 +6,17 @@
 
 #ifndef SubstitutingProtocolHandler_h___
 #define SubstitutingProtocolHandler_h___
 
 #include "nsISubstitutingProtocolHandler.h"
 
 #include "nsInterfaceHashtable.h"
 #include "nsIOService.h"
+#include "nsISubstitutionObserver.h"
 #include "nsStandardURL.h"
 #include "mozilla/chrome/RegistryMessageUtils.h"
 #include "mozilla/Maybe.h"
 
 class nsIIOService;
 
 namespace mozilla {
 namespace net {
@@ -68,21 +69,29 @@ protected:
   virtual MOZ_MUST_USE nsresult SubstituteChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result)
   {
     return NS_OK;
   }
 
   nsIIOService* IOService() { return mIOService; }
 
 private:
+  // Notifies all observers that a new substitution from |aRoot| to
+  // |aBaseURI| has been set/installed for this protocol handler.
+  void NotifyObservers(const nsACString& aRoot, nsIURI* aBaseURI);
+
   nsCString mScheme;
   Maybe<uint32_t> mFlags;
   nsInterfaceHashtable<nsCStringHashKey,nsIURI> mSubstitutions;
   nsCOMPtr<nsIIOService> mIOService;
 
+  // The list of observers added with AddObserver that will be
+  // notified when substitutions are set or unset.
+  nsTArray<nsCOMPtr<nsISubstitutionObserver>> mObservers;
+
   // In general, we expect the principal of a document loaded from a
   // substituting URI to be a codebase principal for that URI (rather than
   // a principal for whatever is underneath). However, this only works if
   // the protocol handler for the underlying URI doesn't set an explicit
   // owner (which chrome:// does, for example). So we want to require that
   // substituting URIs only map to other URIs of the same type, or to
   // file:// and jar:// URIs.
   //
--- a/netwerk/protocol/res/moz.build
+++ b/netwerk/protocol/res/moz.build
@@ -2,16 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 XPIDL_SOURCES += [
     'nsIResProtocolHandler.idl',
     'nsISubstitutingProtocolHandler.idl',
+    'nsISubstitutionObserver.idl',
 ]
 
 XPIDL_MODULE = 'necko_res'
 
 EXPORTS.mozilla.net += [
     'ExtensionProtocolHandler.h',
     'SubstitutingProtocolHandler.h',
 ]
--- a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -1,15 +1,17 @@
 /* -*- 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/. */
 
 #include "nsIProtocolHandler.idl"
 
+interface nsISubstitutionObserver;
+
 /**
  * Protocol handler superinterface for a protocol which performs substitutions
  * from URIs of its scheme to URIs of another scheme.
  */
 [scriptable, uuid(154c64fd-a69e-4105-89f8-bd7dfe621372)]
 interface nsISubstitutingProtocolHandler : nsIProtocolHandler
 {
   /**
@@ -38,9 +40,23 @@ interface nsISubstitutingProtocolHandler
   /**
    * Utility function to resolve a substituted URI.  A resolved URI is not
    * guaranteed to reference a resource that exists (ie. opening a channel to
    * the resolved URI may fail).
    *
    * @throws NS_ERROR_NOT_AVAILABLE if resURI.host() is an unknown root key.
    */
   [must_use] AUTF8String resolveURI(in nsIURI resURI);
+
+  /**
+   * Adds an observer that will be notified on the main thread whenever a
+   * substitition is set or unset. Notifications are not sent for substitutions
+   * that were set prior to the observer being added. Holds an owning reference
+   * to the observer until removeObserver is called or the protocol handler is
+   * destroyed.
+   */
+  [must_use] void addObserver(in nsISubstitutionObserver observer);
+
+  /**
+   * Removes the observer.
+   */
+  [must_use] void removeObserver(in nsISubstitutionObserver observer);
 };
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/res/nsISubstitutionObserver.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * An observer of substitutions being set or unset on a
+ * SubstitutingProtocolHandler. Useful for receiving asynchronous notification
+ * in a child process after a substitution has been set in the parent process
+ * and is propagated to the child.
+ */
+[scriptable, uuid(492c9192-3803-4e2b-8373-d25fe55f5588)]
+interface nsISubstitutionObserver : nsISupports
+{
+    /**
+     * To be called when a substition has been set or unset on a protocol
+     * handler. Unset operations are identified by a null URI argument.
+     *
+     * @param aRoot the root key of the mapping
+     * @param aBaseURI the base URI to be substituted for the root key by the
+     *        protocol handler. For notifications triggered by unset
+     *        operations (i.e., when is a substitution is removed from the
+     *        protocol handler) this argument is null.
+     */
+    void onSetSubstitution(in ACString aRoot, in nsIURI aBaseURI);
+};
--- a/netwerk/protocol/res/nsResProtocolHandler.h
+++ b/netwerk/protocol/res/nsResProtocolHandler.h
@@ -8,16 +8,18 @@
 
 #include "SubstitutingProtocolHandler.h"
 
 #include "nsIResProtocolHandler.h"
 #include "nsInterfaceHashtable.h"
 #include "nsWeakReference.h"
 #include "nsStandardURL.h"
 
+class nsISubstitutionObserver;
+
 struct SubstitutionMapping;
 class nsResProtocolHandler final : public nsIResProtocolHandler,
                                    public mozilla::SubstitutingProtocolHandler,
                                    public nsSupportsWeakReference
 {
 public:
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIRESPROTOCOLHANDLER
@@ -43,16 +45,26 @@ public:
         return mozilla::SubstitutingProtocolHandler::HasSubstitution(aRoot, aResult);
     }
 
     NS_IMETHOD ResolveURI(nsIURI *aResURI, nsACString& aResult) override
     {
         return mozilla::SubstitutingProtocolHandler::ResolveURI(aResURI, aResult);
     }
 
+    NS_IMETHOD AddObserver(nsISubstitutionObserver *aObserver) override
+    {
+        return mozilla::SubstitutingProtocolHandler::AddObserver(aObserver);
+    }
+
+    NS_IMETHOD RemoveObserver(nsISubstitutionObserver *aObserver) override
+    {
+        return mozilla::SubstitutingProtocolHandler::RemoveObserver(aObserver);
+    }
+
 protected:
     MOZ_MUST_USE nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult) override;
     virtual ~nsResProtocolHandler() {}
 
     MOZ_MUST_USE bool ResolveSpecialCases(const nsACString& aHost,
                                           const nsACString& aPath,
                                           const nsACString& aPathname,
                                           nsACString& aResult) override;