Merge mozilla-central to autoland. a=merge CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Wed, 21 Nov 2018 11:48:53 +0200
changeset 503887 95902e82a59eef77a3f5cde7014419e1f13ed763
parent 503886 658cd029a6a1f60ef28a273803439a3f3b1be0b0 (current diff)
parent 503876 7488645b27ac9b273d6785db04204684673b3657 (diff)
child 503888 cfde782a9b546f9282469c932011644db7a396a1
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
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
Merge mozilla-central to autoland. a=merge CLOSED TREE
tools/quitter/bootstrap.js
tools/quitter/chrome.manifest
tools/quitter/install.rdf
tools/quitter/jar.mn
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1544,16 +1544,19 @@ pref("browser.contentblocking.reportBrea
 pref("browser.contentblocking.rejecttrackers.reportBreakage.enabled", true);
 
 pref("browser.contentblocking.reportBreakage.url", "https://tracking-protection-issues.herokuapp.com/new");
 
 pref("browser.contentblocking.introCount", 0);
 
 pref("privacy.trackingprotection.introURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tracking-protection/start/");
 
+// Workaround for Google Recaptcha
+pref("urlclassifier.trackingAnnotationSkipURLs", "google.com/recaptcha/,*.google.com/recaptcha/");
+
 // Always enable newtab segregation using containers
 pref("privacy.usercontext.about_newtab_segregation.enabled", true);
 // Enable Contextual Identity Containers
 #ifdef NIGHTLY_BUILD
 pref("privacy.userContext.enabled", true);
 pref("privacy.userContext.ui.enabled", true);
 
 // 0 disables long press, 1 when clicked, the menu is shown, 2 the menu is shown after X milliseconds.
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -11182,16 +11182,32 @@ nsContentUtils::StringifyJSON(JSContext*
   return true;
 }
 
 /* static */ bool
 nsContentUtils::IsURIInPrefList(nsIURI* aURI, const char* aPrefName)
 {
   MOZ_ASSERT(aPrefName);
 
+  nsAutoCString blackList;
+  Preferences::GetCString(aPrefName, blackList);
+  ToLowerCase(blackList);
+  return IsURIInList(aURI, blackList);
+}
+
+/* static */ bool
+nsContentUtils::IsURIInList(nsIURI* aURI, const nsCString& aBlackList)
+{
+#ifdef DEBUG
+  nsAutoCString blackListLowerCase(aBlackList);
+  ToLowerCase(blackListLowerCase);
+  MOZ_ASSERT(blackListLowerCase.Equals(aBlackList),
+             "The aBlackList argument should be lower-case");
+#endif
+
   if (!aURI) {
     return false;
   }
 
   nsAutoCString scheme;
   aURI->GetScheme(scheme);
   if (!scheme.EqualsLiteral("http") &&
       !scheme.EqualsLiteral("https")) {
@@ -11200,46 +11216,43 @@ nsContentUtils::IsURIInPrefList(nsIURI* 
 
   nsAutoCString host;
   aURI->GetHost(host);
   if (host.IsEmpty()) {
     return false;
   }
   ToLowerCase(host);
 
-  nsAutoCString blackList;
-  Preferences::GetCString(aPrefName, blackList);
-  ToLowerCase(blackList);
-  if (blackList.IsEmpty()) {
+  if (aBlackList.IsEmpty()) {
     return false;
   }
 
   // The list is comma separated domain list.  Each item may start with "*.".
   // If starts with "*.", it matches any sub-domains.
 
   for (;;) {
-    int32_t index = blackList.Find(host, false);
+    int32_t index = aBlackList.Find(host, false);
     if (index >= 0 &&
-        static_cast<uint32_t>(index) + host.Length() <= blackList.Length() &&
+        static_cast<uint32_t>(index) + host.Length() <= aBlackList.Length() &&
         // If start of the black list or next to ","?
-        (!index || blackList[index - 1] == ',')) {
+        (!index || aBlackList[index - 1] == ',')) {
       // If end of the black list or immediately before ","?
       size_t indexAfterHost = index + host.Length();
-      if (indexAfterHost == blackList.Length() ||
-          blackList[indexAfterHost] == ',') {
+      if (indexAfterHost == aBlackList.Length() ||
+          aBlackList[indexAfterHost] == ',') {
         return true;
       }
       // If next character is '/', we need to check the path too.
       // We assume the path in blacklist means "/foo" + "*".
-      if (blackList[indexAfterHost] == '/') {
-        int32_t endOfPath = blackList.Find(",", false, indexAfterHost);
+      if (aBlackList[indexAfterHost] == '/') {
+        int32_t endOfPath = aBlackList.Find(",", false, indexAfterHost);
         nsDependentCSubstring::size_type length =
           endOfPath < 0 ? static_cast<nsDependentCSubstring::size_type>(-1) :
                           endOfPath - indexAfterHost;
-        nsDependentCSubstring pathInBlackList(blackList,
+        nsDependentCSubstring pathInBlackList(aBlackList,
                                               indexAfterHost, length);
         nsAutoCString filePath;
         aURI->GetFilePath(filePath);
         ToLowerCase(filePath);
         if (StringBeginsWith(filePath, pathInBlackList)) {
           return true;
         }
       }
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1206,16 +1206,23 @@ public:
                      uint32_t *aColumnOut, nsString& aMessageOut);
 
   static nsresult CalculateBufferSizeForImage(const uint32_t& aStride,
                                             const mozilla::gfx::IntSize& aImageSize,
                                             const mozilla::gfx::SurfaceFormat& aFormat,
                                             size_t* aMaxBufferSize,
                                             size_t* aUsedBufferSize);
 
+  // Returns true if the URI's host is contained in a list which is a comma
+  // separated domain list.  Each item may start with "*.".  If starts with
+  // "*.", it matches any sub-domains.
+  // The aList argument must be a lower-case string.
+  static bool
+  IsURIInList(nsIURI* aURI, const nsCString& aList);
+
   // Returns true if the URI's host is contained in a pref list which is a comma
   // separated domain list.  Each item may start with "*.".  If starts with
   // "*.", it matches any sub-domains.
   static bool
   IsURIInPrefList(nsIURI* aURI, const char* aPrefName);
 
 private:
   /**
--- a/js/src/devtools/automation/winbuildenv.sh
+++ b/js/src/devtools/automation/winbuildenv.sh
@@ -20,16 +20,20 @@ echo "export ORIGINAL_LIB=$LIB"
 echo "export ORIGINAL_LIBPATH=$LIBPATH"
 
 if [ -n "$USE_64BIT" ]; then
   . $topsrcdir/build/win64/mozconfig.vs-latest
 else
   . $topsrcdir/build/win32/mozconfig.vs-latest
 fi
 
+mk_export_correct_style CC
+mk_export_correct_style CXX
+mk_export_correct_style LINKER
+
 # PATH also needs to point to mozmake.exe, which can come from either
 # newer mozilla-build or tooltool.
 if ! which mozmake 2>/dev/null; then
     export PATH="$PATH:$SOURCE/.."
     if ! which mozmake 2>/dev/null; then
   TT_SERVER=${TT_SERVER:-https://tooltool.mozilla-releng.net/}
   ( cd $SOURCE/..; $SOURCE/mach artifact toolchain -v --tooltool-manifest $SOURCE/browser/config/tooltool-manifests/${platform:-win32}/releng.manifest --tooltool-url $TT_SERVER --retry 4${TOOLTOOL_CACHE:+ --cache-dir ${TOOLTOOL_CACHE}}${MOZ_TOOLCHAINS:+ ${MOZ_TOOLCHAINS}})
     fi
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -5277,50 +5277,58 @@ CompareBorders(const nsIFrame*  aTableFr
 
 static bool
 Perpendicular(mozilla::LogicalSide aSide1,
               mozilla::LogicalSide aSide2)
 {
   return IsInline(aSide1) != IsInline(aSide2);
 }
 
+// Initial value indicating that BCCornerInfo's ownerStyle hasn't been set yet.
+#define BORDER_STYLE_UNSET 0xF
+
 // XXX allocate this as number-of-cols+1 instead of number-of-cols+1 * number-of-rows+1
 struct BCCornerInfo
 {
   BCCornerInfo() { ownerColor = 0; ownerWidth = subWidth = ownerElem = subSide =
                    subElem = hasDashDot = numSegs = bevel = 0; ownerSide = eLogicalSideBStart;
-                   ownerStyle = 0xFF; subStyle = NS_STYLE_BORDER_STYLE_SOLID;  }
+                   ownerStyle = BORDER_STYLE_UNSET;
+                   subStyle = NS_STYLE_BORDER_STYLE_SOLID; }
+
   void Set(mozilla::LogicalSide aSide,
            BCCellBorder  border);
 
   void Update(mozilla::LogicalSide aSide,
               BCCellBorder  border);
 
   nscolor   ownerColor;     // color of borderOwner
   uint16_t  ownerWidth;     // pixel width of borderOwner
   uint16_t  subWidth;       // pixel width of the largest border intersecting the border perpendicular
                             // to ownerSide
   uint32_t  ownerSide:2;    // LogicalSide (e.g eLogicalSideBStart, etc) of the border
                             // owning the corner relative to the corner
-  uint32_t  ownerElem:3;    // elem type (e.g. eTable, eGroup, etc) owning the corner
-  uint32_t  ownerStyle:8;   // border style of ownerElem
+  uint32_t  ownerElem:4;    // elem type (e.g. eTable, eGroup, etc) owning the corner
+  uint32_t  ownerStyle:4;   // border style of ownerElem
   uint32_t  subSide:2;      // side of border with subWidth relative to the corner
-  uint32_t  subElem:3;      // elem type (e.g. eTable, eGroup, etc) of sub owner
-  uint32_t  subStyle:8;     // border style of subElem
+  uint32_t  subElem:4;      // elem type (e.g. eTable, eGroup, etc) of sub owner
+  uint32_t  subStyle:4;     // border style of subElem
   uint32_t  hasDashDot:1;   // does a dashed, dotted segment enter the corner, they cannot be beveled
   uint32_t  numSegs:3;      // number of segments entering corner
   uint32_t  bevel:1;        // is the corner beveled (uses the above two fields together with subWidth)
-  // one bit is unused
+  // 7 bits are unused
 };
 
 void
 BCCornerInfo::Set(mozilla::LogicalSide aSide,
                   BCCellBorder  aBorder)
 {
-  ownerElem  = aBorder.owner;
+  // FIXME bug 1508921: We mask 4-bit BCBorderOwner enum to 3 bits to preserve
+  // buggy behavior found by the frame_above_rules_all.html mochitest.
+  ownerElem  = aBorder.owner & 0x7;
+
   ownerStyle = aBorder.style;
   ownerWidth = aBorder.width;
   ownerColor = aBorder.color;
   ownerSide  = aSide;
   hasDashDot = 0;
   numSegs    = 0;
   if (aBorder.width > 0) {
     numSegs++;
@@ -5334,30 +5342,30 @@ BCCornerInfo::Set(mozilla::LogicalSide a
   subElem    = eTableOwner;
   subStyle   = NS_STYLE_BORDER_STYLE_SOLID;
 }
 
 void
 BCCornerInfo::Update(mozilla::LogicalSide aSide,
                      BCCellBorder  aBorder)
 {
-  bool existingWins = false;
-  if (0xFF == ownerStyle) { // initial value indiating that it hasn't been set yet
+  if (ownerStyle == BORDER_STYLE_UNSET) {
     Set(aSide, aBorder);
   }
   else {
     bool isInline = IsInline(aSide); // relative to the corner
     BCCellBorder oldBorder, tempBorder;
     oldBorder.owner  = (BCBorderOwner) ownerElem;
     oldBorder.style =  ownerStyle;
     oldBorder.width =  ownerWidth;
     oldBorder.color =  ownerColor;
 
     LogicalSide oldSide  = LogicalSide(ownerSide);
 
+    bool existingWins = false;
     tempBorder = CompareBorders(CELL_CORNER, oldBorder, aBorder, isInline, &existingWins);
 
     ownerElem  = tempBorder.owner;
     ownerStyle = tempBorder.style;
     ownerWidth = tempBorder.width;
     ownerColor = tempBorder.color;
     if (existingWins) { // existing corner is dominant
       if (::Perpendicular(LogicalSide(ownerSide), aSide)) {
--- a/netwerk/base/nsChannelClassifier.cpp
+++ b/netwerk/base/nsChannelClassifier.cpp
@@ -59,16 +59,17 @@ static LazyLogModule gChannelClassifierL
 
 #undef LOG
 #define LOG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Info, args)
 #define LOG_DEBUG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Debug, args)
 #define LOG_WARN(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Warning, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(gChannelClassifierLog, LogLevel::Info)
 
 #define URLCLASSIFIER_SKIP_HOSTNAMES       "urlclassifier.skipHostnames"
+#define URLCLASSIFIER_TRACKING_ANNOTATION_SKIP_URLS "urlclassifier.trackingAnnotationSkipURLs"
 #define URLCLASSIFIER_ANNOTATION_TABLE     "urlclassifier.trackingAnnotationTable"
 #define URLCLASSIFIER_ANNOTATION_TABLE_TEST_ENTRIES "urlclassifier.trackingAnnotationTable.testEntries"
 #define URLCLASSIFIER_ANNOTATION_WHITELIST "urlclassifier.trackingAnnotationWhitelistTable"
 #define URLCLASSIFIER_ANNOTATION_WHITELIST_TEST_ENTRIES "urlclassifier.trackingAnnotationWhitelistTable.testEntries"
 #define URLCLASSIFIER_TRACKING_TABLE       "urlclassifier.trackingTable"
 #define URLCLASSIFIER_TRACKING_TABLE_TEST_ENTRIES "urlclassifier.trackingTable.testEntries"
 #define URLCLASSIFIER_TRACKING_WHITELIST   "urlclassifier.trackingWhitelistTable"
 #define URLCLASSIFIER_TRACKING_WHITELIST_TEST_ENTRIES "urlclassifier.trackingWhitelistTable.testEntries"
@@ -95,26 +96,28 @@ public:
   static CachedPrefs* GetInstance();
 
   void Init();
   bool IsAllowListExample() { return sAllowListExample;}
   bool IsLowerNetworkPriority() { return sLowerNetworkPriority;}
   bool IsAnnotateChannelEnabled() { return sAnnotateChannelEnabled;}
 
   nsCString GetSkipHostnames() const { return mSkipHostnames; }
+  nsCString GetSkipTrackingAnnotationURLs() const { return mSkipTrackingAnnotationURLs; }
   nsCString GetAnnotationBlackList() const { return mAnnotationBlacklist; }
   nsCString GetAnnotationBlackListExtraEntries() const { return mAnnotationBlacklistExtraEntries; }
   nsCString GetAnnotationWhiteList() const { return mAnnotationWhitelist; }
   nsCString GetAnnotationWhiteListExtraEntries() const { return mAnnotationWhitelistExtraEntries; }
   nsCString GetTrackingBlackList() const { return mTrackingBlacklist; }
   nsCString GetTrackingBlackListExtraEntries() { return mTrackingBlacklistExtraEntries; }
   nsCString GetTrackingWhiteList() const { return mTrackingWhitelist; }
   nsCString GetTrackingWhiteListExtraEntries() { return mTrackingWhitelistExtraEntries; }
 
   void SetSkipHostnames(const nsACString& aHostnames) { mSkipHostnames = aHostnames; }
+  void SetSkipTrackingAnnotationURLs(const nsACString& aURLs) { mSkipTrackingAnnotationURLs = aURLs; }
   void SetAnnotationBlackList(const nsACString& aList) { mAnnotationBlacklist = aList; }
   void SetAnnotationBlackListExtraEntries(const nsACString& aList) { mAnnotationBlacklistExtraEntries = aList; }
   void SetAnnotationWhiteList(const nsACString& aList) { mAnnotationWhitelist = aList; }
   void SetAnnotationWhiteListExtraEntries(const nsACString& aList) { mAnnotationWhitelistExtraEntries = aList; }
   void SetTrackingBlackList(const nsACString& aList) { mTrackingBlacklist = aList; }
   void SetTrackingBlackListExtraEntries(const nsACString& aList) { mTrackingBlacklistExtraEntries = aList; }
   void SetTrackingWhiteList(const nsACString& aList) { mTrackingWhitelist = aList; }
   void SetTrackingWhiteListExtraEntries(const nsACString& aList) { mTrackingWhitelistExtraEntries = aList; }
@@ -130,16 +133,17 @@ private:
   // list.
   static bool sAnnotateChannelEnabled;
   // Whether the priority of the channels annotated as being on the tracking
   // protection list should be lowered.
   static bool sLowerNetworkPriority;
   static bool sAllowListExample;
 
   nsCString mSkipHostnames;
+  nsCString mSkipTrackingAnnotationURLs;
   nsCString mAnnotationBlacklist;
   nsCString mAnnotationBlacklistExtraEntries;
   nsCString mAnnotationWhitelist;
   nsCString mAnnotationWhitelistExtraEntries;
   nsCString mTrackingBlacklist;
   nsCString mTrackingBlacklistExtraEntries;
   nsCString mTrackingWhitelist;
   nsCString mTrackingWhitelistExtraEntries;
@@ -157,16 +161,21 @@ StaticAutoPtr<CachedPrefs> CachedPrefs::
 void
 CachedPrefs::OnPrefsChange(const char* aPref, CachedPrefs* aPrefs)
 {
   if (!strcmp(aPref, URLCLASSIFIER_SKIP_HOSTNAMES)) {
     nsCString skipHostnames;
     Preferences::GetCString(URLCLASSIFIER_SKIP_HOSTNAMES, skipHostnames);
     ToLowerCase(skipHostnames);
     aPrefs->SetSkipHostnames(skipHostnames);
+  } else if (!strcmp(aPref, URLCLASSIFIER_TRACKING_ANNOTATION_SKIP_URLS)) {
+    nsCString skipTrackingAnnotationURLs;
+    Preferences::GetCString(URLCLASSIFIER_TRACKING_ANNOTATION_SKIP_URLS, skipTrackingAnnotationURLs);
+    ToLowerCase(skipTrackingAnnotationURLs);
+    aPrefs->SetSkipTrackingAnnotationURLs(skipTrackingAnnotationURLs);
   } else if (!strcmp(aPref, URLCLASSIFIER_ANNOTATION_TABLE)) {
     nsAutoCString annotationBlacklist;
     Preferences::GetCString(URLCLASSIFIER_ANNOTATION_TABLE,
                             annotationBlacklist);
     aPrefs->SetAnnotationBlackList(annotationBlacklist);
   } else if (!strcmp(aPref, URLCLASSIFIER_ANNOTATION_TABLE_TEST_ENTRIES)) {
     nsAutoCString annotationBlacklistExtraEntries;
     Preferences::GetCString(URLCLASSIFIER_ANNOTATION_TABLE_TEST_ENTRIES,
@@ -210,16 +219,18 @@ CachedPrefs::Init()
                                "privacy.trackingprotection.annotate_channels");
   Preferences::AddBoolVarCache(&sLowerNetworkPriority,
                                "privacy.trackingprotection.lower_network_priority");
   Preferences::AddBoolVarCache(&sAllowListExample,
                                "channelclassifier.allowlist_example");
   Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
                                        URLCLASSIFIER_SKIP_HOSTNAMES, this);
   Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
+                                       URLCLASSIFIER_TRACKING_ANNOTATION_SKIP_URLS, this);
+  Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
                                        URLCLASSIFIER_ANNOTATION_TABLE, this);
   Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
                                        URLCLASSIFIER_ANNOTATION_TABLE_TEST_ENTRIES, this);
   Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
                                        URLCLASSIFIER_ANNOTATION_WHITELIST, this);
   Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
                                        URLCLASSIFIER_ANNOTATION_WHITELIST_TEST_ENTRIES, this);
   Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
@@ -250,16 +261,17 @@ CachedPrefs::CachedPrefs()
   MOZ_COUNT_CTOR(CachedPrefs);
 }
 
 CachedPrefs::~CachedPrefs()
 {
   MOZ_COUNT_DTOR(CachedPrefs);
 
   Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, URLCLASSIFIER_SKIP_HOSTNAMES, this);
+  Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, URLCLASSIFIER_TRACKING_ANNOTATION_SKIP_URLS, this);
   Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, URLCLASSIFIER_ANNOTATION_TABLE, this);
   Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, URLCLASSIFIER_ANNOTATION_TABLE_TEST_ENTRIES, this);
   Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, URLCLASSIFIER_ANNOTATION_WHITELIST, this);
   Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, URLCLASSIFIER_ANNOTATION_WHITELIST_TEST_ENTRIES, this);
   Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, URLCLASSIFIER_TRACKING_WHITELIST, this);
   Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, URLCLASSIFIER_TRACKING_WHITELIST_TEST_ENTRIES, this);
   Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, URLCLASSIFIER_TRACKING_TABLE, this);
   Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange, URLCLASSIFIER_TRACKING_TABLE_TEST_ENTRIES, this);
@@ -758,16 +770,22 @@ nsChannelClassifier::IsHostnameWhitelist
            this, host.get()));
       return true;
     }
   }
 
   return false;
 }
 
+bool
+nsChannelClassifier::IsTrackingURLWhitelisted(nsIURI *aUri)
+{
+  return nsContentUtils::IsURIInList(aUri, CachedPrefs::GetInstance()->GetSkipTrackingAnnotationURLs());
+}
+
 // Note in the cache entry that this URL was classified, so that future
 // cached loads don't need to be checked.
 void
 nsChannelClassifier::MarkEntryClassified(nsresult status)
 {
     // Should only be called in the parent process.
     MOZ_ASSERT(XRE_IsParentProcess());
 
@@ -1202,35 +1220,44 @@ TrackingURICallback::OnBlacklistResult(n
 }
 
 nsresult
 TrackingURICallback::OnWhitelistResult(nsresult aErrorCode)
 {
   LOG_DEBUG(("TrackingURICallback[%p]::OnWhitelistResult aErrorCode=0x%" PRIx32,
              mChannelClassifier.get(), static_cast<uint32_t>(aErrorCode)));
 
+  // Change our error code if the URL has been whitelisted.
+  nsCOMPtr<nsIChannel> channel = mChannelClassifier->GetChannel();
+  nsCOMPtr<nsIURI> uri;
+  channel->GetURI(getter_AddRefs(uri));
+  if (!uri) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  bool prefBasedWhitelistUsed = false;
+  if (aErrorCode == NS_ERROR_TRACKING_ANNOTATION_URI &&
+      mChannelClassifier->IsTrackingURLWhitelisted(uri)) {
+    aErrorCode = NS_OK;
+    prefBasedWhitelistUsed = true;
+  }
+
   if (NS_SUCCEEDED(aErrorCode)) {
     if (LOG_ENABLED()) {
-      nsCOMPtr<nsIChannel> channel = mChannelClassifier->GetChannel();
-      nsCOMPtr<nsIURI> uri;
-      channel->GetURI(getter_AddRefs(uri));
       nsCString spec = uri->GetSpecOrDefault();
       spec.Truncate(std::min(spec.Length(), sMaxSpecLength));
       LOG(("TrackingURICallback[%p]::OnWhitelistResult uri %s found "
-           "in whitelist so we won't block it", mChannelClassifier.get(),
-           spec.get()));
+           "in %s whitelist so we won't block it", mChannelClassifier.get(),
+           spec.get(), prefBasedWhitelistUsed ? "pref-based" :
+           "tracking protection"));
     }
     mChannelCallback();
     return NS_OK;
   }
 
   if (LOG_ENABLED()) {
-    nsCOMPtr<nsIChannel> channel = mChannelClassifier->GetChannel();
-    nsCOMPtr<nsIURI> uri;
-    channel->GetURI(getter_AddRefs(uri));
     nsCString spec = uri->GetSpecOrDefault();
     spec.Truncate(std::min(spec.Length(), sMaxSpecLength));
     LOG(("TrackingURICallback[%p]::OnWhitelistResult "
          "channel[%p] uri=%s, should not be whitelisted",
          mChannelClassifier.get(), channel.get(), spec.get()));
   }
 
   OnTrackerFound(aErrorCode);
--- a/netwerk/base/nsChannelClassifier.h
+++ b/netwerk/base/nsChannelClassifier.h
@@ -56,16 +56,19 @@ public:
     // and the caller should wait for the result.
     nsresult CheckIsTrackerWithLocalTable(std::function<void()>&& aCallback);
 
     // Helper function to create a whitelist URL.
     nsresult CreateWhiteListURI(nsIURI** aURI) const;
 
     already_AddRefed<nsIChannel> GetChannel();
 
+    // Helper function to check a URI against the tracking skip URL whitelist
+    bool IsTrackingURLWhitelisted(nsIURI *aUri);
+
 private:
     // True if the channel is on the allow list.
     bool mIsAllowListed;
     // True if the channel has been suspended.
     bool mSuspendedChannel;
     nsCOMPtr<nsIChannel> mChannel;
     Maybe<bool> mTrackingProtectionEnabled;
     Maybe<bool> mTrackingAnnotationEnabled;
--- a/testing/awsy/awsy/test_base_memory_usage.py
+++ b/testing/awsy/awsy/test_base_memory_usage.py
@@ -46,20 +46,23 @@ class TestMemoryUsage(AwsyTestCase):
 
     def setUp(self):
         AwsyTestCase.setUp(self)
         self.logger.info("setting up!")
 
         # Override AwsyTestCase value, this is always going to be 1 iteration.
         self._iterations = 1
 
-        self._urls = ['about:blank'] * 4
+        # We don't want to measure the preallocated process, so we load enough
+        # tabs so that it is no longer launched.
+        process_count = self.marionette.get_pref('dom.ipc.processCount')
+        self._urls = ['about:blank'] * process_count
 
-        self.logger.info("areweslimyet run by %d pages, %d iterations, %d perTabPause, %d settleWaitTime"
-                         % (self._pages_to_load, self._iterations, self._perTabPause, self._settleWaitTime))
+        self.logger.info("areweslimyet run by %d pages, %d iterations, %d perTabPause, %d settleWaitTime, %d content processes"
+                         % (self._pages_to_load, self._iterations, self._perTabPause, self._settleWaitTime, process_count))
         self.logger.info("done setting up!")
 
     def tearDown(self):
         self.logger.info("tearing down!")
         AwsyTestCase.tearDown(self)
         self.logger.info("done tearing down!")
 
     def test_open_tabs(self):
--- a/toolkit/components/antitracking/test/browser/browser.ini
+++ b/toolkit/components/antitracking/test/browser/browser.ini
@@ -39,16 +39,17 @@ skip-if = (os == "win" && os_version == 
 [browser_imageCache4-2.js]
 [browser_imageCache8.js]
 [browser_onBeforeRequestNotificationForTrackingResources.js]
 [browser_onModifyRequestNotificationForTrackingResources.js]
 [browser_permissionInNormalWindows.js]
 skip-if = serviceworker_e10s
 [browser_permissionInPrivateWindows.js]
 skip-if = serviceworker_e10s
+[browser_siteSpecificWorkArounds.js]
 [browser_subResources.js]
 skip-if = serviceworker_e10s
 support-files = subResources.sjs
 [browser_script.js]
 support-files = tracker.js
 [browser_userInteraction.js]
 [browser_storageAccessPrivateWindow.js]
 skip-if = serviceworker_e10s
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js
@@ -0,0 +1,65 @@
+AntiTracking.runTest("localStorage with a tracker that is whitelisted via a pref",
+  async _ => {
+    localStorage.foo = 42;
+    ok(true, "LocalStorage is allowed");
+  },
+  async _ => {
+    localStorage.foo = 42;
+    ok(true, "LocalStorage is allowed");
+  },
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  },
+  [["urlclassifier.trackingAnnotationSkipURLs", "tracking.example.org"]],
+  false, // run the window.open() test
+  false, // run the user interaction test
+  0, // don't expect blocking notifications
+  false); // run in a normal window
+
+AntiTracking.runTest("localStorage with a tracker that is whitelisted via a fancy pref",
+  async _ => {
+    localStorage.foo = 42;
+    ok(true, "LocalStorage is allowed");
+  },
+  async _ => {
+    localStorage.foo = 42;
+    ok(true, "LocalStorage is allowed");
+  },
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  },
+  [["urlclassifier.trackingAnnotationSkipURLs", "foobar.example,*.example.org,baz.example"]],
+  false, // run the window.open() test
+  false, // run the user interaction test
+  0, // don't expect blocking notifications
+  false); // run in a normal window
+
+AntiTracking.runTest("localStorage with a tracker that is whitelisted via a misconfigured pref",
+  async _ => {
+    try {
+      localStorage.foo = 42;
+      ok(false, "LocalStorage cannot be used!");
+    } catch (e) {
+      ok(true, "LocalStorage cannot be used!");
+      is(e.name, "SecurityError", "We want a security error message.");
+    }
+  },
+  async _ => {
+    localStorage.foo = 42;
+    ok(true, "LocalStorage is allowed");
+  },
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  },
+  [["urlclassifier.trackingAnnotationSkipURLs", "*.tracking.example.org"]],
+  false, // run the window.open() test
+  false, // run the user interaction test
+  Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER, // expect blocking notifications
+  false); // run in a normal window
+
--- a/toolkit/components/extensions/WebExtensionPolicy.cpp
+++ b/toolkit/components/extensions/WebExtensionPolicy.cpp
@@ -612,17 +612,18 @@ MozDocumentMatcher::Matches(const DocInf
   if (!mMatchAboutBlank && aDoc.URL().InheritsPrincipal()) {
     return false;
   }
 
   // Top-level about:blank is a special case. We treat it as a match if
   // matchAboutBlank is true and it has the null principal. In all other
   // cases, we test the URL of the principal that it inherits.
   if (mMatchAboutBlank && aDoc.IsTopLevel() &&
-      aDoc.URL().Spec().EqualsLiteral("about:blank") &&
+      (aDoc.URL().Spec().EqualsLiteral("about:blank") ||
+       aDoc.URL().Scheme() == nsGkAtoms::data) &&
       aDoc.Principal() && aDoc.Principal()->GetIsNullPrincipal()) {
     return true;
   }
 
   if (mRestricted && mExtension->IsRestrictedDoc(aDoc)) {
     return false;
   }
 
new file mode 100644
--- /dev/null
+++ b/tools/quitter/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  globals: {
+    cloneInto: true,
+  }
+};
new file mode 100644
--- /dev/null
+++ b/tools/quitter/background.js
@@ -0,0 +1,9 @@
+"use strict";
+
+/* eslint-env webextensions */
+
+browser.runtime.onMessage.addListener(msg => {
+  if (msg === "quit") {
+    browser.quitter.quit();
+  }
+});
deleted file mode 100644
--- a/tools/quitter/bootstrap.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/* 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/. */
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-const CHILD_SCRIPT = "chrome://quitter/content/contentscript.js";
-
-const quitterObserver = {
-  init() {
-    Services.mm.addMessageListener("Quitter.Quit", this);
-    Services.mm.loadFrameScript(CHILD_SCRIPT, true);
-  },
-
-  uninit() {
-    Services.mm.removeMessageListener("Quitter.Quit", this);
-    Services.mm.removeDelayedFrameScript(CHILD_SCRIPT, true);
-  },
-
-  /**
-   * messageManager callback function
-   * This will get requests from our API in the window and process them in chrome for it
-   **/
-  receiveMessage(aMessage) {
-    switch (aMessage.name) {
-      case "Quitter.Quit":
-        Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
-        break;
-    }
-  },
-};
-
-function startup(data, reason) {
-  quitterObserver.init();
-}
-
-function shutdown(data, reason) {
-  quitterObserver.uninit();
-}
-
-function install(data, reason) {}
-function uninstall(data, reason) {}
deleted file mode 100644
--- a/tools/quitter/chrome.manifest
+++ /dev/null
@@ -1,1 +0,0 @@
-content quitter chrome/quitter/content/
--- a/tools/quitter/contentscript.js
+++ b/tools/quitter/contentscript.js
@@ -1,35 +1,13 @@
 /* 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/. */
 
-/* eslint-env mozilla/frame-script */
+"use strict";
 
-function Quitter() {
-}
+/* eslint-env webextensions */
 
-Quitter.prototype = {
-  toString() { return "[Quitter]"; },
-  quit() { sendSyncMessage("Quitter.Quit", {}); },
+const Quitter = {
+  quit() { browser.runtime.sendMessage("quit"); },
 };
 
-// This is a frame script, so it may be running in a content process.
-// In any event, it is targeted at a specific "tab", so we listen for
-// the DOMWindowCreated event to be notified about content windows
-// being created in this context.
-
-function QuitterManager() {
-  addEventListener("DOMWindowCreated", this, false);
-}
-
-QuitterManager.prototype = {
-  handleEvent: function handleEvent(aEvent) {
-    var quitter = new Quitter(window);
-    var window = aEvent.target.defaultView;
-    window.wrappedJSObject.Quitter = Cu.cloneInto({
-      toString: quitter.toString.bind(quitter),
-      quit: quitter.quit.bind(quitter),
-    }, window, {cloneFunctions: true});
-  },
-};
-
-var quittermanager = new QuitterManager();
+window.wrappedJSObject.Quitter = cloneInto(Quitter, window, {cloneFunctions: true});
deleted file mode 100644
--- a/tools/quitter/install.rdf
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>quitter@mozilla.org</em:id>
-    <em:version>2018.03.19</em:version>
-    <em:type>2</em:type>
-    <em:bootstrap>true</em:bootstrap>
-
-    <!-- Target Application this extension can install into,
-         with minimum and maximum supported versions. -->
-    <em:targetApplication>
-      <Description>
-        <!-- Firefox -->
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>45</em:minVersion>
-        <em:maxVersion>*</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-    <em:targetApplication>
-      <Description>
-        <!-- Fennec -->
-        <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
-        <em:minVersion>45</em:minVersion>
-        <em:maxVersion>*</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-    <!-- Front End MetaData -->
-    <em:name>Quitter</em:name>
-    <em:description>Adds a quit method that content pages can use to quit the application.</em:description>
-    <em:creator>Mozilla</em:creator>
-  </Description>
-</RDF>
deleted file mode 100644
--- a/tools/quitter/jar.mn
+++ /dev/null
@@ -1,3 +0,0 @@
-quitter.jar:
-% content quitter %content/
-  content/contentscript.js (contentscript.js)
new file mode 100644
--- /dev/null
+++ b/tools/quitter/manifest.json
@@ -0,0 +1,34 @@
+{
+  "manifest_version": 2,
+
+  "applications": {
+    "gecko": {"id": "quitter@mozilla.org"}
+  },
+
+  "name": "Quitter",
+  "description": "Quit",
+  "version": "2018.04.03",
+  "author": "Mozilla",
+
+  "background": {
+    "scripts": ["background.js"]
+  },
+
+  "content_scripts": [{
+    "js": ["contentscript.js"],
+    "run_at": "document_start",
+    "match_about_blank": true,
+    "matches": ["<all_urls>"]
+  }],
+
+  "experiment_apis": {
+    "quitter": {
+      "schema": "schema.json",
+      "parent": {
+        "scopes": ["addon_parent"],
+        "script": "parent.js",
+        "paths": [["quitter", "quit"]]
+      }
+    }
+  }
+}
--- a/tools/quitter/moz.build
+++ b/tools/quitter/moz.build
@@ -1,17 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # 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/.
 
 XPI_NAME = 'quitter'
 
-JAR_MANIFESTS += ['jar.mn']
-
 USE_EXTENSION_MANIFEST = True
 
 FINAL_TARGET_FILES += [
-    'bootstrap.js',
-    'chrome.manifest',
-    'install.rdf',
+    'background.js',
+    'contentscript.js',
+    'manifest.json',
+    'parent.js',
+    'schema.json',
 ]
new file mode 100644
--- /dev/null
+++ b/tools/quitter/parent.js
@@ -0,0 +1,17 @@
+"use strict";
+
+/* globals ExtensionAPI */
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+this.quitter = class extends ExtensionAPI {
+  getAPI(context) {
+    return {
+      quitter: {
+        quit() {
+          Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
+        },
+      },
+    };
+  }
+};
index f4e7d81117c12ff6c00d54cf191b398765e68ab5..acf9c87e90803540c2ce99e3ca5d4e1d2d8070bc
GIT binary patch
literal 6164
zc$}SBWl)^i(w<>(cMneR!7V_5;DfunJHeg7-C-eU7#u<f9^47e;t(LXBxuk80WP_>
zwz4;0?vMRWRaaNPRqu29w4T!{@*sG8004jt_*U<(DDbLx)DZ&!*ue$>etcDw(hz5p
zQ<7$PboRA%a4-eCx|z;qSZky%5=KlTKauo377q(!r{+PxS1lq=DtS#4flV!js|OH=
zgx~|07#WdeQ6SY^_`$(ZVvHMc$ZBa;Buo%>Y<TcqE{XNg8^53xkNLanyt_Q--1hm!
z-D|$C1y11ZrziFlt)o~wVHTX8!U)7dzSJfhS!8QrZZ^nb)M|iZN<tUHP*H$MS3D3A
zJsBY!N{tOxoly2&E~6CzJRCpgphQA`y()1Ax9n0ziw;JASuZmJP+JO-(IJ+>puhgM
zMxaoB-=PuYPZbRjw+9X$GFEeJG?k%@ORtxS&gM!l@G&l=3NzBhM23h=ibhFN!Em6%
zmxyKW9J-p2fr--VD=Xz`0Q~DE1{IAUta=)%huh}&-DW})Y7%f!VRxOn$QoQ!4C9G&
zGKHJ~#6m~ttY{5AMn|exUq0S-=^AcCX4dO|K(b>u(`aJh6l1VAuGBhlG;ti^GV_Od
zhxMn%6JdWK(sk0mKWl==3WvvK0u~*viQ0p<@h%IK;Xu*%x1I7p6S1@oc5o&L-ck%+
zTz_8}8xbCz6oM&=q#h?gD^3wl#DE?pua&P0yB4s%hkPiKelKvXqD8PxsZh<wPE6nK
z>Fk_|OfSj$slH?glMavE>A^+FQ`2yIyse>!$b**C;I&pG{j}dezvWy}3ElWxcMc2f
z``tG;@(Bi|h2i`sKq7;Wf85Ti4VDq39MpJS=<ePfIUGpSybu@|#q?aI%do9W8>pqS
z{A@^eBfWt}4Gh@iyZhwKRy&{9*<f?{eId6p^g<?mZue>gOSDKes3v(_Q0u^p(V#It
z+K`P4^f>{2W^qyKmV=VNe>;OGKwwD@ruF_y1xeo`X27zFI2JRjvg80U87?rtJFZfK
zk80op>Wl*b8jy4DFX^1%QhvMj?M37_Ol{R%&HKEaGy3FdS;mgdc8QPo#Pj^fy&Mfp
zF%v#IF;`II;3{}_nWaAUqs__1dE5~#Av(IuEZ$@~oNyzc>#Evyo<stua|3@Qol9RW
z(pi7dcTJ+gSf-XTng7Ur>}!LxY0Muba%6AHTDi{nbmMI|nr$O<^EAUNGEeKLI+AYf
z1@9ovYQ-y_5gMpMp1uvbhB~eFyE<l7*<ZWExKpL!yr{OhmEGH`Xo+hj<lP+7i28X`
z2<+SILVNBG2wNF4<wr<^o;2y8<_5A`SuhcGZ6YlJxnS!@ZK0#F=egY34c}4R6eY2^
zypoZFL&Vn%e7VFPfY=KyGPNNlCr{B4!(F5u4e!~i&{F|@Fv-5Sc?-U4p10M@gipkF
zYCnao$yfpB9Ye84Q3M8Dy4Gso0|54GC6!);fD_5XI)ENg{Ct1jY`6KO?=wcR3}lm&
zkKQ71a~Rdt@%&w|m9LT_xpk5>IF*qe57~&{N9q789q+siG&S;-VyjO;&N#m8f~n`v
zdL8f65EQ$7u_8xG7ABBf%bkY1#Ss(uv;0I@g!k{h-8Fn-ZY=6k@{ue(^d;vTm$F9Y
z?7s!$ST4P9<GN996*zY)>!7knJ))~2HD7pp`IMomEp3&8RMz+%XFw6#`xav_nk9{3
z<I~AZ(`me#uksP8dun8o_G8cU8@yXfuTS2bUWk-b7;xDaToqeL$@&~+b4m|$`ta}O
z8_rMkmJezsboiWB*rKF<Vtn;%d%5{fDan;QC+rMJd*Dhf+D)G(>ZElx1+jVwi8c-3
zxrq+XtAfZV`<?)W%E|2&$K|{z)3EB@vE#ioHT{y6o3u-H>VT%PdW$5g&euF{wHSCj
z0U%SUgU+mzA)5v51TxaZ)9JPEuK_BbsL7A)R0x&rbe47;tT2_3+toTELUq%c25qzJ
zTq~c(>esIDb$`XVYPI~9`EtJ<a{L}|UK{z)cC4(8z4gY|(5sVY978G4h1siWcJ836
zaQLmBgCDl&-d&-=YTYK*Li#S>G$yi4x6TYgX@yqggpz<7TBSN0U7=pV<N#x6+MDWe
zf6ff38kB3OW$`!{ZR~rVtX6~J&Zswzc8a-f;=U{`hErTW1tJ;ZR|9FvH6L!?cZ?&k
zVUuo=^2K5)NwV_cN}A{L^ts$xbDdlA--qd9VU5T=(eT9NuvKKZ6=2960mb@*rsp82
zc6EVzN4Xf02F1JR_}GdrPvH&%FCvE21qU#J&fY=7j<+6gmhSO{0-1$h;RO}@JDald
z)C4qc_eLikhg|Z}^hD?u^g2ENW+$&*$z^nU##g@H5K6{5@d9&6GPZ3QI`6qXl)Wbu
zG~Bvkq%|pk*7{P?5(pz*@6yyiPV#waQ`{w$PO%s;<;B{wSG#?YgHfQ*RK&#Z$QA3E
z0nHTvq_7YPS`+e3d)<9CIKYzNHmd8VRxUS;7f3HzGT2niX?Kh3#+?9sX;AcJd-3kF
zdAGs)p5+eCX!d;b%9z!%iIl|Dz`?4gbE^arJA<K)+U||kq2s?r+MunrZZmgQ#vbu>
zOn2+Hr$$KS4^NbhFH^V}yB{b+jCns`Msm)+B-{x%c85;L#8YBD*MYYd(1w_y%@*al
zvbN>d8wwmI(uVCt?*#}u=(!|*q;c=(96z3!vI_{#hH3B&_y};5Q8&E6AD+)GZ~bP+
zN1)zN7tPim?41}`0P5(6kVXa7RuZo86dWG3=`kkVzoR~j1qZFva(14w3&2mGPWBh?
zU3(|AS9zH=w?qmX&>1ZlS!vVF1#&8Cp{*d;j@xP9^;vRli45D4j(%hm!I`-hqiMC_
zy>W6LwJeoaRDAO}#5;0uV2bwTaM?l`v8I;#zS;g!WcAw!bo{>UEIf)O8+v$L9YKs{
z7CUy*K!rlK7oSGXy<|I+YwMnSYVf<Ko|->YeMz*EA@b~RNWd%$GDiaW<Cs9r0UeIS
z$h`u9JwoyOQCtpk?4`Mir7WzqU^m;YZgTP*mA!eZ6ar{;_kAA6`FCe@m^JV`jxo6+
zf~m9%_7XC9Gz@dMZ1xY?8Ow5bmP>Li>g0<^jBeB0JkggGYOak5HRTA)jIlO(eN4et
z733(w24@tzb{u+2p90)JJl`^j43=0dfx<~RRUR|y2YCh$9`5ubO?{6J+9(i~xLhT>
zN6eJwKAdxaiBF9W@-&lWuaSLWsEr-wMNoWkXIuikF8)wyf8iJJ+Zun0o9tjiM1Jf0
z7F;)v%A{q-E$30u{-HTm1-BY^mo&d`_Ig9CGV;SHSyleA)YOLXT@TsDw$F{5dx@R=
z7Q3fm)ZTl0M<~J9KNltN>vBEu4o6#@JHL%9C&~EOGzTY}mT(8YtXU#XH%`7Uw9>+g
zQg@JjVc1u<Wo<f!$Z21yUc8GXZy;wQSkjadM#sZ6rrxk$obK$VEn3PFwVFgSMJY_Z
z&AXq@qMN!^!l4CM<XrNx1AW|i#Y^@RXfc>v(Ei!ln4N~dbb}20L8X3{#?A6xrYbS(
z@U$5T(UgAm2YppKZNpxpAr(0-kBq}v+3B@T2=Ml(k|}_;O<3>(K|!L4Umc8+?nxIr
zvCq)h1bR)8Eetj^IzIjLypdsX$nkhF?oZjnZ&>!r%->rSGq~cMM$xoNkPoex5iLri
z&ZQ<6ahwEEDQqK`*we%<kL(D%Dy~eod!A8rnCG(e)r)pq&^A{<R>_Ujs+^ZzwNtw*
z2RrC2_CO-Z#Y=d(`T6*Xifc}*mE!TWza)0e@^Vll;}&e3XG7Rbk@j!wz#Y#lJkg)%
z-RU__Ihey+ePUe1KQ!ffZk=iESDtAQ9b3Z9q>)Z5evSuM%KYlU*!wAkung6$Q9=QU
z3J40EUCbH<+}u4-e)s2pa?ht^)eOK5ItYPv=7WK+L}|!vx;x`6p{JCp#J>9ty1@3!
z9f`g6<a%V8V)GEI!YcI7tBRE-n8YBslwtZDBdqtsTl+pDRz+pmQ_KC$RQ_XZO`2m5
zA74B8yT1hL(>&#0du7v$<vBI>g}$#zH*$sdjA2Z9J|4vYja3ILFDdH<#Rp?<6+f4V
z&lgJlXPWA3xidu<e}uNd7c_7lW$2A)SGs5;ge~C1V|YEaKbxLPH!G!>WpS(ush{Ys
zk;>42gDRw^TFlZ(fnFt@mZ6^$!q?odJb!F40gY4#+sNPxXoH1HjBEU)jjy()vtT!Q
zA<NBeLe-QRbSNs!V0_SeVZw{NwETggTGzY7ODz;CPXEPQ+1FP2*=mrJp0w3+i%8AN
zBjV(DdmgHD{<CMHDC+fwm5g4!cYCc4c_ug3jr_Oa?)kTq;ppR6LkG#dEHo9}g1r?9
zrY?H9hwT(7z{!Jvb-!-kZDAU+vl!aG87!`^NHC=nIu`G(NN{2Z`9?-5YRufO20nya
zgXV>>*3HOAbkRzMHlwR?(q{8#Uv^B4&h2)L`nOAGE#!;uqr0q!U*LY!7K!X-R;^Ks
zCbB}lZMnknRy@eyx;7BF{B(h~cE4diVD3R?JGyR3FxSgcB>2ojxX`e7Nhr_;FVxlF
zN|B4LEpU*igZnD8Zr{!@RAW%60R9!yUAO&7isVq`=pcUui8W-B7V&#sLik0z<yyI?
zdnSQ9hI;Cm|CPKkXHsAEQTLSU$c@9Fq5zd2&oj`E=UHD=sDeUy`JCnBv#jBf&;WD*
zH**_HM^mt!o3j%tfC}(q0RhASC=k|CHZl$TmB;R=sgxm_As`gD^)1hAg!FA`(6b;E
z_e_nZq|DIJKZ!EXvo&&+vkElgQ~{H+!cQa4qfR5t!V$8UvjSp!>uE>m3AK0VLG<!5
z5cT9RG~8Hd1h5KGp8zer3}Kqeh)tW7=x-V;#D^-7eDX{e1p)vIkA(fGvAMI8yQP!6
zo4Kp4i~FPEe^(o@AUIm}LtUq-fAha&sU&gd9uD;I<2wRn5un-UdRm)OIa!6*oHZjn
zhe;)fTn%y=0`6oRofB*}Lb{Ejoz|_HYuhHYjn5GzyiW*B*CjyZ{ThKurtgp_krfJ}
zPg<#UZz=WXNwYQP<}5NzEHRp8)yWBtW~zEzkuWxwjyqqCq2?yI<f$$WcK;Dw{=)j2
z3L4)&Iz9*c`UNk}^Mu}>r5lH5vuk-g2G;D&LU|c(L%5;Es1U=doc!lL>G-a$mV#JK
zV}kSGlSx%MV^$WuV>4xWKjT}qd7Ki$$l_Fq;ye-{@x)MVPMlv3*C19SZ2z*5Qwh0`
zNnp!adq*UHQ<&h3`e`s@=rIifL5lfOQp{EbZ-3bp&#)geB^Ir<Ze88m)7&p1PWlxT
zP0GFVvz-sCo`v>~F(ae+r3Fp0<1RsS=;XM$AI=VlemcY;I#gj%a-_uf(coE+gz@MQ
zGgEVWYgcCvCyQUa0acZ^D*xe)UpQ}^A!;ZGcG&6_zC@mejWD!`!3L|qg9VMZU{MpT
zzm@;a?<UhZ;$<w?Os&DFhomEDE?ZK_#h~VE7uIH33Jhi&9g)MgeQ<`WXm!byr`wRf
zaJA`;%Vs`4`v$KEM;UG_#u*)W)DhKuvl;v_tN$@kb0yuWwEYC(=RxKZLKST2;2k+0
zqsj^HKL>F%b+WaxbaVed*#N)_jHvz*PQP%)Bt@lO4(#wNL8<Kqg7h9Z>ah0!v^h-+
z#V4unUpj2Y;lPGhYD8@J6$5wt&UVHOvQ`qtOfE9?RKAzJv$0+rIqJ$*=<+M1H(>^s
z`^q(FJW0zjxVLOkX1{#G3qw7wyPX~o+wbX`qca@p>v>aqkZKXPYgSE2QZGZQO&sip
z=(!*&+;m&&`5=o-lK{3j6;CC`5D3nRSc4rNlMIPOKG$j!VSm%gkU9>lWn}(37g#c`
zDAvZ~wn422%q%=t8fea>g8x{MgTKwVwnY<@3KXJeiYT*tV>7@)Ol!t5LMFA(n0<4>
zytjdFq&>g-Hh?9~QifMTNy3=PwuP$G42ybE1EaYAnhkK?UT22q(Kff$P{-Eo4@LFs
zsu3V}ekIUvZrC(5w{v5V-EfWc)BM;mp$a@sI^X&pEzI}m0+dJdyO_E@=Jl^Jw3r00
z_+fGvCiWlvFWE4$8}%`xBaQ=N-c3KP{X~F8gWAfp0U9N>Dz>*yiJ|iIs#H={mg}Gk
zy0=Ki(HSHiDuH~);Q=4me|Xo1>f6{oF;5UzuNF%iPUvO!B%|>VPA(6}HOj&T<{?qG
z<PwFc>zoXpqsm}#s-yWlJn8C_paqX&WnwUf{_VZXw^ny`00so&hSyf~YGVk1OrJuD
z3Ke%e1+u3R83Wcf!-}6quS`pJyer<d`{c0{yun<=&R3qX+~IXwb0c>nQ>o3g3{?5A
z!5PBg390_0DbnD69i06YmX22GNs5Uf9QaaK-%&56rQmZj-<PGF6vJ_S*?T*qS5a@n
z6mowvIU0wy64#p1mYw4_f$!SV8}0zEVYC@XvMViS8b;?5R>6zHl-#0UZM+OOQ+34(
ziQ5VpP9p$$*uAnlfF`&KWr;on`znDM-Y)GtEi%I3JLExn8XY0Ao#$UPxBi^7EjN|i
z19V~j88ntIH0d#Xagvzm3g*l}z&&SNEmxsHU>Nj@9%VQ0g0r4q5IZplmPErH_p>qE
zx+r><>=6Fs^BXI2=|t3n`HA?vv!Jsk^KNtzK9xU}{fJw0ksTCHN7-8_Sa;>|eF`)=
zO`9g<YGk<{o>fqMr^V+79Xm9!fiyJcBEod>#T*9rZO>t=36s7<G-qha4Ul<tuuO*c
zWpmW}MmJa`hQ#05r^Onxu+Hh$zviG=@FS#?KajJUo>VGcg3w=}$IU*&-A>ddLzkA0
z|Azm+ow4ZOF<|#7`8*Qd@1~e;R%qi26Y{J~Y>-(Dc{b+p(TO@Wjv4N?QxDErk0AwR
zHt^(y;^>G1i0chLc|b@{>YX}wfP95QT+Yafe+~xv*%L>{<`5Bb49@fnvg?+et#k%0
zU+Y`nn`aS1w;5rG)>aWhQa&GDUjiTdArKB9^uI@}e=R5w@IMBwKM+Jg|N9p3pG5^N
z3eJDT{fBpgzk~m-VgCeI#RUAn+V|gg_+1D3xx)q);Gg%Szr+8oFZ_hBe6;vK?h=28
z|6Set3D1lR_-9q{*U0#_hWv~SLA>8rlwT2k4g8-71Ngs90~L7$M8Lm)M+W3RZfi>L
GL;4pEgu*!h
new file mode 100644
--- /dev/null
+++ b/tools/quitter/schema.json
@@ -0,0 +1,13 @@
+[
+  {
+    "namespace": "quitter",
+    "functions": [
+      {
+        "name": "quit",
+        "type": "function",
+        "async": true,
+        "parameters": []
+      }
+    ]
+  }
+]