fune/toolkit/components/extensions/MatchPattern.cpp
Nika Layzell c15823d075 Bug 1772006 - Part 5: Simplify and move the string searching APIs from ns[T]StringObsolete, r=xpcom-reviewers,necko-reviewers,eeejay,dragana,barret
The biggest set of APIs from ns[T]StringObsolete which are still heavily used
are the string searching APIs. It appears the intention was for these to be
replaced by the `FindInReadable` APIs, however that doesn't appear to have
happened.

In addition, the APIs have some quirks around their handling of mixed character
widths. These APIs generally supported both narrow strings and the native
string type, probably because char16_t string literals weren't available until
c++11. Finally they also used easy-to-confuse unlabeled boolean and integer
optional arguments to control behaviour.

These patches do the following major changes to the searching APIs:

1. The ASCII case-insensitive search method was split out as
   LowerCaseFindASCII, rather than using a boolean. This should be less
   error-prone and more explicit, and allows the method to continue to use
   narrow string literals for all string types (as only ASCII is supported).
2. The other [R]Find methods were restricted to only support arguments with
   matching character types. I considered adding a FindASCII method which would
   use narrow string literals for both wide and narrow strings but it would've
   been the same amount of work as changing all of the literals to unicode
   literals.
   This ends up being the bulk of the changes in the patch.
3. All find methods were re-implemented using std::basic_string_view's find
   algorithm or stl algorithms to reduce code complexity, and avoid the need to
   carry around the logic from nsStringObsolete.cpp.
4. The implementations were moved to nsTStringRepr.cpp.
5. An overload of Find was added to try to catch callers which previously
   called `Find(..., false)` or `Find(..., true)` to set case-sensitivity, due
   to booleans normally implicitly coercing to `index_type`. This should
   probably be removed at some point, but may be useful during the transition.

Differential Revision: https://phabricator.services.mozilla.com/D148300
2022-07-30 00:12:48 +00:00

774 lines
21 KiB
C++

