Bug 1017302 - Faster mapping of paths to add-on IDs (r=irving)
authorBill McCloskey <wmccloskey@mozilla.com>
Sat, 21 Jun 2014 11:54:28 -0700
changeset 189941 5d6ecbe3628a6809b7938e8c9f7649678380a85c
parent 189940 a0265bd71a156961e87f9f3a99763067ce1b577c
child 189942 85c26e838324b53e1f3437a9ee2db950785c1ac4
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersirving
bugs1017302
milestone33.0a1
Bug 1017302 - Faster mapping of paths to add-on IDs (r=irving)
toolkit/components/build/nsToolkitCompsCID.h
toolkit/components/build/nsToolkitCompsModule.cpp
toolkit/mozapps/extensions/AddonPathService.cpp
toolkit/mozapps/extensions/AddonPathService.h
toolkit/mozapps/extensions/amIAddonPathService.idl
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/moz.build
toolkit/mozapps/extensions/test/xpcshell/test_addon_path_service.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/components/build/nsToolkitCompsCID.h
+++ b/toolkit/components/build/nsToolkitCompsCID.h
@@ -90,16 +90,19 @@
 #define NS_APPSTARTUP_CONTRACTID \
   "@mozilla.org/toolkit/app-startup;1"
 
 #if defined(USE_MOZ_UPDATER)
 #define NS_UPDATEPROCESSOR_CONTRACTID \
   "@mozilla.org/updates/update-processor;1"
 #endif
 
+#define NS_ADDONPATHSERVICE_CONTRACTID \
+    "@mozilla.org/addon-path-service;1"
+
 /////////////////////////////////////////////////////////////////////////////
 
 // {A0CCAAF8-09DA-44D8-B250-9AC3E93C8117}
 #define NS_ALERTSSERVICE_CID \
 { 0xa0ccaaf8, 0x9da, 0x44d8, { 0xb2, 0x50, 0x9a, 0xc3, 0xe9, 0x3c, 0x81, 0x17 } }
 
 // {84E11F80-CA55-11DD-AD8B-0800200C9A66}
 #define NS_SYSTEMALERTSSERVICE_CID \
@@ -175,8 +178,11 @@
 { 0xf3dcf644, 0x79e8, 0x4f59, { 0xa1, 0xbb, 0x87, 0x84, 0x54, 0x48, 0x8e, 0xf9 } }
 #endif
 
 #define NS_APPLICATION_REPUTATION_SERVICE_CONTRACTID \
   "@mozilla.org/downloads/application-reputation-service;1"
 
 #define NS_APPLICATION_REPUTATION_SERVICE_CID \
 { 0x8576c950, 0xf4a2, 0x11e2, { 0xb7, 0x78, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
+
+#define NS_ADDON_PATH_SERVICE_CID \
+{ 0xa39f39d0, 0xdfb6, 0x11e3, { 0x8b, 0x68, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
--- a/toolkit/components/build/nsToolkitCompsModule.cpp
+++ b/toolkit/components/build/nsToolkitCompsModule.cpp
@@ -30,16 +30,17 @@
 #include "nsUrlClassifierStreamUpdater.h"
 #include "nsUrlClassifierUtils.h"
 #include "nsUrlClassifierPrefixSet.h"
 #endif
 
 #include "nsBrowserStatusFilter.h"
 #include "mozilla/FinalizationWitnessService.h"
 #include "mozilla/NativeOSFileInternals.h"
+#include "mozilla/AddonPathService.h"
 
 using namespace mozilla;
 
 /////////////////////////////////////////////////////////////////////////////
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppStartup, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUserInfo)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFindService)
@@ -86,16 +87,18 @@ nsUrlClassifierDBServiceConstructor(nsIS
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter)
 #if defined(USE_MOZ_UPDATER)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor)
 #endif
 NS_GENERIC_FACTORY_CONSTRUCTOR(FinalizationWitnessService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(NativeOSFileInternalsService)
 
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonPathService, AddonPathService::GetInstance)
+
 NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
 NS_DEFINE_NAMED_CID(NS_USERINFO_CID);
 NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID);
 #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
 NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_DOWNLOADMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOWNLOADPLATFORM_CID);