/* -*- 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 "mozilla/extensions/MatchPattern.h"
#include "mozilla/extensions/MatchGlob.h"
#include "js/RegExp.h" // JS::NewUCRegExpObject, JS::ExecuteRegExpNoStatics
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/Unused.h"
#include "nsGkAtoms.h"
#include "nsIProtocolHandler.h"
#include "nsIURL.h"
#include "nsNetUtil.h"
namespace mozilla {
namespace extensions {
using namespace mozilla::dom;
/*****************************************************************************
* AtomSet
*****************************************************************************/
AtomSet::AtomSet(const nsTArray<nsString>& aElems) {
mElems.SetCapacity(aElems.Length());
for (const auto& elem : aElems) {
mElems.AppendElement(NS_AtomizeMainThread(elem));
}
SortAndUniquify();
}
AtomSet::AtomSet(const char** aElems) {
for (const char** elemp = aElems; *elemp; elemp++) {
mElems.AppendElement(NS_Atomize(*elemp));
}
SortAndUniquify();
}
AtomSet::AtomSet(std::initializer_list<nsAtom*> aIL) {
mElems.SetCapacity(aIL.size());
for (const auto& elem : aIL) {
mElems.AppendElement(elem);
}
SortAndUniquify();
}
void AtomSet::SortAndUniquify() {
mElems.Sort();
nsAtom* prev = nullptr;
mElems.RemoveElementsBy([&prev](const RefPtr<nsAtom>& aAtom) {
bool remove = aAtom == prev;
prev = aAtom;
return remove;
});
mElems.Compact();
}
bool AtomSet::Intersects(const AtomSet& aOther) const {
for (const auto& atom : *this) {
if (aOther.Contains(atom)) {
return true;
}
}
for (const auto& atom : aOther) {
if (Contains(atom)) {
return true;
}
}
return false;
}
void AtomSet::Add(nsAtom* aAtom) {
auto index = mElems.IndexOfFirstElementGt(aAtom);
if (index == 0 || mElems[index - 1] != aAtom) {
mElems.InsertElementAt(index, aAtom);
}
}
void AtomSet::Remove(nsAtom* aAtom) {
auto index = mElems.BinaryIndexOf(aAtom);
if (index != ArrayType::NoIndex) {
mElems.RemoveElementAt(index);
}
}
/*****************************************************************************
* URLInfo
*****************************************************************************/
nsAtom* URLInfo::Scheme() const {
if (!mScheme) {
nsCString scheme;
if (NS_SUCCEEDED(mURI->GetScheme(scheme))) {
mScheme = NS_AtomizeMainThread(NS_ConvertASCIItoUTF16(scheme));
}
}
return mScheme;
}
const nsCString& URLInfo::Host() const {
if (mHost.IsVoid()) {
Unused << mURI->GetHost(mHost);
}
return mHost;
}
const nsAtom* URLInfo::HostAtom() const {
if (!mHostAtom) {
mHostAtom = NS_Atomize(Host());
}
return mHostAtom;
}
const nsString& URLInfo::FilePath() const {
if (mFilePath.IsEmpty()) {
nsCString path;
nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
if (url && NS_SUCCEEDED(url->GetFilePath(path))) {
AppendUTF8toUTF16(path, mFilePath);
} else {
mFilePath = Path();
}
}
return mFilePath;
}
const nsString& URLInfo::Path() const {
if (mPath.IsEmpty()) {
nsCString path;
if (NS_SUCCEEDED(URINoRef()->GetPathQueryRef(path))) {
AppendUTF8toUTF16(path, mPath);
}
}
return mPath;
}
const nsCString& URLInfo::CSpec() const {
if (mCSpec.IsEmpty()) {
Unused << URINoRef()->GetSpec(mCSpec);
}
return mCSpec;
}
const nsString& URLInfo::Spec() const {
if (mSpec.IsEmpty()) {
AppendUTF8toUTF16(CSpec(), mSpec);
}
return mSpec;
}
nsIURI* URLInfo::URINoRef() const {
if (!mURINoRef) {
if (NS_FAILED(NS_GetURIWithoutRef(mURI, getter_AddRefs(mURINoRef)))) {
mURINoRef = mURI;
}
}
return mURINoRef;
}
bool URLInfo::InheritsPrincipal() const {
if (!mInheritsPrincipal.isSome()) {
// For our purposes, about:blank and about:srcdoc are treated as URIs that
// inherit principals.
bool inherits = Spec().EqualsLiteral("about:blank") ||
Spec().EqualsLiteral("about:srcdoc");
if (!inherits) {
nsresult rv = NS_URIChainHasFlags(
mURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &inherits);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
mInheritsPrincipal.emplace(inherits);
}
return mInheritsPrincipal.ref();
}
/*****************************************************************************
* CookieInfo
*****************************************************************************/
bool CookieInfo::IsDomain() const {
if (mIsDomain.isNothing()) {
mIsDomain.emplace(false);
MOZ_ALWAYS_SUCCEEDS(mCookie->GetIsDomain(mIsDomain.ptr()));
}
return mIsDomain.ref();
}
bool CookieInfo::IsSecure() const {
if (mIsSecure.isNothing()) {
mIsSecure.emplace(false);
MOZ_ALWAYS_SUCCEEDS(mCookie->GetIsSecure(mIsSecure.ptr()));
}
return mIsSecure.ref();
}
const nsCString& CookieInfo::Host() const {
if (mHost.IsEmpty()) {
MOZ_ALWAYS_SUCCEEDS(mCookie->GetHost(mHost));
}
return mHost;
}
const nsCString& CookieInfo::RawHost() const {
if (mRawHost.IsEmpty()) {
MOZ_ALWAYS_SUCCEEDS(mCookie->GetRawHost(mRawHost));
}
return mRawHost;
}
/*****************************************************************************
* MatchPattern
*****************************************************************************/
const char* PERMITTED_SCHEMES[] = {"http", "https", "ws", "wss",
"file", "ftp", "data", nullptr};
// Known schemes that are followed by "://" instead of ":".
const char* HOST_LOCATOR_SCHEMES[] = {
"http", "https", "ws", "wss", "file", "ftp", "moz-extension",
"chrome", "resource", "moz", "moz-icon", "moz-gio", nullptr};
const char* WILDCARD_SCHEMES[] = {"http", "https", "ws", "wss", nullptr};
/* static */
already_AddRefed<MatchPattern> MatchPattern::Constructor(
dom::GlobalObject& aGlobal, const nsAString& aPattern,
const MatchPatternOptions& aOptions, ErrorResult& aRv) {
RefPtr<MatchPattern> pattern = new MatchPattern(aGlobal.GetAsSupports());
pattern->Init(aGlobal.Context(), aPattern, aOptions.mIgnorePath,
aOptions.mRestrictSchemes, aRv);
if (aRv.Failed()) {
return nullptr;
}
return pattern.forget();
}
void MatchPattern::Init(JSContext* aCx, const nsAString& aPattern,
bool aIgnorePath, bool aRestrictSchemes,
ErrorResult& aRv) {
RefPtr<AtomSet> permittedSchemes;
nsresult rv = AtomSet::Get<PERMITTED_SCHEMES>(permittedSchemes);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return;
}
mPattern = aPattern;
if (aPattern.EqualsLiteral("<all_urls>")) {
mSchemes = permittedSchemes;
mMatchSubdomain = true;
return;
}
// The portion of the URL we're currently examining.
uint32_t offset = 0;
auto tail = Substring(aPattern, offset);
/***************************************************************************
* Scheme
***************************************************************************/
int32_t index = aPattern.FindChar(':');
if (index <= 0) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
RefPtr<nsAtom> scheme = NS_AtomizeMainThread(StringHead(aPattern, index));
bool requireHostLocatorScheme = true;
if (scheme == nsGkAtoms::_asterisk) {
rv = AtomSet::Get<WILDCARD_SCHEMES>(mSchemes);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return;
}
} else if (!aRestrictSchemes || permittedSchemes->Contains(scheme) ||
scheme == nsGkAtoms::moz_extension) {
RefPtr<AtomSet> hostLocatorSchemes;
rv = AtomSet::Get<HOST_LOCATOR_SCHEMES>(hostLocatorSchemes);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return;
}
mSchemes = new AtomSet({scheme});
requireHostLocatorScheme = hostLocatorSchemes->Contains(scheme);
} else {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
/***************************************************************************
* Host
***************************************************************************/
offset = index + 1;
tail.Rebind(aPattern, offset);
if (!requireHostLocatorScheme) {
// Unrecognized schemes and some schemes such as about: and data: URIs
// don't have hosts, so just match on the path.
// And so, ignorePath doesn't make sense for these matchers.
aIgnorePath = false;
} else {
if (!StringHead(tail, 2).EqualsLiteral("//")) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
offset += 2;
tail.Rebind(aPattern, offset);
index = tail.FindChar('/');
if (index < 0) {
index = tail.Length();
}
auto host = StringHead(tail, index);
if (host.IsEmpty() && scheme != nsGkAtoms::file) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
offset += index;
tail.Rebind(aPattern, offset);
if (host.EqualsLiteral("*")) {
mMatchSubdomain = true;
} else if (StringHead(host, 2).EqualsLiteral("*.")) {
CopyUTF16toUTF8(Substring(host, 2), mDomain);
mMatchSubdomain = true;
} else if (host.Length() > 1 && host[0] == '[' &&
host[host.Length() - 1] == ']') {
// This is an IPv6 literal, we drop the enclosing `[]` to be
// consistent with nsIURI.
CopyUTF16toUTF8(Substring(host, 1, host.Length() - 2), mDomain);
} else {
CopyUTF16toUTF8(host, mDomain);
}
}
/***************************************************************************
* Path
***************************************************************************/
if (aIgnorePath) {
mPattern.Truncate(offset);
mPattern.AppendLiteral("/*");
return;
}
auto path = tail;
if (path.IsEmpty()) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
mPath = new MatchGlob(this);
mPath->Init(aCx, path, false, aRv);
}
bool MatchPattern::MatchesDomain(const nsACString& aDomain) const {
if (DomainIsWildcard() || mDomain == aDomain) {
return true;
}
if (mMatchSubdomain) {
int64_t offset = (int64_t)aDomain.Length() - mDomain.Length();
if (offset > 0 && aDomain[offset - 1] == '.' &&
Substring(aDomain, offset) == mDomain) {
return true;
}
}
return false;
}
bool MatchPattern::Matches(const nsAString& aURL, bool aExplicit,
ErrorResult& aRv) const {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return false;
}
return Matches(uri.get(), aExplicit);
}
bool MatchPattern::Matches(const URLInfo& aURL, bool aExplicit) const {
if (aExplicit && mMatchSubdomain) {
return false;
}
if (!mSchemes->Contains(aURL.Scheme())) {
return false;
}
if (!MatchesDomain(aURL.Host())) {
return false;
}
if (mPath && !mPath->IsWildcard() && !mPath->Matches(aURL.Path())) {
return false;
}
return true;
}
bool MatchPattern::MatchesCookie(const CookieInfo& aCookie) const {
if (!mSchemes->Contains(nsGkAtoms::https) &&
(aCookie.IsSecure() || !mSchemes->Contains(nsGkAtoms::http))) {
return false;
}
if (MatchesDomain(aCookie.RawHost())) {
return true;
}
if (!aCookie.IsDomain()) {
return false;
}
// Things get tricker for domain cookies. The extension needs to be able
// to read any cookies that could be read by any host it has permissions
// for. This means that our normal host matching checks won't work,
// since the pattern "*://*.foo.example.com/" doesn't match ".example.com",
// but it does match "bar.foo.example.com", which can read cookies
// with the domain ".example.com".
//
// So, instead, we need to manually check our filters, and accept any
// with hosts that end with our cookie's host.
auto& host = aCookie.Host();
return StringTail(mDomain, host.Length()) == host;
}
bool MatchPattern::SubsumesDomain(const MatchPattern& aPattern) const {
if (!mMatchSubdomain && aPattern.mMatchSubdomain &&
aPattern.mDomain == mDomain) {
return false;
}
return MatchesDomain(aPattern.mDomain);
}
bool MatchPattern::Subsumes(const MatchPattern& aPattern) const {
for (auto& scheme : *aPattern.mSchemes) {
if (!mSchemes->Contains(scheme)) {
return false;
}
}
return SubsumesDomain(aPattern);
}
bool MatchPattern::Overlaps(const MatchPattern& aPattern) const {
if (!mSchemes->Intersects(*aPattern.mSchemes)) {
return false;
}
return SubsumesDomain(aPattern) || aPattern.SubsumesDomain(*this);
}
JSObject* MatchPattern::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return MatchPattern_Binding::Wrap(aCx, this, aGivenProto);
}
/* static */
bool MatchPattern::MatchesAllURLs(const URLInfo& aURL) {
RefPtr<AtomSet> permittedSchemes;
nsresult rv = AtomSet::Get<PERMITTED_SCHEMES>(permittedSchemes);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to retrireve PERMITTED_SCHEMES AtomSet");
return false;
}
return permittedSchemes->Contains(aURL.Scheme());
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MatchPattern, mPath, mParent)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchPattern)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(MatchPattern)
NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchPattern)
/*****************************************************************************
* MatchPatternSet
*****************************************************************************/
/* static */
already_AddRefed<MatchPatternSet> MatchPatternSet::Constructor(
dom::GlobalObject& aGlobal,
const nsTArray<dom::OwningStringOrMatchPattern>& aPatterns,
const MatchPatternOptions& aOptions, ErrorResult& aRv) {
ArrayType patterns;
for (auto& elem : aPatterns) {
if (elem.IsMatchPattern()) {
patterns.AppendElement(elem.GetAsMatchPattern());
} else {
RefPtr<MatchPattern> pattern =
MatchPattern::Constructor(aGlobal, elem.GetAsString(), aOptions, aRv);
if (!pattern) {
return nullptr;
}
patterns.AppendElement(std::move(pattern));
}
}
RefPtr<MatchPatternSet> patternSet =
new MatchPatternSet(aGlobal.GetAsSupports(), std::move(patterns));
return patternSet.forget();
}
bool MatchPatternSet::Matches(const nsAString& aURL, bool aExplicit,
ErrorResult& aRv) const {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return false;
}
return Matches(uri.get(), aExplicit);
}
bool MatchPatternSet::Matches(const URLInfo& aURL, bool aExplicit) const {
for (const auto& pattern : mPatterns) {
if (pattern->Matches(aURL, aExplicit)) {
return true;
}
}
return false;
}
bool MatchPatternSet::MatchesCookie(const CookieInfo& aCookie) const {
for (const auto& pattern : mPatterns) {
if (pattern->MatchesCookie(aCookie)) {
return true;
}
}
return false;
}
bool MatchPatternSet::Subsumes(const MatchPattern& aPattern) const {
for (const auto& pattern : mPatterns) {
if (pattern->Subsumes(aPattern)) {
return true;
}
}
return false;
}
bool MatchPatternSet::SubsumesDomain(const MatchPattern& aPattern) const {
for (const auto& pattern : mPatterns) {
if (pattern->SubsumesDomain(aPattern)) {
return true;
}
}
return false;
}
bool MatchPatternSet::Overlaps(const MatchPatternSet& aPatternSet) const {
for (const auto& pattern : aPatternSet.mPatterns) {
if (Overlaps(*pattern)) {
return true;
}
}
return false;
}
bool MatchPatternSet::Overlaps(const MatchPattern& aPattern) const {
for (const auto& pattern : mPatterns) {
if (pattern->Overlaps(aPattern)) {
return true;
}
}
return false;
}
bool MatchPatternSet::OverlapsAll(const MatchPatternSet& aPatternSet) const {
for (const auto& pattern : aPatternSet.mPatterns) {
if (!Overlaps(*pattern)) {
return false;
}
}
return aPatternSet.mPatterns.Length() > 0;
}
JSObject* MatchPatternSet::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return MatchPatternSet_Binding::Wrap(aCx, this, aGivenProto);
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MatchPatternSet, mPatterns, mParent)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchPatternSet)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(MatchPatternSet)
NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchPatternSet)
/*****************************************************************************
* MatchGlob
*****************************************************************************/
MatchGlob::~MatchGlob() { mozilla::DropJSObjects(this); }
/* static */
already_AddRefed<MatchGlob> MatchGlob::Constructor(dom::GlobalObject& aGlobal,
const nsAString& aGlob,
bool aAllowQuestion,
ErrorResult& aRv) {
RefPtr<MatchGlob> glob = new MatchGlob(aGlobal.GetAsSupports());
glob->Init(aGlobal.Context(), aGlob, aAllowQuestion, aRv);
if (aRv.Failed()) {
return nullptr;
}
return glob.forget();
}
void MatchGlob::Init(JSContext* aCx, const nsAString& aGlob,
bool aAllowQuestion, ErrorResult& aRv) {
mGlob = aGlob;
// Check for a literal match with no glob metacharacters.
auto index = mGlob.FindCharInSet(aAllowQuestion ? u"*?" : u"*");
if (index < 0) {
mPathLiteral = mGlob;
return;
}
// Check for a prefix match, where the only glob metacharacter is a "*"
// at the end of the string.
if (index == (int32_t)mGlob.Length() - 1 && mGlob[index] == '*') {
mPathLiteral = StringHead(mGlob, index);
mIsPrefix = true;
return;
}
// Fall back to the regexp slow path.
constexpr auto metaChars = ".+*?^${}()|[]\\"_ns;
nsAutoString escaped;
escaped.Append('^');
// For any continuous string of * (and ? if aAllowQuestion) wildcards, only
// emit the first *, later ones are redundant, and can hang regex matching.
bool emittedFirstStar = false;
for (uint32_t i = 0; i < mGlob.Length(); i++) {
auto c = mGlob[i];
if (c == '*') {
if (!emittedFirstStar) {
escaped.AppendLiteral(".*");
emittedFirstStar = true;
}
} else if (c == '?' && aAllowQuestion) {
escaped.Append('.');
} else {
if (metaChars.Contains(c)) {
escaped.Append('\\');
}
escaped.Append(c);
// String of wildcards broken by a non-wildcard char, reset tracking flag.
emittedFirstStar = false;
}
}
escaped.Append('$');
// TODO: Switch to the Rust regexp crate, when Rust integration is easier.
// It uses a much more efficient, linear time matching algorithm, and
// doesn't require special casing for the literal and prefix cases.
mRegExp = JS::NewUCRegExpObject(aCx, escaped.get(), escaped.Length(), 0);
if (mRegExp) {
mozilla::HoldJSObjects(this);
} else {
aRv.NoteJSContextException(aCx);
}
}
bool MatchGlob::Matches(const nsAString& aString) const {
if (mRegExp) {
AutoJSAPI jsapi;
jsapi.Init();
JSContext* cx = jsapi.cx();
JSAutoRealm ar(cx, mRegExp);
JS::Rooted<JSObject*> regexp(cx, mRegExp);
JS::Rooted<JS::Value> result(cx);
nsString input(aString);
size_t index = 0;
if (!JS::ExecuteRegExpNoStatics(cx, regexp, input.BeginWriting(),
aString.Length(), &index, true, &result)) {
return false;
}
return result.isBoolean() && result.toBoolean();
}
if (mIsPrefix) {
return mPathLiteral == StringHead(aString, mPathLiteral.Length());
}
return mPathLiteral == aString;
}
JSObject* MatchGlob::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return MatchGlob_Binding::Wrap(aCx, this, aGivenProto);
}
NS_IMPL_CYCLE_COLLECTION_CLASS(MatchGlob)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MatchGlob)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
tmp->mRegExp = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MatchGlob)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(MatchGlob)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRegExp)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchGlob)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(MatchGlob)
NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchGlob)
/*****************************************************************************
* MatchGlobSet
*****************************************************************************/
bool MatchGlobSet::Matches(const nsAString& aValue) const {
for (auto& glob : *this) {
if (glob->Matches(aValue)) {
return true;
}
}
return false;
}
} // namespace extensions
} // namespace mozilla