@@ -110,16 +113,17 @@ NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERSTRE
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERUTILS_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILTER_CID);
 #if defined(USE_MOZ_UPDATER)
 NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
 #endif
 NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NATIVE_OSFILE_INTERNALS_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_ADDON_PATH_SERVICE_CID);
 
 static const Module::CIDEntry kToolkitCIDs[] = {
   { &kNS_TOOLKIT_APPSTARTUP_CID, false, nullptr, nsAppStartupConstructor },
   { &kNS_USERINFO_CID, false, nullptr, nsUserInfoConstructor },
   { &kNS_ALERTSSERVICE_CID, false, nullptr, nsAlertsServiceConstructor },
 #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
   { &kNS_PARENTALCONTROLSSERVICE_CID, false, nullptr, nsParentalControlsServiceConstructor },
 #endif
@@ -136,16 +140,17 @@ static const Module::CIDEntry kToolkitCI
   { &kNS_URLCLASSIFIERUTILS_CID, false, nullptr, nsUrlClassifierUtilsConstructor },
 #endif
   { &kNS_BROWSERSTATUSFILTER_CID, false, nullptr, nsBrowserStatusFilterConstructor },
 #if defined(USE_MOZ_UPDATER)
   { &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor },
 #endif
   { &kFINALIZATIONWITNESSSERVICE_CID, false, nullptr, FinalizationWitnessServiceConstructor },
   { &kNATIVE_OSFILE_INTERNALS_SERVICE_CID, false, nullptr, NativeOSFileInternalsServiceConstructor },
+  { &kNS_ADDON_PATH_SERVICE_CID, false, nullptr, AddonPathServiceConstructor },
   { nullptr }
 };
 
 static const Module::ContractIDEntry kToolkitContracts[] = {
   { NS_APPSTARTUP_CONTRACTID, &kNS_TOOLKIT_APPSTARTUP_CID },
   { NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID },
   { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID },
 #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
@@ -165,16 +170,17 @@ static const Module::ContractIDEntry kTo
   { NS_URLCLASSIFIERUTILS_CONTRACTID, &kNS_URLCLASSIFIERUTILS_CID },
 #endif
   { NS_BROWSERSTATUSFILTER_CONTRACTID, &kNS_BROWSERSTATUSFILTER_CID },
 #if defined(USE_MOZ_UPDATER)
   { NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
 #endif
   { FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
   { NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID, &kNATIVE_OSFILE_INTERNALS_SERVICE_CID },
+  { NS_ADDONPATHSERVICE_CONTRACTID, &kNS_ADDON_PATH_SERVICE_CID },
   { nullptr }
 };
 
 static const Module kToolkitModule = {
   Module::kVersion,
   kToolkitCIDs,
   kToolkitContracts
 };
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/AddonPathService.cpp
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 8; 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 "AddonPathService.h"
+
+#include "amIAddonManager.h"
+#include "nsIURI.h"
+#include "nsXULAppAPI.h"
+#include "jsapi.h"
+#include "nsCxPusher.h"
+#include "nsServiceManagerUtils.h"
+#include "nsLiteralString.h"
+#include "nsThreadUtils.h"
+#include "nsIIOService.h"
+#include "nsNetUtil.h"
+#include "nsIResProtocolHandler.h"
+#include "nsIChromeRegistry.h"
+#include "nsIJARURI.h"
+#include "mozilla/AddonPathService.h"
+#include "mozilla/Omnijar.h"
+
+#include <algorithm>
+
+namespace mozilla {
+
+struct PathEntryComparator
+{
+  typedef AddonPathService::PathEntry PathEntry;
+
+  bool Equals(const PathEntry& entry1, const PathEntry& entry2) const
+  {
+    return entry1.mPath == entry2.mPath;
+  }
+
+  bool LessThan(const PathEntry& entry1, const PathEntry& entry2) const
+  {
+    return entry1.mPath < entry2.mPath;
+  }
+};
+
+AddonPathService::AddonPathService()
+{
+}
+
+AddonPathService::~AddonPathService()
+{
+  sInstance = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(AddonPathService, amIAddonPathService)
+
+AddonPathService *AddonPathService::sInstance;
+
+/* static */ AddonPathService*
+AddonPathService::GetInstance()
+{
+  if (!sInstance) {
+    sInstance = new AddonPathService();
+  }
+  NS_ADDREF(sInstance);
+  return sInstance;
+}
+
+JSAddonId*
+AddonPathService::Find(const nsAString& path)
+{
+  // Use binary search to find the nearest entry that is <= |path|.
+  PathEntryComparator comparator;
+  unsigned index = mPaths.IndexOfFirstElementGt(PathEntry(path, nullptr), comparator);
+  if (index == 0) {
+    return nullptr;
+  }
+  const PathEntry& entry = mPaths[index - 1];
+
+  // Return the entry's addon if its path is a prefix of |path|.
+  if (StringBeginsWith(path, entry.mPath)) {
+    return entry.mAddonId;
+  }
+  return nullptr;
+}
+
+NS_IMETHODIMP
+AddonPathService::FindAddonId(const nsAString& path, nsAString& addonIdString)
+{
+  if (JSAddonId* id = Find(path)) {
+    addonIdString = JS::CharsZOfAddonId(id);
+  }
+  return NS_OK;
+}
+
+/* static */ JSAddonId*
+AddonPathService::FindAddonId(const nsAString& path)
+{
+  // If no service has been created, then we're not going to find anything.
+  if (!sInstance) {
+    return nullptr;
+  }
+
+  return sInstance->Find(path);
+}
+
+NS_IMETHODIMP
+AddonPathService::InsertPath(const nsAString& path, const nsAString& addonIdString)
+{
+  AutoSafeJSContext cx;
+  JS::RootedString str(cx, JS_NewUCStringCopyN(cx,
+                                               addonIdString.BeginReading(),
+                                               addonIdString.Length()));
+  JSAddonId* addonId = JS::NewAddonId(cx, str);
+
+  // Add the new path in sorted order.
+  PathEntryComparator comparator;
+  mPaths.InsertElementSorted(PathEntry(path, addonId), comparator);
+  return NS_OK;
+}
+
+static nsresult
+ResolveURI(nsIURI* aURI, nsAString& out)
+{
+  bool equals;
+  nsresult rv;
+  nsCOMPtr<nsIURI> uri;
+  nsAutoCString spec;
+
+  // Resolve resource:// URIs. At the end of this if/else block, we
+  // have both spec and uri variables identifying the same URI.
+  if (NS_SUCCEEDED(aURI->SchemeIs("resource", &equals)) && equals) {
+    nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+    if (NS_WARN_IF(NS_FAILED(rv)))
+      return rv;
+
+    nsCOMPtr<nsIProtocolHandler> ph;
+    rv = ioService->GetProtocolHandler("resource", getter_AddRefs(ph));
+    if (NS_WARN_IF(NS_FAILED(rv)))
+      return rv;
+
+    nsCOMPtr<nsIResProtocolHandler> irph(do_QueryInterface(ph, &rv));
+    if (NS_WARN_IF(NS_FAILED(rv)))
+      return rv;
+
+    rv = irph->ResolveURI(aURI, spec);
+    if (NS_WARN_IF(NS_FAILED(rv)))
+      return rv;
+
+    rv = ioService->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
+    if (NS_WARN_IF(NS_FAILED(rv)))
+      return rv;
+  } else if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &equals)) && equals) {
+    nsCOMPtr<nsIChromeRegistry> chromeReg =
+      mozilla::services::GetChromeRegistryService();
+    if (NS_WARN_IF(!chromeReg))
+      return NS_ERROR_UNEXPECTED;
+
+    rv = chromeReg->ConvertChromeURL(aURI, getter_AddRefs(uri));
+    if (NS_WARN_IF(NS_FAILED(rv)))
+      return rv;
+  } else {
+    uri = aURI;
+  }
+
+  if (NS_SUCCEEDED(uri->SchemeIs("jar", &equals)) && equals) {
+    nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv)))
+      return rv;
+
+    nsCOMPtr<nsIURI> jarFileURI;
+    rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI));
+    if (NS_WARN_IF(NS_FAILED(rv)))
+      return rv;
+
+    return ResolveURI(jarFileURI, out);
+  }
+
+  if (NS_SUCCEEDED(uri->SchemeIs("file", &equals)) && equals) {
+    nsCOMPtr<nsIFileURL> baseFileURL = do_QueryInterface(uri, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv)))
+      return rv;
+
+    nsCOMPtr<nsIFile> file;
+    rv = baseFileURL->GetFile(getter_AddRefs(file));
+    if (NS_WARN_IF(NS_FAILED(rv)))
+      return rv;
+
+    return file->GetPath(out);
+  }
+  return NS_ERROR_FAILURE;
+}
+
+JSAddonId*
+MapURIToAddonID(nsIURI* aURI)
+{
+  if (!NS_IsMainThread() || XRE_GetProcessType() != GeckoProcessType_Default) {
+    return nullptr;
+  }
+
+  nsAutoString filePath;
+  nsresult rv = ResolveURI(aURI, filePath);
+  if (NS_FAILED(rv))
+    return nullptr;
+
+  nsCOMPtr<nsIFile> greJar = Omnijar::GetPath(Omnijar::GRE);
+  nsCOMPtr<nsIFile> appJar = Omnijar::GetPath(Omnijar::APP);
+  if (greJar && appJar) {
+    nsAutoString greJarString, appJarString;
+    if (NS_FAILED(greJar->GetPath(greJarString)) || NS_FAILED(appJar->GetPath(appJarString)))
+      return nullptr;
+
+    // If |aURI| is part of either Omnijar, then it can't be part of an
+    // add-on. This catches pretty much all URLs for Firefox content.
+    if (filePath.Equals(greJarString) || filePath.Equals(appJarString))
+      return nullptr;
+  }
+
+  // If it's not part of Firefox, we resort to binary searching through the
+  // add-on paths.
+  return AddonPathService::FindAddonId(filePath);
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/AddonPathService.h
@@ -0,0 +1,54 @@
+//* -*- Mode: C++; tab-width: 8; 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 AddonPathService_h
+#define AddonPathService_h
+
+#include "amIAddonPathService.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIURI;
+class JSAddonId;
+
+namespace mozilla {
+
+JSAddonId*
+MapURIToAddonID(nsIURI* aURI);
+
+class AddonPathService MOZ_FINAL : public amIAddonPathService
+{
+public:
+  AddonPathService();
+  virtual ~AddonPathService();
+
+  static AddonPathService* GetInstance();
+
+  JSAddonId* Find(const nsAString& path);
+  static JSAddonId* FindAddonId(const nsAString& path);
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_AMIADDONPATHSERVICE
+
+  struct PathEntry
+  {
+    nsString mPath;
+    JSAddonId* mAddonId;
+
+    PathEntry(const nsAString& aPath, JSAddonId* aAddonId)
+     : mPath(aPath), mAddonId(aAddonId)
+    {}
+  };
+
+private:
+  // Paths are stored sorted in order of their mPath.
+  nsTArray<PathEntry> mPaths;
+
+  static AddonPathService* sInstance;
+};
+
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/amIAddonPathService.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsISupports.idl"
+
+/**
+ * This service maps file system paths where add-ons reside to the ID
+ * of the add-on. Paths are added by the add-on manager. They can
+ * looked up by anyone.
+ */
+[scriptable, uuid(fcd9e270-dfb1-11e3-8b68-0800200c9a66)]
+interface amIAddonPathService : nsISupports
+{
+  /**
+   * Given a path to a file, return the ID of the add-on that the file belongs
+   * to. Returns an empty string if there is no add-on there. Note that if an
+   * add-on is located at /a/b/c, then looking up the path /a/b/c/d will return
+   * that add-on.
+   */
+  AString findAddonId(in AString path);
+
+  /**
+   * Call this function to inform the service that the given file system path is
+   * associated with the given add-on ID.
+   */
+  void insertPath(in AString path, in AString addonId);
+};
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1677,16 +1677,19 @@ this.XPIProvider = {
    *
    * Mappings should not be removed at any point. This is so that the mappings
    * will be still valid after an add-on gets disabled or uninstalled, as
    * consumers may still have URIs of (leaked) resources they want to map.
    */
   _addURIMapping: function XPI__addURIMapping(aID, aFile) {
     logger.info("Mapping " + aID + " to " + aFile.path);
     this._addonFileMap.set(aID, aFile.path);
+
+    let service = Cc["@mozilla.org/addon-path-service;1"].getService(Ci.amIAddonPathService);
+    service.insertPath(aFile.path, aID);
   },
 
   /**
    * Resolve a URI back to physical file.
    *
    * Of course, this works only for URIs pointing to local resources.
    *
    * @param  aURI
--- a/toolkit/mozapps/extensions/moz.build
+++ b/toolkit/mozapps/extensions/moz.build
@@ -4,16 +4,17 @@
 # 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/.
 
 DIRS += ['internal']
 TEST_DIRS += ['test']
 
 XPIDL_SOURCES += [
     'amIAddonManager.idl',
+    'amIAddonPathService.idl',
     'amIWebInstaller.idl',
     'amIWebInstallListener.idl',
 ]
 
 XPIDL_MODULE = 'extensions'
 
 EXTRA_COMPONENTS += [
     'addonManager.js',
@@ -40,8 +41,20 @@ EXTRA_PP_JS_MODULES += [
 if CONFIG['MOZ_UPDATE_CHANNEL'] not in ('aurora', 'beta', 'release', 'esr'):
     DEFINES['MOZ_COMPATIBILITY_NIGHTLY'] = 1
 
 # Additional debugging info is exposed in debug builds
 if CONFIG['MOZ_EM_DEBUG']:
     DEFINES['MOZ_EM_DEBUG'] = 1
 
 JAR_MANIFESTS += ['jar.mn']
+
+EXPORTS.mozilla += [
+    'AddonPathService.h',
+]
+
+UNIFIED_SOURCES += [
+    'AddonPathService.cpp'
+]
+
+FAIL_ON_WARNINGS = True
+
+FINAL_LIBRARY = 'xulapp_s'
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_addon_path_service.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+let service = Components.classes["@mozilla.org/addon-path-service;1"].getService(Components.interfaces.amIAddonPathService);
+
+function insert(path, value)
+{
+  service.insertPath("/test/" + path, value);
+}
+
+function find(path)
+{
+  return service.findAddonId("/test/" + path);
+}
+
+function run_test()
+{
+  insert("abc", "10");
+  insert("def", "11");
+  insert("axy", "12");
+  insert("defghij", "13");
+  insert("defghi", "14");
+
+  do_check_eq(find("abc"), "10");
+  do_check_eq(find("abc123"), "10");
+  do_check_eq(find("def"), "11");
+  do_check_eq(find("axy"), "12");
+  do_check_eq(find("axy1"), "12");
+  do_check_eq(find("defghij"), "13");
+  do_check_eq(find("abd"), "");
+  do_check_eq(find("x"), "");
+
+  insert("file:///home/billm/mozilla/in4/objdir-ff-dbg/dist/bin/browser/extensions/%7B972ce4c6-7e08-4474-a285-3208198ce6fd%7D/", "{972ce4c6-7e08-4474-a285-3208198ce6fd}");
+  insert("file:///home/billm/mozilla/addons/dl-helper-workspace/addon/", "{b9db16a4-6edc-47ec-a1f4-b86292ed211d}");
+
+  do_check_eq(find("file:///home/billm/mozilla/addons/dl-helper-workspace/addon/local/modules/medialist-manager.jsm"), "{b9db16a4-6edc-47ec-a1f4-b86292ed211d}");
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -4,12 +4,13 @@ tail =
 firefox-appdir = browser
 dupe-manifest =
 support-files =
   data/**
   xpcshell-shared.ini
 
 [include:xpcshell-shared.ini]
 
+[test_addon_path_service.js]
 [test_asyncBlocklistLoad.js]
 [test_DeferredSave.js]
 [test_XPIcancel.js]
 [test_metadata_update.js]