diff options
Diffstat (limited to 'xpcom/ds')
75 files changed, 14404 insertions, 0 deletions
diff --git a/xpcom/ds/IncrementalTokenizer.cpp b/xpcom/ds/IncrementalTokenizer.cpp new file mode 100644 index 000000000..429428516 --- /dev/null +++ b/xpcom/ds/IncrementalTokenizer.cpp @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/IncrementalTokenizer.h" + +#include "mozilla/AutoRestore.h" + +#include "nsIInputStream.h" +#include "IncrementalTokenizer.h" +#include <algorithm> + +namespace mozilla { + +IncrementalTokenizer::IncrementalTokenizer(Consumer aConsumer, + const char * aWhitespaces, + const char * aAdditionalWordChars, + uint32_t aRawMinBuffered) + : TokenizerBase(aWhitespaces, aAdditionalWordChars) +#ifdef DEBUG + , mConsuming(false) +#endif + , mNeedMoreInput(false) + , mRollback(false) + , mInputCursor(0) + , mConsumer(aConsumer) +{ + mInputFinished = false; + mMinRawDelivery = aRawMinBuffered; +} + +nsresult IncrementalTokenizer::FeedInput(const nsACString & aInput) +{ + NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED); + MOZ_ASSERT(!mInputFinished); + + mInput.Cut(0, mInputCursor); + mInputCursor = 0; + + mInput.Append(aInput); + + return Process(); +} + +nsresult IncrementalTokenizer::FeedInput(nsIInputStream * aInput, uint32_t aCount) +{ + NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED); + MOZ_ASSERT(!mInputFinished); + MOZ_ASSERT(!mConsuming); + + mInput.Cut(0, mInputCursor); + mInputCursor = 0; + + nsresult rv = NS_OK; + while (NS_SUCCEEDED(rv) && aCount) { + nsCString::index_type remainder = mInput.Length(); + nsCString::index_type load = + std::min<nsCString::index_type>(aCount, PR_UINT32_MAX - remainder); + + if (!load) { + // To keep the API simple, we fail if the input data buffer if filled. + // It's highly unlikely there will ever be such amout of data cumulated + // unless a logic fault in the consumer code. + NS_ERROR("IncrementalTokenizer consumer not reading data?"); + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!mInput.SetLength(remainder + load, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCString::char_iterator buffer = mInput.BeginWriting() + remainder; + + uint32_t read; + rv = aInput->Read(buffer, load, &read); + if (NS_SUCCEEDED(rv)) { + // remainder + load fits the uint32_t size, so must remainder + read. + mInput.SetLength(remainder + read); + aCount -= read; + + rv = Process(); + } + } + + return rv; +} + +nsresult IncrementalTokenizer::FinishInput() +{ + NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED); + MOZ_ASSERT(!mInputFinished); + MOZ_ASSERT(!mConsuming); + + mInput.Cut(0, mInputCursor); + mInputCursor = 0; + + mInputFinished = true; + nsresult rv = Process(); + mConsumer = nullptr; + return rv; +} + +bool IncrementalTokenizer::Next(Token & aToken) +{ + // Assert we are called only from the consumer callback + MOZ_ASSERT(mConsuming); + + if (mPastEof) { + return false; + } + + nsACString::const_char_iterator next = Parse(aToken); + mPastEof = aToken.Type() == TOKEN_EOF; + if (next == mCursor && !mPastEof) { + // Not enough input to make a deterministic decision. + return false; + } + + AssignFragment(aToken, mCursor, next); + mCursor = next; + return true; +} + +void IncrementalTokenizer::NeedMoreInput() +{ + // Assert we are called only from the consumer callback + MOZ_ASSERT(mConsuming); + + // When the input has been finished, we can't set the flag to prevent + // indefinite wait for more input (that will never come) + mNeedMoreInput = !mInputFinished; +} + +void IncrementalTokenizer::Rollback() +{ + // Assert we are called only from the consumer callback + MOZ_ASSERT(mConsuming); + + mRollback = true; +} + +nsresult IncrementalTokenizer::Process() +{ +#ifdef DEBUG + // Assert we are not re-entered + MOZ_ASSERT(!mConsuming); + + AutoRestore<bool> consuming(mConsuming); + mConsuming = true; +#endif + + MOZ_ASSERT(!mPastEof); + + nsresult rv = NS_OK; + + mInput.BeginReading(mCursor); + mCursor += mInputCursor; + mInput.EndReading(mEnd); + + while (NS_SUCCEEDED(rv) && !mPastEof) { + Token token; + nsACString::const_char_iterator next = Parse(token); + mPastEof = token.Type() == TOKEN_EOF; + if (next == mCursor && !mPastEof) { + // Not enough input to make a deterministic decision. + break; + } + + AssignFragment(token, mCursor, next); + + nsACString::const_char_iterator rollback = mCursor; + mCursor = next; + + mNeedMoreInput = mRollback = false; + + rv = mConsumer(token, *this); + if (NS_FAILED(rv)) { + break; + } + if (mNeedMoreInput || mRollback) { + mCursor = rollback; + mPastEof = false; + if (mNeedMoreInput) { + break; + } + } + } + + mInputCursor = mCursor - mInput.BeginReading(); + return rv; +} + +} // mozilla diff --git a/xpcom/ds/IncrementalTokenizer.h b/xpcom/ds/IncrementalTokenizer.h new file mode 100644 index 000000000..f93668e63 --- /dev/null +++ b/xpcom/ds/IncrementalTokenizer.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 INCREMENTAL_TOKENIZER_H__ +#define INCREMENTAL_TOKENIZER_H__ + +#include "mozilla/Tokenizer.h" + +#include "nsError.h" +#include <functional> + +class nsIInputStream; + +namespace mozilla { + +class IncrementalTokenizer : public TokenizerBase +{ +public: + /** + * The consumer callback. The function is called for every single token + * as found in the input. Failure result returned by this callback stops + * the tokenization immediately and bubbles to result of Feed/FinishInput. + * + * Fragment()s of consumed tokens are ensured to remain valid until next call to + * Feed/FinishInput and are pointing to a single linear buffer. Hence, those can + * be safely used to accumulate the data for processing after Feed/FinishInput + * returned. + */ + typedef std::function<nsresult(Token const&, IncrementalTokenizer& i)> Consumer; + + /** + * For aWhitespaces and aAdditionalWordChars arguments see TokenizerBase. + * + * @param aConsumer + * A mandatory non-null argument, a function that consumes the tokens as they + * come when the tokenizer is fed. + * @param aRawMinBuffered + * When we have buffered at least aRawMinBuffered data, but there was no custom + * token found so far because of too small incremental feed chunks, deliver + * the raw data to preserve streaming and to save memory. This only has effect + * in OnlyCustomTokenizing mode. + */ + explicit IncrementalTokenizer(Consumer aConsumer, + const char* aWhitespaces = nullptr, + const char* aAdditionalWordChars = nullptr, + uint32_t aRawMinBuffered = 1024); + + /** + * Pushes the input to be tokenized. These directly call the Consumer callback + * on every found token. Result of the Consumer callback is returned here. + * + * The tokenizer must be initialized with a valid consumer prior call to these + * methods. It's not allowed to call Feed/FinishInput from inside the Consumer + * callback. + */ + nsresult FeedInput(const nsACString& aInput); + nsresult FeedInput(nsIInputStream* aInput, uint32_t aCount); + nsresult FinishInput(); + + /** + * Can only be called from inside the consumer callback. + * + * When there is still anything to read from the input, tokenize it, store + * the token type and value to aToken result and shift the cursor past this + * just parsed token. Each call to Next() reads another token from + * the input and shifts the cursor. + * + * Returns false if there is not enough data to deterministically recognize + * tokens or when the last returned token was EOF. + */ + MOZ_MUST_USE + bool Next(Token& aToken); + + /** + * Can only be called from inside the consumer callback. + * + * Tells the tokenizer to revert the cursor and stop the async parsing until + * next feed of the input. This is useful when more than one token is needed + * to decide on the syntax but there is not enough input to get a next token + * (Next() returned false.) + */ + void NeedMoreInput(); + + /** + * Can only be called from inside the consumer callback. + * + * This makes the consumer callback be called again while parsing + * the input at the previous cursor position again. This is useful when + * the tokenizer state (custom tokens, tokenization mode) has changed and + * we want to re-parse the input again. + */ + void Rollback(); + +private: + // Loops over the input with TokenizerBase::Parse and calls the Consumer callback. + nsresult Process(); + +#ifdef DEBUG + // True when inside the consumer callback, used only for assertions. + bool mConsuming; +#endif // DEBUG + // Modifyable only from the Consumer callback, tells the parser to break, rollback + // and wait for more input. + bool mNeedMoreInput; + // Modifyable only from the Consumer callback, tells the parser to rollback and + // parse the input again, with (if modified) new settings of the tokenizer. + bool mRollback; + // The input buffer. Updated with each call to Feed/FinishInput. + nsCString mInput; + // Numerical index pointing at the current cursor position. We don't keep direct + // reference to the string buffer since the buffer gets often reallocated. + nsCString::index_type mInputCursor; + // Refernce to the consumer function. + Consumer mConsumer; +}; + +} // mozilla + +#endif diff --git a/xpcom/ds/StickyTimeDuration.h b/xpcom/ds/StickyTimeDuration.h new file mode 100644 index 000000000..39a887dbc --- /dev/null +++ b/xpcom/ds/StickyTimeDuration.h @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_StickyTimeDuration_h +#define mozilla_StickyTimeDuration_h + +#include "mozilla/TimeStamp.h" +#include "mozilla/FloatingPoint.h" + +namespace mozilla { + +/** + * A ValueCalculator class that performs additional checks before performing + * arithmetic operations such that if either operand is Forever (or the + * negative equivalent) the result remains Forever (or the negative equivalent + * as appropriate). + * + * Currently this only checks if either argument to each operation is + * Forever/-Forever. However, it is possible that, for example, + * aA + aB > INT64_MAX (or < INT64_MIN). + * + * We currently don't check for that case since we don't expect that to + * happen often except under test conditions in which case the wrapping + * behavior is probably acceptable. + */ +class StickyTimeDurationValueCalculator +{ +public: + static int64_t + Add(int64_t aA, int64_t aB) + { + MOZ_ASSERT((aA != INT64_MAX || aB != INT64_MIN) && + (aA != INT64_MIN || aB != INT64_MAX), + "'Infinity + -Infinity' and '-Infinity + Infinity'" + " are undefined"); + + // Forever + x = Forever + // x + Forever = Forever + if (aA == INT64_MAX || aB == INT64_MAX) { + return INT64_MAX; + } + // -Forever + x = -Forever + // x + -Forever = -Forever + if (aA == INT64_MIN || aB == INT64_MIN) { + return INT64_MIN; + } + + return aA + aB; + } + + // Note that we can't just define Add and have BaseTimeDuration call Add with + // negative arguments since INT64_MAX != -INT64_MIN so the saturating logic + // won't work. + static int64_t + Subtract(int64_t aA, int64_t aB) + { + MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) || aA != aB, + "'Infinity - Infinity' and '-Infinity - -Infinity'" + " are undefined"); + + // Forever - x = Forever + // x - -Forever = Forever + if (aA == INT64_MAX || aB == INT64_MIN) { + return INT64_MAX; + } + // -Forever - x = -Forever + // x - Forever = -Forever + if (aA == INT64_MIN || aB == INT64_MAX) { + return INT64_MIN; + } + + return aA - aB; + } + + template <typename T> + static int64_t + Multiply(int64_t aA, T aB) { + // Specializations for double, float, and int64_t are provided following. + return Multiply(aA, static_cast<int64_t>(aB)); + } + + static int64_t + Divide(int64_t aA, int64_t aB) { + MOZ_ASSERT(aB != 0, "Division by zero"); + MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) || + (aB != INT64_MAX && aB != INT64_MIN), + "Dividing +/-Infinity by +/-Infinity is undefined"); + + // Forever / +x = Forever + // Forever / -x = -Forever + // -Forever / +x = -Forever + // -Forever / -x = Forever + if (aA == INT64_MAX || aA == INT64_MIN) { + return (aA >= 0) ^ (aB >= 0) ? INT64_MIN : INT64_MAX; + } + // x / Forever = 0 + // x / -Forever = 0 + if (aB == INT64_MAX || aB == INT64_MIN) { + return 0; + } + + return aA / aB; + } + + static double + DivideDouble(int64_t aA, int64_t aB) + { + MOZ_ASSERT(aB != 0, "Division by zero"); + MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) || + (aB != INT64_MAX && aB != INT64_MIN), + "Dividing +/-Infinity by +/-Infinity is undefined"); + + // Forever / +x = Forever + // Forever / -x = -Forever + // -Forever / +x = -Forever + // -Forever / -x = Forever + if (aA == INT64_MAX || aA == INT64_MIN) { + return (aA >= 0) ^ (aB >= 0) + ? NegativeInfinity<double>() + : PositiveInfinity<double>(); + } + // x / Forever = 0 + // x / -Forever = 0 + if (aB == INT64_MAX || aB == INT64_MIN) { + return 0.0; + } + + return static_cast<double>(aA) / aB; + } + + static int64_t + Modulo(int64_t aA, int64_t aB) + { + MOZ_ASSERT(aA != INT64_MAX && aA != INT64_MIN, + "Infinity modulo x is undefined"); + + return aA % aB; + } +}; + +template <> +inline int64_t +StickyTimeDurationValueCalculator::Multiply<int64_t>(int64_t aA, + int64_t aB) +{ + MOZ_ASSERT((aA != 0 || (aB != INT64_MIN && aB != INT64_MAX)) && + ((aA != INT64_MIN && aA != INT64_MAX) || aB != 0), + "Multiplication of infinity by zero"); + + // Forever * +x = Forever + // Forever * -x = -Forever + // -Forever * +x = -Forever + // -Forever * -x = Forever + // + // i.e. If one or more of the arguments is +/-Forever, then + // return -Forever if the signs differ, or +Forever otherwise. + if (aA == INT64_MAX || aA == INT64_MIN || + aB == INT64_MAX || aB == INT64_MIN) { + return (aA >= 0) ^ (aB >= 0) ? INT64_MIN : INT64_MAX; + } + + return aA * aB; +} + +template <> +inline int64_t +StickyTimeDurationValueCalculator::Multiply<double>(int64_t aA, double aB) +{ + MOZ_ASSERT((aA != 0 || (!IsInfinite(aB))) && + ((aA != INT64_MIN && aA != INT64_MAX) || aB != 0.0), + "Multiplication of infinity by zero"); + + // As with Multiply<int64_t>, if one or more of the arguments is + // +/-Forever or +/-Infinity, then return -Forever if the signs differ, + // or +Forever otherwise. + if (aA == INT64_MAX || aA == INT64_MIN || IsInfinite(aB)) { + return (aA >= 0) ^ (aB >= 0.0) ? INT64_MIN : INT64_MAX; + } + + return aA * aB; +} + +template <> +inline int64_t +StickyTimeDurationValueCalculator::Multiply<float>(int64_t aA, float aB) +{ + MOZ_ASSERT(IsInfinite(aB) == IsInfinite(static_cast<double>(aB)), + "Casting to float loses infinite-ness"); + + return Multiply(aA, static_cast<double>(aB)); +} + +/** + * Specialization of BaseTimeDuration that uses + * StickyTimeDurationValueCalculator for arithmetic on the mValue member. + * + * Use this class when you need a time duration that is expected to hold values + * of Forever (or the negative equivalent) *and* when you expect that + * time duration to be used in arithmetic operations (and not just value + * comparisons). + */ +typedef BaseTimeDuration<StickyTimeDurationValueCalculator> + StickyTimeDuration; + +// Template specializations to allow arithmetic between StickyTimeDuration +// and TimeDuration objects by falling back to the safe behavior. +inline StickyTimeDuration +operator+(const TimeDuration& aA, const StickyTimeDuration& aB) +{ + return StickyTimeDuration(aA) + aB; +} +inline StickyTimeDuration +operator+(const StickyTimeDuration& aA, const TimeDuration& aB) +{ + return aA + StickyTimeDuration(aB); +} + +inline StickyTimeDuration +operator-(const TimeDuration& aA, const StickyTimeDuration& aB) +{ + return StickyTimeDuration(aA) - aB; +} +inline StickyTimeDuration +operator-(const StickyTimeDuration& aA, const TimeDuration& aB) +{ + return aA - StickyTimeDuration(aB); +} + +inline StickyTimeDuration& +operator+=(StickyTimeDuration &aA, const TimeDuration& aB) +{ + return aA += StickyTimeDuration(aB); +} +inline StickyTimeDuration& +operator-=(StickyTimeDuration &aA, const TimeDuration& aB) +{ + return aA -= StickyTimeDuration(aB); +} + +inline double +operator/(const TimeDuration& aA, const StickyTimeDuration& aB) +{ + return StickyTimeDuration(aA) / aB; +} +inline double +operator/(const StickyTimeDuration& aA, const TimeDuration& aB) +{ + return aA / StickyTimeDuration(aB); +} + +inline StickyTimeDuration +operator%(const TimeDuration& aA, const StickyTimeDuration& aB) +{ + return StickyTimeDuration(aA) % aB; +} +inline StickyTimeDuration +operator%(const StickyTimeDuration& aA, const TimeDuration& aB) +{ + return aA % StickyTimeDuration(aB); +} + +} // namespace mozilla + +#endif /* mozilla_StickyTimeDuration_h */ diff --git a/xpcom/ds/Tokenizer.cpp b/xpcom/ds/Tokenizer.cpp new file mode 100644 index 000000000..66cc1ebb7 --- /dev/null +++ b/xpcom/ds/Tokenizer.cpp @@ -0,0 +1,738 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "Tokenizer.h" + +#include "nsUnicharUtils.h" +#include <algorithm> + +namespace mozilla { + +static const char sWhitespaces[] = " \t"; + +Tokenizer::Tokenizer(const nsACString& aSource, + const char* aWhitespaces, + const char* aAdditionalWordChars) + : TokenizerBase(aWhitespaces, aAdditionalWordChars) +{ + mInputFinished = true; + aSource.BeginReading(mCursor); + mRecord = mRollback = mCursor; + aSource.EndReading(mEnd); +} + +Tokenizer::Tokenizer(const char* aSource, + const char* aWhitespaces, + const char* aAdditionalWordChars) + : Tokenizer(nsDependentCString(aSource), aWhitespaces, aAdditionalWordChars) +{ +} + +bool +Tokenizer::Next(Token& aToken) +{ + if (!HasInput()) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + mCursor = Parse(aToken); + + AssignFragment(aToken, mRollback, mCursor); + + mPastEof = aToken.Type() == TOKEN_EOF; + mHasFailed = false; + return true; +} + +bool +Tokenizer::Check(const TokenType aTokenType, Token& aResult) +{ + if (!HasInput()) { + mHasFailed = true; + return false; + } + + nsACString::const_char_iterator next = Parse(aResult); + if (aTokenType != aResult.Type()) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + mCursor = next; + + AssignFragment(aResult, mRollback, mCursor); + + mPastEof = aResult.Type() == TOKEN_EOF; + mHasFailed = false; + return true; +} + +bool +Tokenizer::Check(const Token& aToken) +{ + if (!HasInput()) { + mHasFailed = true; + return false; + } + + Token parsed; + nsACString::const_char_iterator next = Parse(parsed); + if (!aToken.Equals(parsed)) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + mCursor = next; + mPastEof = parsed.Type() == TOKEN_EOF; + mHasFailed = false; + return true; +} + +void +Tokenizer::SkipWhites(WhiteSkipping aIncludeNewLines) +{ + if (!CheckWhite() && (aIncludeNewLines == DONT_INCLUDE_NEW_LINE || !CheckEOL())) { + return; + } + + nsACString::const_char_iterator rollback = mRollback; + while (CheckWhite() || (aIncludeNewLines == INCLUDE_NEW_LINE && CheckEOL())) { + } + + mHasFailed = false; + mRollback = rollback; +} + +void +Tokenizer::SkipUntil(Token const& aToken) +{ + nsACString::const_char_iterator rollback = mCursor; + const Token eof = Token::EndOfFile(); + + Token t; + while (Next(t)) { + if (aToken.Equals(t) || eof.Equals(t)) { + Rollback(); + break; + } + } + + mRollback = rollback; +} + +bool +Tokenizer::CheckChar(bool (*aClassifier)(const char aChar)) +{ + if (!aClassifier) { + MOZ_ASSERT(false); + return false; + } + + if (!HasInput() || mCursor == mEnd) { + mHasFailed = true; + return false; + } + + if (!aClassifier(*mCursor)) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + ++mCursor; + mHasFailed = false; + return true; +} + +bool +Tokenizer::ReadChar(char* aValue) +{ + MOZ_RELEASE_ASSERT(aValue); + + Token t; + if (!Check(TOKEN_CHAR, t)) { + return false; + } + + *aValue = t.AsChar(); + return true; +} + +bool +Tokenizer::ReadChar(bool (*aClassifier)(const char aChar), char* aValue) +{ + MOZ_RELEASE_ASSERT(aValue); + + if (!CheckChar(aClassifier)) { + return false; + } + + *aValue = *mRollback; + return true; +} + +bool +Tokenizer::ReadWord(nsACString& aValue) +{ + Token t; + if (!Check(TOKEN_WORD, t)) { + return false; + } + + aValue.Assign(t.AsString()); + return true; +} + +bool +Tokenizer::ReadWord(nsDependentCSubstring& aValue) +{ + Token t; + if (!Check(TOKEN_WORD, t)) { + return false; + } + + aValue.Rebind(t.AsString().BeginReading(), t.AsString().Length()); + return true; +} + +bool +Tokenizer::ReadUntil(Token const& aToken, nsACString& aResult, ClaimInclusion aInclude) +{ + nsDependentCSubstring substring; + bool rv = ReadUntil(aToken, substring, aInclude); + aResult.Assign(substring); + return rv; +} + +bool +Tokenizer::ReadUntil(Token const& aToken, nsDependentCSubstring& aResult, ClaimInclusion aInclude) +{ + Record(); + nsACString::const_char_iterator rollback = mCursor; + + bool found = false; + Token t; + while (Next(t)) { + if (aToken.Equals(t)) { + found = true; + break; + } + } + + Claim(aResult, aInclude); + mRollback = rollback; + return found; +} + +void +Tokenizer::Rollback() +{ + MOZ_ASSERT(mCursor > mRollback || mPastEof, + "Tokenizer::Rollback() cannot use twice or before any parsing"); + + mPastEof = false; + mHasFailed = false; + mCursor = mRollback; +} + +void +Tokenizer::Record(ClaimInclusion aInclude) +{ + mRecord = aInclude == INCLUDE_LAST + ? mRollback + : mCursor; +} + +void +Tokenizer::Claim(nsACString& aResult, ClaimInclusion aInclusion) +{ + nsACString::const_char_iterator close = aInclusion == EXCLUDE_LAST + ? mRollback + : mCursor; + aResult.Assign(Substring(mRecord, close)); +} + +void +Tokenizer::Claim(nsDependentCSubstring& aResult, ClaimInclusion aInclusion) +{ + nsACString::const_char_iterator close = aInclusion == EXCLUDE_LAST + ? mRollback + : mCursor; + aResult.Rebind(mRecord, close - mRecord); +} + +// TokenizerBase + +TokenizerBase::TokenizerBase(const char* aWhitespaces, + const char* aAdditionalWordChars) + : mPastEof(false) + , mHasFailed(false) + , mInputFinished(true) + , mMode(Mode::FULL) + , mMinRawDelivery(1024) + , mWhitespaces(aWhitespaces ? aWhitespaces : sWhitespaces) + , mAdditionalWordChars(aAdditionalWordChars) + , mCursor(nullptr) + , mEnd(nullptr) + , mNextCustomTokenID(TOKEN_CUSTOM0) +{ +} + +TokenizerBase::Token +TokenizerBase::AddCustomToken(const nsACString & aValue, + ECaseSensitivity aCaseInsensitivity, bool aEnabled) +{ + MOZ_ASSERT(!aValue.IsEmpty()); + + UniquePtr<Token>& t = *mCustomTokens.AppendElement(); + t = MakeUnique<Token>(); + + t->mType = static_cast<TokenType>(++mNextCustomTokenID); + t->mCustomCaseInsensitivity = aCaseInsensitivity; + t->mCustomEnabled = aEnabled; + t->mCustom.Assign(aValue); + return *t; +} + +void +TokenizerBase::RemoveCustomToken(Token& aToken) +{ + if (aToken.mType == TOKEN_UNKNOWN) { + // Already removed + return; + } + + for (UniquePtr<Token> const& custom : mCustomTokens) { + if (custom->mType == aToken.mType) { + mCustomTokens.RemoveElement(custom); + aToken.mType = TOKEN_UNKNOWN; + return; + } + } + + MOZ_ASSERT(false, "Token to remove not found"); +} + +void +TokenizerBase::EnableCustomToken(Token const& aToken, bool aEnabled) +{ + if (aToken.mType == TOKEN_UNKNOWN) { + // Already removed + return; + } + + for (UniquePtr<Token> const& custom : mCustomTokens) { + if (custom->Type() == aToken.Type()) { + // This effectively destroys the token instance. + custom->mCustomEnabled = aEnabled; + return; + } + } + + MOZ_ASSERT(false, "Token to change not found"); +} + +void +TokenizerBase::SetTokenizingMode(Mode aMode) +{ + mMode = aMode; +} + +bool +TokenizerBase::HasFailed() const +{ + return mHasFailed; +} + +bool +TokenizerBase::HasInput() const +{ + return !mPastEof; +} + +nsACString::const_char_iterator +TokenizerBase::Parse(Token& aToken) const +{ + if (mCursor == mEnd) { + if (!mInputFinished) { + return mCursor; + } + + aToken = Token::EndOfFile(); + return mEnd; + } + + nsACString::size_type available = mEnd - mCursor; + + uint32_t longestCustom = 0; + for (UniquePtr<Token> const& custom : mCustomTokens) { + if (IsCustom(mCursor, *custom, &longestCustom)) { + aToken = *custom; + return mCursor + custom->mCustom.Length(); + } + } + + if (!mInputFinished && available < longestCustom) { + // Not enough data to deterministically decide. + return mCursor; + } + + nsACString::const_char_iterator next = mCursor; + + if (mMode == Mode::CUSTOM_ONLY) { + // We have to do a brute-force search for all of the enabled custom + // tokens. + while (next < mEnd) { + ++next; + for (UniquePtr<Token> const& custom : mCustomTokens) { + if (IsCustom(next, *custom)) { + aToken = Token::Raw(); + return next; + } + } + } + + if (mInputFinished) { + // End of the data reached. + aToken = Token::Raw(); + return next; + } + + if (longestCustom < available && available > mMinRawDelivery) { + // We can return some data w/o waiting for either a custom token + // or call to FinishData() when we leave the tail where all the + // custom tokens potentially fit, so we can't lose only partially + // delivered tokens. This preserves reasonable granularity. + aToken = Token::Raw(); + return mEnd - longestCustom + 1; + } + + // Not enough data to deterministically decide. + return mCursor; + } + + enum State { + PARSE_INTEGER, + PARSE_WORD, + PARSE_CRLF, + PARSE_LF, + PARSE_WS, + PARSE_CHAR, + } state; + + if (IsWordFirst(*next)) { + state = PARSE_WORD; + } else if (IsNumber(*next)) { + state = PARSE_INTEGER; + } else if (strchr(mWhitespaces, *next)) { // not UTF-8 friendly? + state = PARSE_WS; + } else if (*next == '\r') { + state = PARSE_CRLF; + } else if (*next == '\n') { + state = PARSE_LF; + } else { + state = PARSE_CHAR; + } + + mozilla::CheckedUint64 resultingNumber = 0; + + while (next < mEnd) { + switch (state) { + case PARSE_INTEGER: + // Keep it simple for now + resultingNumber *= 10; + resultingNumber += static_cast<uint64_t>(*next - '0'); + + ++next; + if (IsPending(next)) { + break; + } + if (IsEnd(next) || !IsNumber(*next)) { + if (!resultingNumber.isValid()) { + aToken = Token::Error(); + } else { + aToken = Token::Number(resultingNumber.value()); + } + return next; + } + break; + + case PARSE_WORD: + ++next; + if (IsPending(next)) { + break; + } + if (IsEnd(next) || !IsWord(*next)) { + aToken = Token::Word(Substring(mCursor, next)); + return next; + } + break; + + case PARSE_CRLF: + ++next; + if (IsPending(next)) { + break; + } + if (!IsEnd(next) && *next == '\n') { // LF is optional + ++next; + } + aToken = Token::NewLine(); + return next; + + case PARSE_LF: + ++next; + aToken = Token::NewLine(); + return next; + + case PARSE_WS: + ++next; + aToken = Token::Whitespace(); + return next; + + case PARSE_CHAR: + ++next; + aToken = Token::Char(*mCursor); + return next; + } // switch (state) + } // while (next < end) + + MOZ_ASSERT(!mInputFinished); + return mCursor; +} + +bool +TokenizerBase::IsEnd(const nsACString::const_char_iterator& caret) const +{ + return caret == mEnd; +} + +bool +TokenizerBase::IsPending(const nsACString::const_char_iterator& caret) const +{ + return IsEnd(caret) && !mInputFinished; +} + +bool +TokenizerBase::IsWordFirst(const char aInput) const +{ + // TODO: make this fully work with unicode + return (ToLowerCase(static_cast<uint32_t>(aInput)) != + ToUpperCase(static_cast<uint32_t>(aInput))) || + '_' == aInput || + (mAdditionalWordChars ? !!strchr(mAdditionalWordChars, aInput) : false); +} + +bool +TokenizerBase::IsWord(const char aInput) const +{ + return IsWordFirst(aInput) || IsNumber(aInput); +} + +bool +TokenizerBase::IsNumber(const char aInput) const +{ + // TODO: are there unicode numbers? + return aInput >= '0' && aInput <= '9'; +} + +bool +TokenizerBase::IsCustom(const nsACString::const_char_iterator & caret, + const Token & aCustomToken, + uint32_t * aLongest) const +{ + MOZ_ASSERT(aCustomToken.mType > TOKEN_CUSTOM0); + if (!aCustomToken.mCustomEnabled) { + return false; + } + + if (aLongest) { + *aLongest = std::max(*aLongest, aCustomToken.mCustom.Length()); + } + + uint32_t inputLength = mEnd - caret; + if (aCustomToken.mCustom.Length() > inputLength) { + return false; + } + + nsDependentCSubstring inputFragment(caret, aCustomToken.mCustom.Length()); + if (aCustomToken.mCustomCaseInsensitivity == CASE_INSENSITIVE) { + return inputFragment.Equals(aCustomToken.mCustom, nsCaseInsensitiveUTF8StringComparator()); + } + return inputFragment.Equals(aCustomToken.mCustom); +} + +void TokenizerBase::AssignFragment(Token& aToken, + nsACString::const_char_iterator begin, + nsACString::const_char_iterator end) +{ + aToken.AssignFragment(begin, end); +} + +// TokenizerBase::Token + +TokenizerBase::Token::Token() + : mType(TOKEN_UNKNOWN) + , mChar(0) + , mInteger(0) + , mCustomCaseInsensitivity(CASE_SENSITIVE) + , mCustomEnabled(false) +{ +} + +TokenizerBase::Token::Token(const Token& aOther) + : mType(aOther.mType) + , mCustom(aOther.mCustom) + , mChar(aOther.mChar) + , mInteger(aOther.mInteger) + , mCustomCaseInsensitivity(aOther.mCustomCaseInsensitivity) + , mCustomEnabled(aOther.mCustomEnabled) +{ + if (mType == TOKEN_WORD || mType > TOKEN_CUSTOM0) { + mWord.Rebind(aOther.mWord.BeginReading(), aOther.mWord.Length()); + } +} + +TokenizerBase::Token& +TokenizerBase::Token::operator=(const Token& aOther) +{ + mType = aOther.mType; + mCustom = aOther.mCustom; + mChar = aOther.mChar; + mWord.Rebind(aOther.mWord.BeginReading(), aOther.mWord.Length()); + mInteger = aOther.mInteger; + mCustomCaseInsensitivity = aOther.mCustomCaseInsensitivity; + mCustomEnabled = aOther.mCustomEnabled; + return *this; +} + +void +TokenizerBase::Token::AssignFragment(nsACString::const_char_iterator begin, + nsACString::const_char_iterator end) +{ + mFragment.Rebind(begin, end - begin); +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Raw() +{ + Token t; + t.mType = TOKEN_RAW; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Word(const nsACString& aValue) +{ + Token t; + t.mType = TOKEN_WORD; + t.mWord.Rebind(aValue.BeginReading(), aValue.Length()); + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Char(const char aValue) +{ + Token t; + t.mType = TOKEN_CHAR; + t.mChar = aValue; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Number(const uint64_t aValue) +{ + Token t; + t.mType = TOKEN_INTEGER; + t.mInteger = aValue; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Whitespace() +{ + Token t; + t.mType = TOKEN_WS; + t.mChar = '\0'; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::NewLine() +{ + Token t; + t.mType = TOKEN_EOL; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::EndOfFile() +{ + Token t; + t.mType = TOKEN_EOF; + return t; +} + +// static +TokenizerBase::Token +TokenizerBase::Token::Error() +{ + Token t; + t.mType = TOKEN_ERROR; + return t; +} + +bool +TokenizerBase::Token::Equals(const Token& aOther) const +{ + if (mType != aOther.mType) { + return false; + } + + switch (mType) { + case TOKEN_INTEGER: + return AsInteger() == aOther.AsInteger(); + case TOKEN_WORD: + return AsString() == aOther.AsString(); + case TOKEN_CHAR: + return AsChar() == aOther.AsChar(); + default: + return true; + } +} + +char +TokenizerBase::Token::AsChar() const +{ + MOZ_ASSERT(mType == TOKEN_CHAR || mType == TOKEN_WS); + return mChar; +} + +nsDependentCSubstring +TokenizerBase::Token::AsString() const +{ + MOZ_ASSERT(mType == TOKEN_WORD); + return mWord; +} + +uint64_t +TokenizerBase::Token::AsInteger() const +{ + MOZ_ASSERT(mType == TOKEN_INTEGER); + return mInteger; +} + +} // mozilla diff --git a/xpcom/ds/Tokenizer.h b/xpcom/ds/Tokenizer.h new file mode 100644 index 000000000..b4aad9ed9 --- /dev/null +++ b/xpcom/ds/Tokenizer.h @@ -0,0 +1,446 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 Tokenizer_h__ +#define Tokenizer_h__ + +#include "nsString.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +namespace mozilla { + +class TokenizerBase +{ +public: + /** + * The analyzer works with elements in the input cut to a sequence of token + * where each token has an elementary type + */ + enum TokenType : uint32_t + { + TOKEN_UNKNOWN, + TOKEN_RAW, + TOKEN_ERROR, + TOKEN_INTEGER, + TOKEN_WORD, + TOKEN_CHAR, + TOKEN_WS, + TOKEN_EOL, + TOKEN_EOF, + TOKEN_CUSTOM0 = 1000 + }; + + enum ECaseSensitivity + { + CASE_SENSITIVE, + CASE_INSENSITIVE + }; + + /** + * Class holding the type and the value of a token. It can be manually created + * to allow checks against it via methods of Tokenizer or are results of some of + * the Tokenizer's methods. + */ + class Token + { + TokenType mType; + nsDependentCSubstring mWord; + nsCString mCustom; + char mChar; + uint64_t mInteger; + ECaseSensitivity mCustomCaseInsensitivity; + bool mCustomEnabled; + + // If this token is a result of the parsing process, this member is referencing + // a sub-string in the input buffer. If this is externally created Token this + // member is left an empty string. + nsDependentCSubstring mFragment; + + friend class TokenizerBase; + void AssignFragment(nsACString::const_char_iterator begin, + nsACString::const_char_iterator end); + + static Token Raw(); + + public: + Token(); + Token(const Token& aOther); + Token& operator=(const Token& aOther); + + // Static constructors of tokens by type and value + static Token Word(const nsACString& aWord); + static Token Char(const char aChar); + static Token Number(const uint64_t aNumber); + static Token Whitespace(); + static Token NewLine(); + static Token EndOfFile(); + static Token Error(); + + // Compares the two tokens, type must be identical and value + // of one of the tokens must be 'any' or equal. + bool Equals(const Token& aOther) const; + + TokenType Type() const { return mType; } + char AsChar() const; + nsDependentCSubstring AsString() const; + uint64_t AsInteger() const; + + nsDependentCSubstring Fragment() const { return mFragment; } + }; + + /** + * Consumers may register a custom string that, when found in the input, is considered + * a token and returned by Next*() and accepted by Check*() methods. + * AddCustomToken() returns a reference to a token that can then be comapred using + * Token::Equals() againts the output from Next*() or be passed to Check*(). + */ + Token AddCustomToken(const nsACString& aValue, ECaseSensitivity aCaseInsensitivity, bool aEnabled = true); + template <uint32_t N> + Token AddCustomToken(const char(&aValue)[N], ECaseSensitivity aCaseInsensitivity, bool aEnabled = true) + { + return AddCustomToken(nsDependentCSubstring(aValue, N - 1), aCaseInsensitivity, aEnabled); + } + void RemoveCustomToken(Token& aToken); + /** + * Only applies to a custom type of a Token (see AddCustomToken above.) + * This turns on and off token recognition. When a custom token is disabled, + * it's ignored as never added as a custom token. + */ + void EnableCustomToken(Token const& aToken, bool aEnable); + + /** + * Mode of tokenization. + * FULL tokenization, the default, recognizes built-in tokens and any custom tokens, + * if added. + * CUSTOM_ONLY will only recognize custom tokens, the rest is seen as 'raw'. + * This mode can be understood as a 'binary' mode. + */ + enum class Mode + { + FULL, + CUSTOM_ONLY + }; + void SetTokenizingMode(Mode aMode); + + /** + * Return false iff the last Check*() call has returned false or when we've read past + * the end of the input string. + */ + MOZ_MUST_USE bool HasFailed() const; + +protected: + explicit TokenizerBase(const char* aWhitespaces = nullptr, + const char* aAdditionalWordChars = nullptr); + + // false if we have already read the EOF token. + bool HasInput() const; + // Main parsing function, it doesn't shift the read cursor, just returns the next + // token position. + nsACString::const_char_iterator Parse(Token& aToken) const; + // Is read cursor at the end? + bool IsEnd(const nsACString::const_char_iterator& caret) const; + // True, when we are at the end of the input data, but it has not been marked + // as complete yet. In that case we cannot proceed with providing a multi-char token. + bool IsPending(const nsACString::const_char_iterator & caret) const; + // Is read cursor on a character that is a word start? + bool IsWordFirst(const char aInput) const; + // Is read cursor on a character that is an in-word letter? + bool IsWord(const char aInput) const; + // Is read cursor on a character that is a valid number? + // TODO - support multiple radix + bool IsNumber(const char aInput) const; + // Is equal to the given custom token? + bool IsCustom(const nsACString::const_char_iterator& caret, + const Token& aCustomToken, uint32_t* aLongest = nullptr) const; + + // Friendly helper to assign a fragment on a Token + static void AssignFragment(Token& aToken, + nsACString::const_char_iterator begin, + nsACString::const_char_iterator end); + + // true iff we have already read the EOF token + bool mPastEof; + // true iff the last Check*() call has returned false, reverts to true on Rollback() call + bool mHasFailed; + // true if the input string is final (finished), false when we expect more data + // yet to be fed to the tokenizer (see IncrementalTokenizer derived class). + bool mInputFinished; + // custom only vs full tokenizing mode, see the Parse() method + Mode mMode; + // minimal raw data chunked delivery during incremental feed + uint32_t mMinRawDelivery; + + // Customizable list of whitespaces + const char* mWhitespaces; + // Additinal custom word characters + const char* mAdditionalWordChars; + + // All these point to the original buffer passed to the constructor or to the incremental + // buffer after FeedInput. + nsACString::const_char_iterator mCursor; // Position of the current (actually next to read) token start + nsACString::const_char_iterator mEnd; // End of the input position + + // This is the list of tokens user has registered with AddCustomToken() + nsTArray<UniquePtr<Token>> mCustomTokens; + uint32_t mNextCustomTokenID; + +private: + TokenizerBase() = delete; + TokenizerBase(const TokenizerBase&) = delete; + TokenizerBase(TokenizerBase&&) = delete; + TokenizerBase(const TokenizerBase&&) = delete; + TokenizerBase &operator=(const TokenizerBase&) = delete; +}; + +/** + * This is a simple implementation of a lexical analyzer or maybe better + * called a tokenizer. It doesn't allow any user dictionaries or + * user define token types. + * + * It is limited only to ASCII input for now. UTF-8 or any other input + * encoding must yet be implemented. + */ +class Tokenizer : public TokenizerBase +{ +public: + /** + * @param aSource + * The string to parse. + * IMPORTANT NOTE: Tokenizer doesn't ensure the input string buffer lifetime. + * It's up to the consumer to make sure the string's buffer outlives the Tokenizer! + * @param aWhitespaces + * If non-null Tokenizer will use this custom set of whitespaces for CheckWhite() + * and SkipWhites() calls. + * By default the list consists of space and tab. + * @param aAdditionalWordChars + * If non-null it will be added to the list of characters that consist a word. + * This is useful when you want to accept e.g. '-' in HTTP headers. + * By default a word character is consider any character for which upper case + * is different from lower case. + * + * If there is an overlap between aWhitespaces and aAdditionalWordChars, the check for + * word characters is made first. + */ + explicit Tokenizer(const nsACString& aSource, + const char* aWhitespaces = nullptr, + const char* aAdditionalWordChars = nullptr); + explicit Tokenizer(const char* aSource, + const char* aWhitespaces = nullptr, + const char* aAdditionalWordChars = nullptr); + + /** + * When there is still anything to read from the input, tokenize it, store the token type + * and value to aToken result and shift the cursor past this just parsed token. Each call + * to Next() reads another token from the input and shifts the cursor. + * Returns false if we have passed the end of the input. + */ + MOZ_MUST_USE + bool Next(Token& aToken); + + /** + * Parse the token on the input read cursor position, check its type is equal to aTokenType + * and if so, put it into aResult, shift the cursor and return true. Otherwise, leave + * the input read cursor position intact and return false. + */ + MOZ_MUST_USE + bool Check(const TokenType aTokenType, Token& aResult); + /** + * Same as above method, just compares both token type and token value passed in aToken. + * When both the type and the value equals, shift the cursor and return true. Otherwise + * return false. + */ + MOZ_MUST_USE + bool Check(const Token& aToken); + + /** + * SkipWhites method (below) may also skip new line characters automatically. + */ + enum WhiteSkipping { + /** + * SkipWhites will only skip what is defined as a white space (default). + */ + DONT_INCLUDE_NEW_LINE = 0, + /** + * SkipWhites will skip definited white spaces as well as new lines + * automatically. + */ + INCLUDE_NEW_LINE = 1 + }; + + /** + * Skips any occurence of whitespaces specified in mWhitespaces member, + * optionally skip also new lines. + */ + void SkipWhites(WhiteSkipping aIncludeNewLines = DONT_INCLUDE_NEW_LINE); + + /** + * Skips all tokens until the given one is found or EOF is hit. The token + * or EOF are next to read. + */ + void SkipUntil(Token const& aToken); + + // These are mostly shortcuts for the Check() methods above. + + /** + * Check whitespace character is present. + */ + MOZ_MUST_USE + bool CheckWhite() { return Check(Token::Whitespace()); } + /** + * Check there is a single character on the read cursor position. If so, shift the read + * cursor position and return true. Otherwise false. + */ + MOZ_MUST_USE + bool CheckChar(const char aChar) { return Check(Token::Char(aChar)); } + /** + * This is a customizable version of CheckChar. aClassifier is a function called with + * value of the character on the current input read position. If this user function + * returns true, read cursor is shifted and true returned. Otherwise false. + * The user classifiction function is not called when we are at or past the end and + * false is immediately returned. + */ + MOZ_MUST_USE + bool CheckChar(bool (*aClassifier)(const char aChar)); + /** + * Check for a whole expected word. + */ + MOZ_MUST_USE + bool CheckWord(const nsACString& aWord) { return Check(Token::Word(aWord)); } + /** + * Shortcut for literal const word check with compile time length calculation. + */ + template <uint32_t N> + MOZ_MUST_USE + bool CheckWord(const char (&aWord)[N]) { return Check(Token::Word(nsDependentCString(aWord, N - 1))); } + /** + * Checks \r, \n or \r\n. + */ + MOZ_MUST_USE + bool CheckEOL() { return Check(Token::NewLine()); } + /** + * Checks we are at the end of the input string reading. If so, shift past the end + * and returns true. Otherwise does nothing and returns false. + */ + MOZ_MUST_USE + bool CheckEOF() { return Check(Token::EndOfFile()); } + + /** + * These are shortcuts to obtain the value immediately when the token type matches. + */ + MOZ_MUST_USE bool ReadChar(char* aValue); + MOZ_MUST_USE bool ReadChar(bool (*aClassifier)(const char aChar), + char* aValue); + MOZ_MUST_USE bool ReadWord(nsACString& aValue); + MOZ_MUST_USE bool ReadWord(nsDependentCSubstring& aValue); + + /** + * This is an integer read helper. It returns false and doesn't move the read + * cursor when any of the following happens: + * - the token at the read cursor is not an integer + * - the final number doesn't fit the T type + * Otherwise true is returned, aValue is filled with the integral number + * and the cursor is moved forward. + */ + template <typename T> + MOZ_MUST_USE bool ReadInteger(T* aValue) + { + MOZ_RELEASE_ASSERT(aValue); + + nsACString::const_char_iterator rollback = mRollback; + nsACString::const_char_iterator cursor = mCursor; + Token t; + if (!Check(TOKEN_INTEGER, t)) { + return false; + } + + mozilla::CheckedInt<T> checked(t.AsInteger()); + if (!checked.isValid()) { + // Move to a state as if Check() call has failed + mRollback = rollback; + mCursor = cursor; + mHasFailed = true; + return false; + } + + *aValue = checked.value(); + return true; + } + + /** + * Returns the read cursor position back as it was before the last call of any parsing + * method of Tokenizer (Next, Check*, Skip*, Read*) so that the last operation + * can be repeated. + * Rollback cannot be used multiple times, it only reverts the last successfull parse + * operation. It also cannot be used before any parsing operation has been called + * on the Tokenizer. + */ + void Rollback(); + + /** + * Record() and Claim() are collecting the input as it is being parsed to obtain + * a substring between particular syntax bounderies defined by any recursive + * descent parser or simple parser the Tokenizer is used to read the input for. + * Inlucsion of a token that has just been parsed can be controlled using an arguemnt. + */ + enum ClaimInclusion { + /** + * Include resulting (or passed) token of the last lexical analyzer operation in the result. + */ + INCLUDE_LAST, + /** + * Do not include it. + */ + EXCLUDE_LAST + }; + + /** + * Start the process of recording. Based on aInclude value the begining of the recorded + * sub-string is at the current position (EXCLUDE_LAST) or at the position before the last + * parsed token (INCLUDE_LAST). + */ + void Record(ClaimInclusion aInclude = EXCLUDE_LAST); + /** + * Claim result of the record started with Record() call before. Depending on aInclude + * the ending of the sub-string result includes or excludes the last parsed or checked + * token. + */ + void Claim(nsACString& aResult, ClaimInclusion aInclude = EXCLUDE_LAST); + void Claim(nsDependentCSubstring& aResult, ClaimInclusion aInclude = EXCLUDE_LAST); + + /** + * If aToken is found, aResult is set to the substring between the current + * position and the position of aToken, potentially including aToken depending + * on aInclude. + * If aToken isn't found aResult is set to the substring between the current + * position and the end of the string. + * If aToken is found, the method returns true. Otherwise it returns false. + * + * Calling Rollback() after ReadUntil() will return the read cursor to the + * position it had before ReadUntil was called. + */ + MOZ_MUST_USE bool ReadUntil(Token const& aToken, nsDependentCSubstring& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + MOZ_MUST_USE bool ReadUntil(Token const& aToken, nsACString& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + +protected: + // All these point to the original buffer passed to the Tokenizer's constructor + nsACString::const_char_iterator mRecord; // Position where the recorded sub-string for Claim() is + nsACString::const_char_iterator mRollback; // Position of the previous token start + +private: + Tokenizer() = delete; + Tokenizer(const Tokenizer&) = delete; + Tokenizer(Tokenizer&&) = delete; + Tokenizer(const Tokenizer&&) = delete; + Tokenizer &operator=(const Tokenizer&) = delete; +}; + +} // mozilla + +#endif // Tokenizer_h__ diff --git a/xpcom/ds/moz.build b/xpcom/ds/moz.build new file mode 100644 index 000000000..e12f1c3dd --- /dev/null +++ b/xpcom/ds/moz.build @@ -0,0 +1,105 @@ +# -*- 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/. + +XPIDL_SOURCES += [ + 'nsIArray.idl', + 'nsIArrayExtensions.idl', + 'nsIAtom.idl', + 'nsIAtomService.idl', + 'nsICollection.idl', + 'nsIEnumerator.idl', + 'nsIHashable.idl', + 'nsIINIParser.idl', + 'nsIMutableArray.idl', + 'nsIObserver.idl', + 'nsIObserverService.idl', + 'nsIPersistentProperties2.idl', + 'nsIProperties.idl', + 'nsIProperty.idl', + 'nsIPropertyBag.idl', + 'nsIPropertyBag2.idl', + 'nsISerializable.idl', + 'nsISimpleEnumerator.idl', + 'nsIStringEnumerator.idl', + 'nsISupportsArray.idl', + 'nsISupportsIterators.idl', + 'nsISupportsPrimitives.idl', + 'nsIVariant.idl', + 'nsIWritablePropertyBag.idl', + 'nsIWritablePropertyBag2.idl', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + XPIDL_SOURCES += [ + 'nsIWindowsRegKey.idl', + ] + EXPORTS += ['nsWindowsRegKey.h'] + SOURCES += [ + 'nsWindowsRegKey.cpp' + ] + +XPIDL_MODULE = 'xpcom_ds' + +EXPORTS += [ + 'nsArray.h', + 'nsAtomService.h', + 'nsCharSeparatedTokenizer.h', + 'nsCheapSets.h', + 'nsCRT.h', + 'nsExpirationTracker.h', + 'nsHashPropertyBag.h', + 'nsMathUtils.h', + 'nsStaticAtom.h', + 'nsStaticNameTable.h', + 'nsStringEnumerator.h', + 'nsSupportsArray.h', + 'nsSupportsPrimitives.h', + 'nsVariant.h', + 'nsWhitespaceTokenizer.h', +] + +EXPORTS.mozilla += [ + 'IncrementalTokenizer.h', + 'StickyTimeDuration.h', + 'Tokenizer.h', +] + +UNIFIED_SOURCES += [ + 'IncrementalTokenizer.cpp', + 'nsArray.cpp', + 'nsAtomService.cpp', + 'nsAtomTable.cpp', + 'nsCRT.cpp', + 'nsHashPropertyBag.cpp', + 'nsINIParserImpl.cpp', + 'nsObserverList.cpp', + 'nsObserverService.cpp', + 'nsProperties.cpp', + 'nsStringEnumerator.cpp', + 'nsSupportsArray.cpp', + 'nsSupportsArrayEnumerator.cpp', + 'nsSupportsPrimitives.cpp', + 'nsVariant.cpp', + 'Tokenizer.cpp', +] + +# These two files cannot be built in unified mode because they use the +# PL_ARENA_CONST_ALIGN_MASK macro with plarena.h. +SOURCES += [ + 'nsPersistentProperties.cpp', + 'nsStaticNameTable.cpp', +] + +EXTRA_COMPONENTS += [ + 'nsINIProcessor.js', + 'nsINIProcessor.manifest', +] + +LOCAL_INCLUDES += [ + '../io', +] + +FINAL_LIBRARY = 'xul' diff --git a/xpcom/ds/nsArray.cpp b/xpcom/ds/nsArray.cpp new file mode 100644 index 000000000..d137a14a5 --- /dev/null +++ b/xpcom/ds/nsArray.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsArray.h" +#include "nsArrayEnumerator.h" +#include "nsIWeakReference.h" +#include "nsIWeakReferenceUtils.h" +#include "nsThreadUtils.h" + +// used by IndexOf() +struct MOZ_STACK_CLASS findIndexOfClosure +{ + // This is only used for pointer comparison, so we can just use a void*. + void* targetElement; + uint32_t startIndex; + uint32_t resultIndex; +}; + +static bool FindElementCallback(void* aElement, void* aClosure); + +NS_INTERFACE_MAP_BEGIN(nsArray) + NS_INTERFACE_MAP_ENTRY(nsIArray) + NS_INTERFACE_MAP_ENTRY(nsIArrayExtensions) + NS_INTERFACE_MAP_ENTRY(nsIMutableArray) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMutableArray) +NS_INTERFACE_MAP_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsArrayCC) + NS_INTERFACE_MAP_ENTRY(nsIArray) + NS_INTERFACE_MAP_ENTRY(nsIArrayExtensions) + NS_INTERFACE_MAP_ENTRY(nsIMutableArray) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMutableArray) +NS_INTERFACE_MAP_END + +nsArrayBase::~nsArrayBase() +{ + Clear(); +} + + +NS_IMPL_ADDREF(nsArray) +NS_IMPL_RELEASE(nsArray) + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsArrayCC) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsArrayCC) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsArrayCC) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsArrayCC) + tmp->Clear(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsArrayCC) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArray) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMETHODIMP +nsArrayBase::GetLength(uint32_t* aLength) +{ + *aLength = mArray.Count(); + return NS_OK; +} + +NS_IMETHODIMP +nsArrayBase::QueryElementAt(uint32_t aIndex, + const nsIID& aIID, + void** aResult) +{ + nsISupports* obj = mArray.SafeObjectAt(aIndex); + if (!obj) { + return NS_ERROR_ILLEGAL_VALUE; + } + + // no need to worry about a leak here, because SafeObjectAt() + // doesn't addref its result + return obj->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsArrayBase::IndexOf(uint32_t aStartIndex, nsISupports* aElement, + uint32_t* aResult) +{ + // optimize for the common case by forwarding to mArray + if (aStartIndex == 0) { + uint32_t idx = mArray.IndexOf(aElement); + if (idx == UINT32_MAX) { + return NS_ERROR_FAILURE; + } + + *aResult = idx; + return NS_OK; + } + + findIndexOfClosure closure = { aElement, aStartIndex, 0 }; + bool notFound = mArray.EnumerateForwards(FindElementCallback, &closure); + if (notFound) { + return NS_ERROR_FAILURE; + } + + *aResult = closure.resultIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsArrayBase::Enumerate(nsISimpleEnumerator** aResult) +{ + return NS_NewArrayEnumerator(aResult, static_cast<nsIArray*>(this)); +} + +// nsIMutableArray implementation + +NS_IMETHODIMP +nsArrayBase::AppendElement(nsISupports* aElement, bool aWeak) +{ + bool result; + if (aWeak) { + nsCOMPtr<nsIWeakReference> elementRef = do_GetWeakReference(aElement); + NS_ASSERTION(elementRef, + "AppendElement: Trying to use weak references on an object that doesn't support it"); + if (!elementRef) { + return NS_ERROR_FAILURE; + } + result = mArray.AppendObject(elementRef); + } + + else { + // add the object directly + result = mArray.AppendObject(aElement); + } + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsArrayBase::RemoveElementAt(uint32_t aIndex) +{ + bool result = mArray.RemoveObjectAt(aIndex); + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsArrayBase::InsertElementAt(nsISupports* aElement, uint32_t aIndex, bool aWeak) +{ + nsCOMPtr<nsISupports> elementRef; + if (aWeak) { + elementRef = do_GetWeakReference(aElement); + NS_ASSERTION(elementRef, + "InsertElementAt: Trying to use weak references on an object that doesn't support it"); + if (!elementRef) { + return NS_ERROR_FAILURE; + } + } else { + elementRef = aElement; + } + bool result = mArray.InsertObjectAt(elementRef, aIndex); + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsArrayBase::ReplaceElementAt(nsISupports* aElement, uint32_t aIndex, bool aWeak) +{ + nsCOMPtr<nsISupports> elementRef; + if (aWeak) { + elementRef = do_GetWeakReference(aElement); + NS_ASSERTION(elementRef, + "ReplaceElementAt: Trying to use weak references on an object that doesn't support it"); + if (!elementRef) { + return NS_ERROR_FAILURE; + } + } else { + elementRef = aElement; + } + mArray.ReplaceObjectAt(elementRef, aIndex); + return NS_OK; +} + +NS_IMETHODIMP +nsArrayBase::Clear() +{ + mArray.Clear(); + return NS_OK; +} + +// nsIArrayExtensions implementation. + +NS_IMETHODIMP +nsArrayBase::Count(uint32_t* aResult) +{ + return GetLength(aResult); +} + +NS_IMETHODIMP +nsArrayBase::GetElementAt(uint32_t aIndex, nsISupports** aResult) +{ + nsCOMPtr<nsISupports> obj = mArray.SafeObjectAt(aIndex); + obj.forget(aResult); + return NS_OK; +} + +// +// static helper routines +// +bool +FindElementCallback(void* aElement, void* aClosure) +{ + findIndexOfClosure* closure = static_cast<findIndexOfClosure*>(aClosure); + nsISupports* element = static_cast<nsISupports*>(aElement); + + // don't start searching until we're past the startIndex + if (closure->resultIndex >= closure->startIndex && + element == closure->targetElement) { + return false; // stop! We found it + } + closure->resultIndex++; + + return true; +} + +nsresult +nsArrayBase::XPCOMConstructor(nsISupports* aOuter, const nsIID& aIID, + void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + nsCOMPtr<nsIMutableArray> inst = Create(); + return inst->QueryInterface(aIID, aResult); +} + +already_AddRefed<nsIMutableArray> +nsArrayBase::Create() +{ + nsCOMPtr<nsIMutableArray> inst; + if (NS_IsMainThread()) { + inst = new nsArrayCC; + } else { + inst = new nsArray; + } + return inst.forget(); +} diff --git a/xpcom/ds/nsArray.h b/xpcom/ds/nsArray.h new file mode 100644 index 000000000..0cda23487 --- /dev/null +++ b/xpcom/ds/nsArray.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsArray_h__ +#define nsArray_h__ + +#include "nsIArrayExtensions.h" +#include "nsIMutableArray.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" + +// {35C66FD1-95E9-4e0a-80C5-C3BD2B375481} +#define NS_ARRAY_CID \ +{ 0x35c66fd1, 0x95e9, 0x4e0a, \ + { 0x80, 0xc5, 0xc3, 0xbd, 0x2b, 0x37, 0x54, 0x81 } } + +// nsArray without any refcounting declarations +class nsArrayBase : public nsIMutableArray +{ +public: + NS_DECL_NSIARRAY + NS_DECL_NSIARRAYEXTENSIONS + NS_DECL_NSIMUTABLEARRAY + + /* Both of these factory functions create a cycle-collectable array + on the main thread and a non-cycle-collectable array on other + threads. */ + static already_AddRefed<nsIMutableArray> Create(); + /* Only for the benefit of the XPCOM module system, use Create() + instead. */ + static nsresult XPCOMConstructor(nsISupports* aOuter, const nsIID& aIID, + void** aResult); +protected: + nsArrayBase() {} + nsArrayBase(const nsArrayBase& aOther); + explicit nsArrayBase(const nsCOMArray_base& aBaseArray) : mArray(aBaseArray) {} + virtual ~nsArrayBase(); + + nsCOMArray_base mArray; +}; + +class nsArray final : public nsArrayBase +{ + friend class nsArrayBase; + +public: + NS_DECL_ISUPPORTS + +private: + nsArray() : nsArrayBase() {} + nsArray(const nsArray& aOther); + explicit nsArray(const nsCOMArray_base& aBaseArray) : nsArrayBase(aBaseArray) {} + ~nsArray() {} +}; + +class nsArrayCC final : public nsArrayBase +{ + friend class nsArrayBase; + +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsArrayCC, nsIMutableArray) + +private: + nsArrayCC() : nsArrayBase() {} + nsArrayCC(const nsArrayCC& aOther); + explicit nsArrayCC(const nsCOMArray_base& aBaseArray) : nsArrayBase(aBaseArray) {} + ~nsArrayCC() {} +}; + +#endif diff --git a/xpcom/ds/nsAtomService.cpp b/xpcom/ds/nsAtomService.cpp new file mode 100644 index 000000000..7785ca2e4 --- /dev/null +++ b/xpcom/ds/nsAtomService.cpp @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsAtomService.h" +#include "nsIAtom.h" + +NS_IMPL_ISUPPORTS(nsAtomService, nsIAtomService) + +nsAtomService::nsAtomService() +{ +} + +nsresult +nsAtomService::GetAtom(const nsAString& aString, nsIAtom** aResult) +{ + *aResult = NS_Atomize(aString).take(); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} diff --git a/xpcom/ds/nsAtomService.h b/xpcom/ds/nsAtomService.h new file mode 100644 index 000000000..7a1bcf2cd --- /dev/null +++ b/xpcom/ds/nsAtomService.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 __nsAtomService_h +#define __nsAtomService_h + +#include "nsIAtomService.h" +#include "mozilla/Attributes.h" + +class nsAtomService final : public nsIAtomService +{ +public: + nsAtomService(); + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIATOMSERVICE + +private: + ~nsAtomService() {} +}; + +#endif diff --git a/xpcom/ds/nsAtomTable.cpp b/xpcom/ds/nsAtomTable.cpp new file mode 100644 index 000000000..3dd3bd36c --- /dev/null +++ b/xpcom/ds/nsAtomTable.cpp @@ -0,0 +1,736 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" + +#include "nsAtomTable.h" +#include "nsStaticAtom.h" +#include "nsString.h" +#include "nsCRT.h" +#include "PLDHashTable.h" +#include "prenv.h" +#include "nsThreadUtils.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsAutoPtr.h" +#include "nsUnicharUtils.h" +#include "nsPrintfCString.h" + +// There are two kinds of atoms handled by this module. +// +// - DynamicAtom: the atom itself is heap allocated, as is the nsStringBuffer it +// points to. |gAtomTable| holds weak references to them DynamicAtoms. When +// the refcount of a DynamicAtom drops to zero, we increment a static counter. +// When that counter reaches a certain threshold, we iterate over the atom +// table, removing and deleting DynamicAtoms with refcount zero. This allows +// us to avoid acquiring the atom table lock during normal refcounting. +// +// - StaticAtom: the atom itself is heap allocated, but it points to a static +// nsStringBuffer. |gAtomTable| effectively owns StaticAtoms, because such +// atoms ignore all AddRef/Release calls, which ensures they stay alive until +// |gAtomTable| itself is destroyed whereupon they are explicitly deleted. +// +// Note that gAtomTable is used on multiple threads, and callers must +// acquire gAtomTableLock before touching it. + +using namespace mozilla; + +//---------------------------------------------------------------------- + +class CheckStaticAtomSizes +{ + CheckStaticAtomSizes() + { + static_assert((sizeof(nsFakeStringBuffer<1>().mRefCnt) == + sizeof(nsStringBuffer().mRefCount)) && + (sizeof(nsFakeStringBuffer<1>().mSize) == + sizeof(nsStringBuffer().mStorageSize)) && + (offsetof(nsFakeStringBuffer<1>, mRefCnt) == + offsetof(nsStringBuffer, mRefCount)) && + (offsetof(nsFakeStringBuffer<1>, mSize) == + offsetof(nsStringBuffer, mStorageSize)) && + (offsetof(nsFakeStringBuffer<1>, mStringData) == + sizeof(nsStringBuffer)), + "mocked-up strings' representations should be compatible"); + } +}; + +//---------------------------------------------------------------------- + +static Atomic<uint32_t, ReleaseAcquire> gUnusedAtomCount(0); + +class DynamicAtom final : public nsIAtom +{ +public: + static already_AddRefed<DynamicAtom> Create(const nsAString& aString, uint32_t aHash) + { + // The refcount is appropriately initialized in the constructor. + return dont_AddRef(new DynamicAtom(aString, aHash)); + } + + static void GCAtomTable(); + + enum class GCKind { + RegularOperation, + Shutdown, + }; + + static void GCAtomTableLocked(const MutexAutoLock& aProofOfLock, + GCKind aKind); + +private: + DynamicAtom(const nsAString& aString, uint32_t aHash) + : mRefCnt(1) + { + mLength = aString.Length(); + mIsStatic = false; + RefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aString); + if (buf) { + mString = static_cast<char16_t*>(buf->Data()); + } else { + const size_t size = (mLength + 1) * sizeof(char16_t); + buf = nsStringBuffer::Alloc(size); + if (MOZ_UNLIKELY(!buf)) { + // We OOM because atom allocations should be small and it's hard to + // handle them more gracefully in a constructor. + NS_ABORT_OOM(size); + } + mString = static_cast<char16_t*>(buf->Data()); + CopyUnicodeTo(aString, 0, mString, mLength); + mString[mLength] = char16_t(0); + } + + mHash = aHash; + MOZ_ASSERT(mHash == HashString(mString, mLength)); + + NS_ASSERTION(mString[mLength] == char16_t(0), "null terminated"); + NS_ASSERTION(buf && buf->StorageSize() >= (mLength + 1) * sizeof(char16_t), + "enough storage"); + NS_ASSERTION(Equals(aString), "correct data"); + + // Take ownership of buffer + mozilla::Unused << buf.forget(); + } + +private: + // We don't need a virtual destructor because we always delete via a + // DynamicAtom* pointer (in GCAtomTable()), not an nsIAtom* pointer. + ~DynamicAtom(); + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIATOM +}; + +class StaticAtom final : public nsIAtom +{ +public: + StaticAtom(nsStringBuffer* aStringBuffer, uint32_t aLength, uint32_t aHash) + { + mLength = aLength; + mIsStatic = true; + mString = static_cast<char16_t*>(aStringBuffer->Data()); + // Technically we could currently avoid doing this addref by instead making + // the static atom buffers have an initial refcount of 2. + aStringBuffer->AddRef(); + + mHash = aHash; + MOZ_ASSERT(mHash == HashString(mString, mLength)); + + MOZ_ASSERT(mString[mLength] == char16_t(0), "null terminated"); + MOZ_ASSERT(aStringBuffer && + aStringBuffer->StorageSize() == (mLength + 1) * sizeof(char16_t), + "correct storage"); + } + + // We don't need a virtual destructor because we always delete via a + // StaticAtom* pointer (in AtomTableClearEntry()), not an nsIAtom* pointer. + ~StaticAtom() {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIATOM +}; + +NS_IMPL_QUERY_INTERFACE(StaticAtom, nsIAtom) + +NS_IMETHODIMP_(MozExternalRefCountType) +StaticAtom::AddRef() +{ + return 2; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +StaticAtom::Release() +{ + return 1; +} + +NS_IMETHODIMP +DynamicAtom::ScriptableToString(nsAString& aBuf) +{ + nsStringBuffer::FromData(mString)->ToString(mLength, aBuf); + return NS_OK; +} + +NS_IMETHODIMP +StaticAtom::ScriptableToString(nsAString& aBuf) +{ + nsStringBuffer::FromData(mString)->ToString(mLength, aBuf); + return NS_OK; +} + +NS_IMETHODIMP +DynamicAtom::ToUTF8String(nsACString& aBuf) +{ + CopyUTF16toUTF8(nsDependentString(mString, mLength), aBuf); + return NS_OK; +} + +NS_IMETHODIMP +StaticAtom::ToUTF8String(nsACString& aBuf) +{ + CopyUTF16toUTF8(nsDependentString(mString, mLength), aBuf); + return NS_OK; +} + +NS_IMETHODIMP +DynamicAtom::ScriptableEquals(const nsAString& aString, bool* aResult) +{ + *aResult = aString.Equals(nsDependentString(mString, mLength)); + return NS_OK; +} + +NS_IMETHODIMP +StaticAtom::ScriptableEquals(const nsAString& aString, bool* aResult) +{ + *aResult = aString.Equals(nsDependentString(mString, mLength)); + return NS_OK; +} + +NS_IMETHODIMP_(size_t) +DynamicAtom::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + n += nsStringBuffer::FromData(mString)->SizeOfIncludingThisIfUnshared( + aMallocSizeOf); + return n; +} + +NS_IMETHODIMP_(size_t) +StaticAtom::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + // Don't measure the string buffer pointed to by the StaticAtom because it's + // in static memory. + return n; +} + +//---------------------------------------------------------------------- + +/** + * The shared hash table for atom lookups. + * + * Callers must hold gAtomTableLock before manipulating the table. + */ +static PLDHashTable* gAtomTable; +static Mutex* gAtomTableLock; + +struct AtomTableKey +{ + AtomTableKey(const char16_t* aUTF16String, uint32_t aLength, uint32_t aHash) + : mUTF16String(aUTF16String) + , mUTF8String(nullptr) + , mLength(aLength) + , mHash(aHash) + { + MOZ_ASSERT(mHash == HashString(mUTF16String, mLength)); + } + + AtomTableKey(const char* aUTF8String, uint32_t aLength, uint32_t aHash) + : mUTF16String(nullptr) + , mUTF8String(aUTF8String) + , mLength(aLength) + , mHash(aHash) + { + mozilla::DebugOnly<bool> err; + MOZ_ASSERT(aHash == HashUTF8AsUTF16(mUTF8String, mLength, &err)); + } + + AtomTableKey(const char16_t* aUTF16String, uint32_t aLength, + uint32_t* aHashOut) + : mUTF16String(aUTF16String) + , mUTF8String(nullptr) + , mLength(aLength) + { + mHash = HashString(mUTF16String, mLength); + *aHashOut = mHash; + } + + AtomTableKey(const char* aUTF8String, uint32_t aLength, uint32_t* aHashOut) + : mUTF16String(nullptr) + , mUTF8String(aUTF8String) + , mLength(aLength) + { + bool err; + mHash = HashUTF8AsUTF16(mUTF8String, mLength, &err); + if (err) { + mUTF8String = nullptr; + mLength = 0; + mHash = 0; + } + *aHashOut = mHash; + } + + const char16_t* mUTF16String; + const char* mUTF8String; + uint32_t mLength; + uint32_t mHash; +}; + +struct AtomTableEntry : public PLDHashEntryHdr +{ + // These references are either to DynamicAtoms, in which case they are + // non-owning, or they are to StaticAtoms, which aren't really refcounted. + // See the comment at the top of this file for more details. + nsIAtom* MOZ_NON_OWNING_REF mAtom; +}; + +static PLDHashNumber +AtomTableGetHash(const void* aKey) +{ + const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey); + return k->mHash; +} + +static bool +AtomTableMatchKey(const PLDHashEntryHdr* aEntry, const void* aKey) +{ + const AtomTableEntry* he = static_cast<const AtomTableEntry*>(aEntry); + const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey); + + if (k->mUTF8String) { + return + CompareUTF8toUTF16(nsDependentCSubstring(k->mUTF8String, + k->mUTF8String + k->mLength), + nsDependentAtomString(he->mAtom)) == 0; + } + + uint32_t length = he->mAtom->GetLength(); + if (length != k->mLength) { + return false; + } + + return memcmp(he->mAtom->GetUTF16String(), + k->mUTF16String, length * sizeof(char16_t)) == 0; +} + +static void +AtomTableClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) +{ + auto entry = static_cast<AtomTableEntry*>(aEntry); + nsIAtom* atom = entry->mAtom; + if (atom->IsStaticAtom()) { + // This case -- when the entry being cleared holds a StaticAtom -- only + // occurs when gAtomTable is destroyed, whereupon all StaticAtoms within it + // must be explicitly deleted. The cast is required because StaticAtom + // doesn't have a virtual destructor. + delete static_cast<StaticAtom*>(atom); + } +} + +static void +AtomTableInitEntry(PLDHashEntryHdr* aEntry, const void* aKey) +{ + static_cast<AtomTableEntry*>(aEntry)->mAtom = nullptr; +} + +static const PLDHashTableOps AtomTableOps = { + AtomTableGetHash, + AtomTableMatchKey, + PLDHashTable::MoveEntryStub, + AtomTableClearEntry, + AtomTableInitEntry +}; + +//---------------------------------------------------------------------- + +void +DynamicAtom::GCAtomTable() +{ + MutexAutoLock lock(*gAtomTableLock); + GCAtomTableLocked(lock, GCKind::RegularOperation); +} + +void +DynamicAtom::GCAtomTableLocked(const MutexAutoLock& aProofOfLock, + GCKind aKind) +{ + uint32_t removedCount = 0; // Use a non-atomic temporary for cheaper increments. + nsAutoCString nonZeroRefcountAtoms; + uint32_t nonZeroRefcountAtomsCount = 0; + for (auto i = gAtomTable->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast<AtomTableEntry*>(i.Get()); + if (entry->mAtom->IsStaticAtom()) { + continue; + } + + auto atom = static_cast<DynamicAtom*>(entry->mAtom); + if (atom->mRefCnt == 0) { + i.Remove(); + delete atom; + ++removedCount; + } +#ifdef NS_FREE_PERMANENT_DATA + else if (aKind == GCKind::Shutdown && PR_GetEnv("XPCOM_MEM_BLOAT_LOG")) { + // Only report leaking atoms in leak-checking builds in a run + // where we are checking for leaks, during shutdown. If + // something is anomalous, then we'll assert later in this + // function. + nsAutoCString name; + atom->ToUTF8String(name); + if (nonZeroRefcountAtomsCount == 0) { + nonZeroRefcountAtoms = name; + } else if (nonZeroRefcountAtomsCount < 20) { + nonZeroRefcountAtoms += NS_LITERAL_CSTRING(",") + name; + } else if (nonZeroRefcountAtomsCount == 20) { + nonZeroRefcountAtoms += NS_LITERAL_CSTRING(",..."); + } + nonZeroRefcountAtomsCount++; + } +#endif + + } + if (nonZeroRefcountAtomsCount) { + nsPrintfCString msg("%d dynamic atom(s) with non-zero refcount: %s", + nonZeroRefcountAtomsCount, nonZeroRefcountAtoms.get()); + NS_ASSERTION(nonZeroRefcountAtomsCount == 0, msg.get()); + } + + // During the course of this function, the atom table is locked. This means + // that, barring refcounting bugs in consumers, an atom can never go from + // refcount == 0 to refcount != 0 during a GC. However, an atom _can_ go from + // refcount != 0 to refcount == 0 if a Release() occurs in parallel with GC. + // This means that we cannot assert that gUnusedAtomCount == removedCount, and + // thus that there are no unused atoms at the end of a GC. We can and do, + // however, assert this after the last GC at shutdown. + if (aKind == GCKind::RegularOperation) { + MOZ_ASSERT(removedCount <= gUnusedAtomCount); + } else { + // Complain if somebody adds new GCKind enums. + MOZ_ASSERT(aKind == GCKind::Shutdown); + // Our unused atom count should be accurate. + MOZ_ASSERT(removedCount == gUnusedAtomCount); + } + + gUnusedAtomCount -= removedCount; +} + +NS_IMPL_QUERY_INTERFACE(DynamicAtom, nsIAtom) + +NS_IMETHODIMP_(MozExternalRefCountType) +DynamicAtom::AddRef(void) +{ + nsrefcnt count = ++mRefCnt; + if (count == 1) { + MOZ_ASSERT(gUnusedAtomCount > 0); + gUnusedAtomCount--; + } + return count; +} + +#ifdef DEBUG +// We set a lower GC threshold for atoms in debug builds so that we exercise +// the GC machinery more often. +static const uint32_t kAtomGCThreshold = 20; +#else +static const uint32_t kAtomGCThreshold = 10000; +#endif + +NS_IMETHODIMP_(MozExternalRefCountType) +DynamicAtom::Release(void) +{ + MOZ_ASSERT(mRefCnt > 0); + nsrefcnt count = --mRefCnt; + if (count == 0) { + if (++gUnusedAtomCount >= kAtomGCThreshold) { + GCAtomTable(); + } + } + + return count; +} + +DynamicAtom::~DynamicAtom() +{ + nsStringBuffer::FromData(mString)->Release(); +} + +//---------------------------------------------------------------------- + +class StaticAtomEntry : public PLDHashEntryHdr +{ +public: + typedef const nsAString& KeyType; + typedef const nsAString* KeyTypePointer; + + explicit StaticAtomEntry(KeyTypePointer aKey) {} + StaticAtomEntry(const StaticAtomEntry& aOther) : mAtom(aOther.mAtom) {} + + // We do not delete the atom because that's done when gAtomTable is + // destroyed -- which happens immediately after gStaticAtomTable is destroyed + // -- in NS_PurgeAtomTable(). + ~StaticAtomEntry() {} + + bool KeyEquals(KeyTypePointer aKey) const + { + return mAtom->Equals(*aKey); + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return HashString(*aKey); + } + + enum { ALLOW_MEMMOVE = true }; + + // StaticAtoms aren't really refcounted. Because these entries live in a + // global hashtable, this reference is essentially owning. + StaticAtom* MOZ_OWNING_REF mAtom; +}; + +/** + * A hashtable of static atoms that existed at app startup. This hashtable + * helps nsHtml5AtomTable. + */ +typedef nsTHashtable<StaticAtomEntry> StaticAtomTable; +static StaticAtomTable* gStaticAtomTable = nullptr; + +/** + * Whether it is still OK to add atoms to gStaticAtomTable. + */ +static bool gStaticAtomTableSealed = false; + +// The atom table very quickly gets 10,000+ entries in it (or even 100,000+). +// But choosing the best initial length has some subtleties: we add ~2700 +// static atoms to the table at start-up, and then we start adding and removing +// dynamic atoms. If we make the table too big to start with, when the first +// dynamic atom gets removed the load factor will be < 25% and so we will +// shrink it to 4096 entries. +// +// By choosing an initial length of 4096, we get an initial capacity of 8192. +// That's the biggest initial capacity that will let us be > 25% full when the +// first dynamic atom is removed (when the count is ~2700), thus avoiding any +// shrinking. +#define ATOM_HASHTABLE_INITIAL_LENGTH 4096 + +void +NS_InitAtomTable() +{ + MOZ_ASSERT(!gAtomTable); + gAtomTable = new PLDHashTable(&AtomTableOps, sizeof(AtomTableEntry), + ATOM_HASHTABLE_INITIAL_LENGTH); + gAtomTableLock = new Mutex("Atom Table Lock"); +} + +void +NS_ShutdownAtomTable() +{ + delete gStaticAtomTable; + gStaticAtomTable = nullptr; + +#ifdef NS_FREE_PERMANENT_DATA + // Do a final GC to satisfy leak checking. We skip this step in release + // builds. + { + MutexAutoLock lock(*gAtomTableLock); + DynamicAtom::GCAtomTableLocked(lock, DynamicAtom::GCKind::Shutdown); + } +#endif + + delete gAtomTable; + gAtomTable = nullptr; + delete gAtomTableLock; + gAtomTableLock = nullptr; +} + +void +NS_SizeOfAtomTablesIncludingThis(MallocSizeOf aMallocSizeOf, + size_t* aMain, size_t* aStatic) +{ + MutexAutoLock lock(*gAtomTableLock); + *aMain = gAtomTable->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (auto iter = gAtomTable->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<AtomTableEntry*>(iter.Get()); + *aMain += entry->mAtom->SizeOfIncludingThis(aMallocSizeOf); + } + + // The atoms pointed to by gStaticAtomTable are also pointed to by gAtomTable, + // and they're measured by the loop above. So no need to measure them here. + *aStatic = gStaticAtomTable + ? gStaticAtomTable->ShallowSizeOfIncludingThis(aMallocSizeOf) + : 0; +} + +static inline AtomTableEntry* +GetAtomHashEntry(const char* aString, uint32_t aLength, uint32_t* aHashOut) +{ + gAtomTableLock->AssertCurrentThreadOwns(); + AtomTableKey key(aString, aLength, aHashOut); + // This is an infallible add. + return static_cast<AtomTableEntry*>(gAtomTable->Add(&key)); +} + +static inline AtomTableEntry* +GetAtomHashEntry(const char16_t* aString, uint32_t aLength, uint32_t* aHashOut) +{ + gAtomTableLock->AssertCurrentThreadOwns(); + AtomTableKey key(aString, aLength, aHashOut); + // This is an infallible add. + return static_cast<AtomTableEntry*>(gAtomTable->Add(&key)); +} + +void +RegisterStaticAtoms(const nsStaticAtom* aAtoms, uint32_t aAtomCount) +{ + MutexAutoLock lock(*gAtomTableLock); + + MOZ_RELEASE_ASSERT(!gStaticAtomTableSealed, + "Atom table has already been sealed!"); + + if (!gStaticAtomTable) { + gStaticAtomTable = new StaticAtomTable(); + } + + for (uint32_t i = 0; i < aAtomCount; ++i) { + nsStringBuffer* stringBuffer = aAtoms[i].mStringBuffer; + nsIAtom** atomp = aAtoms[i].mAtom; + + MOZ_ASSERT(nsCRT::IsAscii(static_cast<char16_t*>(stringBuffer->Data()))); + + uint32_t stringLen = stringBuffer->StorageSize() / sizeof(char16_t) - 1; + + uint32_t hash; + AtomTableEntry* he = + GetAtomHashEntry(static_cast<char16_t*>(stringBuffer->Data()), + stringLen, &hash); + + nsIAtom* atom = he->mAtom; + if (atom) { + // Disallow creating a dynamic atom, and then later, while the + // dynamic atom is still alive, registering that same atom as a + // static atom. It causes subtle bugs, and we're programming in + // C++ here, not Smalltalk. + if (!atom->IsStaticAtom()) { + nsAutoCString name; + atom->ToUTF8String(name); + MOZ_CRASH_UNSAFE_PRINTF( + "Static atom registration for %s should be pushed back", name.get()); + } + } else { + atom = new StaticAtom(stringBuffer, stringLen, hash); + he->mAtom = atom; + } + *atomp = atom; + + if (!gStaticAtomTableSealed) { + StaticAtomEntry* entry = + gStaticAtomTable->PutEntry(nsDependentAtomString(atom)); + MOZ_ASSERT(atom->IsStaticAtom()); + entry->mAtom = static_cast<StaticAtom*>(atom); + } + } +} + +already_AddRefed<nsIAtom> +NS_Atomize(const char* aUTF8String) +{ + return NS_Atomize(nsDependentCString(aUTF8String)); +} + +already_AddRefed<nsIAtom> +NS_Atomize(const nsACString& aUTF8String) +{ + MutexAutoLock lock(*gAtomTableLock); + uint32_t hash; + AtomTableEntry* he = GetAtomHashEntry(aUTF8String.Data(), + aUTF8String.Length(), + &hash); + + if (he->mAtom) { + nsCOMPtr<nsIAtom> atom = he->mAtom; + + return atom.forget(); + } + + // This results in an extra addref/release of the nsStringBuffer. + // Unfortunately there doesn't seem to be any APIs to avoid that. + // Actually, now there is, sort of: ForgetSharedBuffer. + nsString str; + CopyUTF8toUTF16(aUTF8String, str); + RefPtr<DynamicAtom> atom = DynamicAtom::Create(str, hash); + + he->mAtom = atom; + + return atom.forget(); +} + +already_AddRefed<nsIAtom> +NS_Atomize(const char16_t* aUTF16String) +{ + return NS_Atomize(nsDependentString(aUTF16String)); +} + +already_AddRefed<nsIAtom> +NS_Atomize(const nsAString& aUTF16String) +{ + MutexAutoLock lock(*gAtomTableLock); + uint32_t hash; + AtomTableEntry* he = GetAtomHashEntry(aUTF16String.Data(), + aUTF16String.Length(), + &hash); + + if (he->mAtom) { + nsCOMPtr<nsIAtom> atom = he->mAtom; + + return atom.forget(); + } + + RefPtr<DynamicAtom> atom = DynamicAtom::Create(aUTF16String, hash); + he->mAtom = atom; + + return atom.forget(); +} + +nsrefcnt +NS_GetNumberOfAtoms(void) +{ + DynamicAtom::GCAtomTable(); // Trigger a GC so that we return a deterministic result. + MutexAutoLock lock(*gAtomTableLock); + return gAtomTable->EntryCount(); +} + +nsIAtom* +NS_GetStaticAtom(const nsAString& aUTF16String) +{ + NS_PRECONDITION(gStaticAtomTable, "Static atom table not created yet."); + NS_PRECONDITION(gStaticAtomTableSealed, "Static atom table not sealed yet."); + StaticAtomEntry* entry = gStaticAtomTable->GetEntry(aUTF16String); + return entry ? entry->mAtom : nullptr; +} + +void +NS_SealStaticAtomTable() +{ + gStaticAtomTableSealed = true; +} diff --git a/xpcom/ds/nsAtomTable.h b/xpcom/ds/nsAtomTable.h new file mode 100644 index 000000000..89e5792f7 --- /dev/null +++ b/xpcom/ds/nsAtomTable.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsAtomTable_h__ +#define nsAtomTable_h__ + +#include "mozilla/MemoryReporting.h" +#include <stddef.h> + +void NS_InitAtomTable(); +void NS_ShutdownAtomTable(); + +void NS_SizeOfAtomTablesIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + size_t* aMain, size_t* aStatic); + +#endif // nsAtomTable_h__ diff --git a/xpcom/ds/nsCRT.cpp b/xpcom/ds/nsCRT.cpp new file mode 100644 index 000000000..0d11a8c26 --- /dev/null +++ b/xpcom/ds/nsCRT.cpp @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + + +/** + * MODULE NOTES: + * @update gess7/30/98 + * + * Much as I hate to do it, we were using string compares wrong. + * Often, programmers call functions like strcmp(s1,s2), and pass + * one or more null strings. Rather than blow up on these, I've + * added quick checks to ensure that cases like this don't cause + * us to fail. + * + * In general, if you pass a null into any of these string compare + * routines, we simply return 0. + */ + + +#include "nsCRT.h" +#include "nsDebug.h" + +//---------------------------------------------------------------------- + + +//////////////////////////////////////////////////////////////////////////////// +// My lovely strtok routine + +#define IS_DELIM(m, c) ((m)[(c) >> 3] & (1 << ((c) & 7))) +#define SET_DELIM(m, c) ((m)[(c) >> 3] |= (1 << ((c) & 7))) +#define DELIM_TABLE_SIZE 32 + +char* +nsCRT::strtok(char* aString, const char* aDelims, char** aNewStr) +{ + NS_ASSERTION(aString, + "Unlike regular strtok, the first argument cannot be null."); + + char delimTable[DELIM_TABLE_SIZE]; + uint32_t i; + char* result; + char* str = aString; + + for (i = 0; i < DELIM_TABLE_SIZE; ++i) { + delimTable[i] = '\0'; + } + + for (i = 0; aDelims[i]; i++) { + SET_DELIM(delimTable, static_cast<uint8_t>(aDelims[i])); + } + NS_ASSERTION(aDelims[i] == '\0', "too many delimiters"); + + // skip to beginning + while (*str && IS_DELIM(delimTable, static_cast<uint8_t>(*str))) { + str++; + } + result = str; + + // fix up the end of the token + while (*str) { + if (IS_DELIM(delimTable, static_cast<uint8_t>(*str))) { + *str++ = '\0'; + break; + } + str++; + } + *aNewStr = str; + + return str == result ? nullptr : result; +} + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Compare unichar string ptrs, stopping at the 1st null + * NOTE: If both are null, we return 0. + * NOTE: We terminate the search upon encountering a nullptr + * + * @update gess 11/10/99 + * @param s1 and s2 both point to unichar strings + * @return 0 if they match, -1 if s1<s2; 1 if s1>s2 + */ +int32_t +nsCRT::strcmp(const char16_t* aStr1, const char16_t* aStr2) +{ + if (aStr1 && aStr2) { + for (;;) { + char16_t c1 = *aStr1++; + char16_t c2 = *aStr2++; + if (c1 != c2) { + if (c1 < c2) { + return -1; + } + return 1; + } + if (c1 == 0 || c2 == 0) { + break; + } + } + } else { + if (aStr1) { // aStr2 must have been null + return -1; + } + if (aStr2) { // aStr1 must have been null + return 1; + } + } + return 0; +} + +/** + * Compare unichar string ptrs, stopping at the 1st null or nth char. + * NOTE: If either is null, we return 0. + * NOTE: We DO NOT terminate the search upon encountering nullptr's before N + * + * @update gess 11/10/99 + * @param s1 and s2 both point to unichar strings + * @return 0 if they match, -1 if s1<s2; 1 if s1>s2 + */ +int32_t +nsCRT::strncmp(const char16_t* aStr1, const char16_t* aStr2, uint32_t aNum) +{ + if (aStr1 && aStr2) { + if (aNum != 0) { + do { + char16_t c1 = *aStr1++; + char16_t c2 = *aStr2++; + if (c1 != c2) { + if (c1 < c2) { + return -1; + } + return 1; + } + } while (--aNum != 0); + } + } + return 0; +} + +const char* +nsCRT::memmem(const char* aHaystack, uint32_t aHaystackLen, + const char* aNeedle, uint32_t aNeedleLen) +{ + // Sanity checking + if (!(aHaystack && aNeedle && aHaystackLen && aNeedleLen && + aNeedleLen <= aHaystackLen)) { + return nullptr; + } + +#ifdef HAVE_MEMMEM + return (const char*)::memmem(aHaystack, aHaystackLen, aNeedle, aNeedleLen); +#else + // No memmem means we need to roll our own. This isn't really optimized + // for performance ... if that becomes an issue we can take some inspiration + // from the js string compare code in jsstr.cpp + for (uint32_t i = 0; i < aHaystackLen - aNeedleLen; i++) { + if (!memcmp(aHaystack + i, aNeedle, aNeedleLen)) { + return aHaystack + i; + } + } +#endif + return nullptr; +} + +// This should use NSPR but NSPR isn't exporting its PR_strtoll function +// Until then... +int64_t +nsCRT::atoll(const char* aStr) +{ + if (!aStr) { + return 0; + } + + int64_t ll = 0; + + while (*aStr && *aStr >= '0' && *aStr <= '9') { + ll *= 10; + ll += *aStr - '0'; + aStr++; + } + + return ll; +} + diff --git a/xpcom/ds/nsCRT.h b/xpcom/ds/nsCRT.h new file mode 100644 index 000000000..6fad83d6d --- /dev/null +++ b/xpcom/ds/nsCRT.h @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsCRT_h___ +#define nsCRT_h___ + +#include <stdlib.h> +#include <ctype.h> +#include "plstr.h" +#include "nscore.h" +#include "nsCRTGlue.h" + +#if defined(XP_WIN) +# define NS_LINEBREAK "\015\012" +# define NS_LINEBREAK_LEN 2 +#else +# ifdef XP_UNIX +# define NS_LINEBREAK "\012" +# define NS_LINEBREAK_LEN 1 +# endif /* XP_UNIX */ +#endif /* XP_WIN */ + +extern const char16_t kIsoLatin1ToUCS2[256]; + +// This macro can be used in a class declaration for classes that want +// to ensure that their instance memory is zeroed. +#define NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW \ + void* operator new(size_t sz) CPP_THROW_NEW { \ + void* rv = ::operator new(sz); \ + if (rv) { \ + memset(rv, 0, sz); \ + } \ + return rv; \ + } \ + void operator delete(void* ptr) { \ + ::operator delete(ptr); \ + } + +// This macro works with the next macro to declare a non-inlined +// version of the above. +#define NS_DECL_ZEROING_OPERATOR_NEW \ + void* operator new(size_t sz) CPP_THROW_NEW; \ + void operator delete(void* ptr); + +#define NS_IMPL_ZEROING_OPERATOR_NEW(_class) \ + void* _class::operator new(size_t sz) CPP_THROW_NEW { \ + void* rv = ::operator new(sz); \ + if (rv) { \ + memset(rv, 0, sz); \ + } \ + return rv; \ + } \ + void _class::operator delete(void* ptr) { \ + ::operator delete(ptr); \ + } + +// Freeing helper +#define CRTFREEIF(x) if (x) { nsCRT::free(x); x = 0; } + +/// This is a wrapper class around all the C runtime functions. + +class nsCRT +{ +public: + enum + { + LF = '\n' /* Line Feed */, + VTAB = '\v' /* Vertical Tab */, + CR = '\r' /* Carriage Return */ + }; + + /// String comparison. + static int32_t strcmp(const char* aStr1, const char* aStr2) + { + return int32_t(PL_strcmp(aStr1, aStr2)); + } + + static int32_t strncmp(const char* aStr1, const char* aStr2, + uint32_t aMaxLen) + { + return int32_t(PL_strncmp(aStr1, aStr2, aMaxLen)); + } + + /// Case-insensitive string comparison. + static int32_t strcasecmp(const char* aStr1, const char* aStr2) + { + return int32_t(PL_strcasecmp(aStr1, aStr2)); + } + + /// Case-insensitive string comparison with length + static int32_t strncasecmp(const char* aStr1, const char* aStr2, + uint32_t aMaxLen) + { + int32_t result = int32_t(PL_strncasecmp(aStr1, aStr2, aMaxLen)); + //Egads. PL_strncasecmp is returning *very* negative numbers. + //Some folks expect -1,0,1, so let's temper its enthusiasm. + if (result < 0) { + result = -1; + } + return result; + } + + static int32_t strncmp(const char* aStr1, const char* aStr2, int32_t aMaxLen) + { + // inline the first test (assumes strings are not null): + int32_t diff = + ((const unsigned char*)aStr1)[0] - ((const unsigned char*)aStr2)[0]; + if (diff != 0) { + return diff; + } + return int32_t(PL_strncmp(aStr1, aStr2, unsigned(aMaxLen))); + } + + /** + + How to use this fancy (thread-safe) version of strtok: + + void main(void) { + printf("%s\n\nTokens:\n", string); + // Establish string and get the first token: + char* newStr; + token = nsCRT::strtok(string, seps, &newStr); + while (token != nullptr) { + // While there are tokens in "string" + printf(" %s\n", token); + // Get next token: + token = nsCRT::strtok(newStr, seps, &newStr); + } + } + * WARNING - STRTOK WHACKS str THE FIRST TIME IT IS CALLED * + * MAKE A COPY OF str IF YOU NEED TO USE IT AFTER strtok() * + */ + static char* strtok(char* aStr, const char* aDelims, char** aNewStr); + + /// Like strcmp except for ucs2 strings + static int32_t strcmp(const char16_t* aStr1, const char16_t* aStr2); + /// Like strcmp except for ucs2 strings + static int32_t strncmp(const char16_t* aStr1, const char16_t* aStr2, + uint32_t aMaxLen); + + // The GNU libc has memmem, which is strstr except for binary data + // This is our own implementation that uses memmem on platforms + // where it's available. + static const char* memmem(const char* aHaystack, uint32_t aHaystackLen, + const char* aNeedle, uint32_t aNeedleLen); + + // String to longlong + static int64_t atoll(const char* aStr); + + static char ToUpper(char aChar) { return NS_ToUpper(aChar); } + static char ToLower(char aChar) { return NS_ToLower(aChar); } + + static bool IsUpper(char aChar) { return NS_IsUpper(aChar); } + static bool IsLower(char aChar) { return NS_IsLower(aChar); } + + static bool IsAscii(char16_t aChar) { return NS_IsAscii(aChar); } + static bool IsAscii(const char16_t* aString) { return NS_IsAscii(aString); } + static bool IsAsciiAlpha(char16_t aChar) { return NS_IsAsciiAlpha(aChar); } + static bool IsAsciiDigit(char16_t aChar) { return NS_IsAsciiDigit(aChar); } + static bool IsAsciiSpace(char16_t aChar) { return NS_IsAsciiWhitespace(aChar); } + static bool IsAscii(const char* aString) { return NS_IsAscii(aString); } + static bool IsAscii(const char* aString, uint32_t aLength) + { + return NS_IsAscii(aString, aLength); + } +}; + + +inline bool +NS_IS_SPACE(char16_t aChar) +{ + return ((int(aChar) & 0x7f) == int(aChar)) && isspace(int(aChar)); +} + +#define NS_IS_CNTRL(i) ((((unsigned int) (i)) > 0x7f) ? (int) 0 : iscntrl(i)) +#define NS_IS_DIGIT(i) ((((unsigned int) (i)) > 0x7f) ? (int) 0 : isdigit(i)) +#if defined(XP_WIN) +#define NS_IS_ALPHA(VAL) (isascii((int)(VAL)) && isalpha((int)(VAL))) +#else +#define NS_IS_ALPHA(VAL) ((((unsigned int) (VAL)) > 0x7f) ? (int) 0 : isalpha((int)(VAL))) +#endif + + +#endif /* nsCRT_h___ */ diff --git a/xpcom/ds/nsCharSeparatedTokenizer.h b/xpcom/ds/nsCharSeparatedTokenizer.h new file mode 100644 index 000000000..0e24d9d3e --- /dev/null +++ b/xpcom/ds/nsCharSeparatedTokenizer.h @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 __nsCharSeparatedTokenizer_h +#define __nsCharSeparatedTokenizer_h + +#include "mozilla/RangedPtr.h" + +#include "nsDependentSubstring.h" +#include "nsCRT.h" + +/** + * This parses a SeparatorChar-separated string into tokens. + * Whitespace surrounding tokens is not treated as part of tokens, however + * whitespace inside a token is. If the final token is the empty string, it is + * not returned. + * + * Some examples, with SeparatorChar = ',': + * + * "foo, bar, baz" -> "foo" "bar" "baz" + * "foo,bar,baz" -> "foo" "bar" "baz" + * "foo , bar hi , baz" -> "foo" "bar hi" "baz" + * "foo, ,bar,baz" -> "foo" "" "bar" "baz" + * "foo,,bar,baz" -> "foo" "" "bar" "baz" + * "foo,bar,baz," -> "foo" "bar" "baz" + * + * The function used for whitespace detection is a template argument. + * By default, it is NS_IsAsciiWhitespace. + */ +template<typename DependentSubstringType, bool IsWhitespace(char16_t)> +class nsTCharSeparatedTokenizer +{ + typedef typename DependentSubstringType::char_type CharType; + typedef typename DependentSubstringType::substring_type SubstringType; + +public: + // Flags -- only one for now. If we need more, they should be defined to + // be 1 << 1, 1 << 2, etc. (They're masks, and aFlags is a bitfield.) + enum + { + SEPARATOR_OPTIONAL = 1 + }; + + nsTCharSeparatedTokenizer(const SubstringType& aSource, + CharType aSeparatorChar, + uint32_t aFlags = 0) + : mIter(aSource.Data(), aSource.Length()) + , mEnd(aSource.Data() + aSource.Length(), aSource.Data(), + aSource.Length()) + , mSeparatorChar(aSeparatorChar) + , mWhitespaceBeforeFirstToken(false) + , mWhitespaceAfterCurrentToken(false) + , mSeparatorAfterCurrentToken(false) + , mSeparatorOptional(aFlags & SEPARATOR_OPTIONAL) + { + // Skip initial whitespace + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceBeforeFirstToken = true; + ++mIter; + } + } + + /** + * Checks if any more tokens are available. + */ + bool hasMoreTokens() const + { + MOZ_ASSERT(mIter == mEnd || !IsWhitespace(*mIter), + "Should be at beginning of token if there is one"); + + return mIter < mEnd; + } + + /* + * Returns true if there is whitespace prior to the first token. + */ + bool whitespaceBeforeFirstToken() const + { + return mWhitespaceBeforeFirstToken; + } + + /* + * Returns true if there is a separator after the current token. + * Useful if you want to check whether the last token has a separator + * after it which may not be valid. + */ + bool separatorAfterCurrentToken() const + { + return mSeparatorAfterCurrentToken; + } + + /* + * Returns true if there is any whitespace after the current token. + */ + bool whitespaceAfterCurrentToken() const + { + return mWhitespaceAfterCurrentToken; + } + + /** + * Returns the next token. + */ + const DependentSubstringType nextToken() + { + mozilla::RangedPtr<const CharType> tokenStart = mIter; + mozilla::RangedPtr<const CharType> tokenEnd = mIter; + + MOZ_ASSERT(mIter == mEnd || !IsWhitespace(*mIter), + "Should be at beginning of token if there is one"); + + // Search until we hit separator or end (or whitespace, if a separator + // isn't required -- see clause with 'break' below). + while (mIter < mEnd && *mIter != mSeparatorChar) { + // Skip to end of the current word. + while (mIter < mEnd && + !IsWhitespace(*mIter) && *mIter != mSeparatorChar) { + ++mIter; + } + tokenEnd = mIter; + + // Skip whitespace after the current word. + mWhitespaceAfterCurrentToken = false; + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceAfterCurrentToken = true; + ++mIter; + } + if (mSeparatorOptional) { + // We've hit (and skipped) whitespace, and that's sufficient to end + // our token, regardless of whether we've reached a SeparatorChar. + break; + } // (else, we'll keep looping until we hit mEnd or SeparatorChar) + } + + mSeparatorAfterCurrentToken = (mIter != mEnd && + *mIter == mSeparatorChar); + MOZ_ASSERT(mSeparatorOptional || + (mSeparatorAfterCurrentToken == (mIter < mEnd)), + "If we require a separator and haven't hit the end of " + "our string, then we shouldn't have left the loop " + "unless we hit a separator"); + + // Skip separator (and any whitespace after it), if we're at one. + if (mSeparatorAfterCurrentToken) { + ++mIter; + + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceAfterCurrentToken = true; + ++mIter; + } + } + + return Substring(tokenStart.get(), tokenEnd.get()); + } + +private: + mozilla::RangedPtr<const CharType> mIter; + const mozilla::RangedPtr<const CharType> mEnd; + CharType mSeparatorChar; + bool mWhitespaceBeforeFirstToken; + bool mWhitespaceAfterCurrentToken; + bool mSeparatorAfterCurrentToken; + bool mSeparatorOptional; +}; + +template<bool IsWhitespace(char16_t) = NS_IsAsciiWhitespace> +class nsCharSeparatedTokenizerTemplate + : public nsTCharSeparatedTokenizer<nsDependentSubstring, IsWhitespace> +{ +public: + nsCharSeparatedTokenizerTemplate(const nsSubstring& aSource, + char16_t aSeparatorChar, + uint32_t aFlags = 0) + : nsTCharSeparatedTokenizer<nsDependentSubstring, + IsWhitespace>(aSource, aSeparatorChar, aFlags) + { + } +}; + +typedef nsCharSeparatedTokenizerTemplate<> nsCharSeparatedTokenizer; + +template<bool IsWhitespace(char16_t) = NS_IsAsciiWhitespace> +class nsCCharSeparatedTokenizerTemplate + : public nsTCharSeparatedTokenizer<nsDependentCSubstring, IsWhitespace> +{ +public: + nsCCharSeparatedTokenizerTemplate(const nsCSubstring& aSource, + char aSeparatorChar, + uint32_t aFlags = 0) + : nsTCharSeparatedTokenizer<nsDependentCSubstring, + IsWhitespace>(aSource, aSeparatorChar, aFlags) + { + } +}; + +typedef nsCCharSeparatedTokenizerTemplate<> nsCCharSeparatedTokenizer; + +#endif /* __nsCharSeparatedTokenizer_h */ diff --git a/xpcom/ds/nsCheapSets.h b/xpcom/ds/nsCheapSets.h new file mode 100644 index 000000000..d75e60d20 --- /dev/null +++ b/xpcom/ds/nsCheapSets.h @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 __nsCheapSets_h__ +#define __nsCheapSets_h__ + +#include "nsTHashtable.h" +#include <stdint.h> + +enum nsCheapSetOperator +{ + OpNext = 0, // enumerator says continue + OpRemove = 1, // enumerator says remove and continue +}; + +/** + * A set that takes up minimal size when there are 0 or 1 entries in the set. + * Use for cases where sizes of 0 and 1 are even slightly common. + */ +template<typename EntryType> +class nsCheapSet +{ +public: + typedef typename EntryType::KeyType KeyType; + typedef nsCheapSetOperator (*Enumerator)(EntryType* aEntry, void* userArg); + + nsCheapSet() : mState(ZERO) {} + ~nsCheapSet() { Clear(); } + + /** + * Remove all entries. + */ + void Clear() + { + switch (mState) { + case ZERO: + break; + case ONE: + GetSingleEntry()->~EntryType(); + break; + case MANY: + delete mUnion.table; + break; + default: + NS_NOTREACHED("bogus state"); + break; + } + mState = ZERO; + } + + void Put(const KeyType aVal); + + void Remove(const KeyType aVal); + + bool Contains(const KeyType aVal) + { + switch (mState) { + case ZERO: + return false; + case ONE: + return GetSingleEntry()->KeyEquals(EntryType::KeyToPointer(aVal)); + case MANY: + return !!mUnion.table->GetEntry(aVal); + default: + NS_NOTREACHED("bogus state"); + return false; + } + } + + uint32_t EnumerateEntries(Enumerator aEnumFunc, void* aUserArg) + { + switch (mState) { + case ZERO: + return 0; + case ONE: + if (aEnumFunc(GetSingleEntry(), aUserArg) == OpRemove) { + GetSingleEntry()->~EntryType(); + mState = ZERO; + } + return 1; + case MANY: { + uint32_t n = mUnion.table->Count(); + for (auto iter = mUnion.table->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<EntryType*>(iter.Get()); + if (aEnumFunc(entry, aUserArg) == OpRemove) { + iter.Remove(); + } + } + return n; + } + default: + NS_NOTREACHED("bogus state"); + return 0; + } + } + +private: + EntryType* GetSingleEntry() + { + return reinterpret_cast<EntryType*>(&mUnion.singleEntry[0]); + } + + enum SetState + { + ZERO, + ONE, + MANY + }; + + union + { + nsTHashtable<EntryType>* table; + char singleEntry[sizeof(EntryType)]; + } mUnion; + enum SetState mState; +}; + +template<typename EntryType> +void +nsCheapSet<EntryType>::Put(const KeyType aVal) +{ + switch (mState) { + case ZERO: + new (GetSingleEntry()) EntryType(EntryType::KeyToPointer(aVal)); + mState = ONE; + return; + case ONE: { + nsTHashtable<EntryType>* table = new nsTHashtable<EntryType>(); + EntryType* entry = GetSingleEntry(); + table->PutEntry(entry->GetKey()); + entry->~EntryType(); + mUnion.table = table; + mState = MANY; + } + MOZ_FALLTHROUGH; + + case MANY: + mUnion.table->PutEntry(aVal); + return; + default: + NS_NOTREACHED("bogus state"); + return; + } +} + +template<typename EntryType> +void +nsCheapSet<EntryType>::Remove(const KeyType aVal) +{ + switch (mState) { + case ZERO: + break; + case ONE: + if (Contains(aVal)) { + GetSingleEntry()->~EntryType(); + mState = ZERO; + } + break; + case MANY: + mUnion.table->RemoveEntry(aVal); + break; + default: + NS_NOTREACHED("bogus state"); + break; + } +} + +#endif diff --git a/xpcom/ds/nsExpirationTracker.h b/xpcom/ds/nsExpirationTracker.h new file mode 100644 index 000000000..2e77895db --- /dev/null +++ b/xpcom/ds/nsExpirationTracker.h @@ -0,0 +1,570 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 NSEXPIRATIONTRACKER_H_ +#define NSEXPIRATIONTRACKER_H_ + +#include "mozilla/Logging.h" +#include "nsTArray.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/Services.h" + +/** + * Data used to track the expiration state of an object. We promise that this + * is 32 bits so that objects that includes this as a field can pad and align + * efficiently. + */ +struct nsExpirationState +{ + enum + { + NOT_TRACKED = (1U << 4) - 1, + MAX_INDEX_IN_GENERATION = (1U << 28) - 1 + }; + + nsExpirationState() : mGeneration(NOT_TRACKED) {} + bool IsTracked() { return mGeneration != NOT_TRACKED; } + + /** + * The generation that this object belongs to, or NOT_TRACKED. + */ + uint32_t mGeneration:4; + uint32_t mIndexInGeneration:28; +}; + +/** + * ExpirationTracker classes: + * - ExpirationTrackerImpl (Thread-safe class) + * - nsExpirationTracker (Main-thread only class) + * + * These classes can track the lifetimes and usage of a large number of + * objects, and send a notification some window of time after a live object was + * last used. This is very useful when you manage a large number of objects + * and want to flush some after they haven't been used for a while. + * nsExpirationTracker is designed to be very space and time efficient. + * + * The type parameter T is the object type that we will track pointers to. T + * must include an accessible method GetExpirationState() that returns a + * pointer to an nsExpirationState associated with the object (preferably, + * stored in a field of the object). + * + * The parameter K is the number of generations that will be used. Increasing + * the number of generations narrows the window within which we promise + * to fire notifications, at a slight increase in space cost for the tracker. + * We require 2 <= K <= nsExpirationState::NOT_TRACKED (currently 15). + * + * To use this class, you need to inherit from it and override the + * NotifyExpired() method. + * + * The approach is to track objects in K generations. When an object is accessed + * it moves from its current generation to the newest generation. Generations + * are stored in a cyclic array; when a timer interrupt fires, we advance + * the current generation pointer to effectively age all objects very efficiently. + * By storing information in each object about its generation and index within its + * generation array, we make removal of objects from a generation very cheap. + * + * Future work: + * -- Add a method to change the timer period? + */ + +/** + * Base class for ExiprationTracker implementations. + * + * nsExpirationTracker class below is a specialized class to be inherited by the + * instances to be accessed only on main-thread. + * + * For creating a thread-safe tracker, you can define a subclass inheriting this + * base class and specialize the Mutex and AutoLock to be used. + * + */ +template<typename T, uint32_t K, typename Mutex, typename AutoLock> +class ExpirationTrackerImpl +{ +public: + /** + * Initialize the tracker. + * @param aTimerPeriod the timer period in milliseconds. The guarantees + * provided by the tracker are defined in terms of this period. If the + * period is zero, then we don't use a timer and rely on someone calling + * AgeOneGenerationLocked explicitly. + */ + ExpirationTrackerImpl(uint32_t aTimerPeriod, const char* aName) + : mTimerPeriod(aTimerPeriod) + , mNewestGeneration(0) + , mInAgeOneGeneration(false) + , mName(aName) + { + static_assert(K >= 2 && K <= nsExpirationState::NOT_TRACKED, + "Unsupported number of generations (must be 2 <= K <= 15)"); + MOZ_ASSERT(NS_IsMainThread()); + mObserver = new ExpirationTrackerObserver(); + mObserver->Init(this); + } + + virtual ~ExpirationTrackerImpl() + { + MOZ_ASSERT(NS_IsMainThread()); + if (mTimer) { + mTimer->Cancel(); + } + mObserver->Destroy(); + } + + /** + * Add an object to be tracked. It must not already be tracked. It will + * be added to the newest generation, i.e., as if it was just used. + * @return an error on out-of-memory + */ + nsresult AddObjectLocked(T* aObj, const AutoLock& aAutoLock) + { + nsExpirationState* state = aObj->GetExpirationState(); + NS_ASSERTION(!state->IsTracked(), + "Tried to add an object that's already tracked"); + nsTArray<T*>& generation = mGenerations[mNewestGeneration]; + uint32_t index = generation.Length(); + if (index > nsExpirationState::MAX_INDEX_IN_GENERATION) { + NS_WARNING("More than 256M elements tracked, this is probably a problem"); + return NS_ERROR_OUT_OF_MEMORY; + } + if (index == 0) { + // We might need to start the timer + nsresult rv = CheckStartTimerLocked(aAutoLock); + if (NS_FAILED(rv)) { + return rv; + } + } + if (!generation.AppendElement(aObj)) { + return NS_ERROR_OUT_OF_MEMORY; + } + state->mGeneration = mNewestGeneration; + state->mIndexInGeneration = index; + return NS_OK; + } + + /** + * Remove an object from the tracker. It must currently be tracked. + */ + void RemoveObjectLocked(T* aObj, const AutoLock& aAutoLock) + { + nsExpirationState* state = aObj->GetExpirationState(); + NS_ASSERTION(state->IsTracked(), "Tried to remove an object that's not tracked"); + nsTArray<T*>& generation = mGenerations[state->mGeneration]; + uint32_t index = state->mIndexInGeneration; + NS_ASSERTION(generation.Length() > index && + generation[index] == aObj, "Object is lying about its index"); + // Move the last object to fill the hole created by removing aObj + uint32_t last = generation.Length() - 1; + T* lastObj = generation[last]; + generation[index] = lastObj; + lastObj->GetExpirationState()->mIndexInGeneration = index; + generation.RemoveElementAt(last); + state->mGeneration = nsExpirationState::NOT_TRACKED; + // We do not check whether we need to stop the timer here. The timer + // will check that itself next time it fires. Checking here would not + // be efficient since we'd need to track all generations. Also we could + // thrash by incessantly creating and destroying timers if someone + // kept adding and removing an object from the tracker. + } + + /** + * Notify that an object has been used. + * @return an error if we lost the object from the tracker... + */ + nsresult MarkUsedLocked(T* aObj, const AutoLock& aAutoLock) + { + nsExpirationState* state = aObj->GetExpirationState(); + if (mNewestGeneration == state->mGeneration) { + return NS_OK; + } + RemoveObjectLocked(aObj, aAutoLock); + return AddObjectLocked(aObj, aAutoLock); + } + + /** + * The timer calls this, but it can also be manually called if you want + * to age objects "artifically". This can result in calls to NotifyExpiredLocked. + */ + void AgeOneGenerationLocked(const AutoLock& aAutoLock) + { + if (mInAgeOneGeneration) { + NS_WARNING("Can't reenter AgeOneGeneration from NotifyExpired"); + return; + } + + mInAgeOneGeneration = true; + uint32_t reapGeneration = + mNewestGeneration > 0 ? mNewestGeneration - 1 : K - 1; + nsTArray<T*>& generation = mGenerations[reapGeneration]; + // The following is rather tricky. We have to cope with objects being + // removed from this generation either because of a call to RemoveObject + // (or indirectly via MarkUsedLocked) inside NotifyExpiredLocked. Fortunately + // no objects can be added to this generation because it's not the newest + // generation. We depend on the fact that RemoveObject can only cause + // the indexes of objects in this generation to *decrease*, not increase. + // So if we start from the end and work our way backwards we are guaranteed + // to see each object at least once. + size_t index = generation.Length(); + for (;;) { + // Objects could have been removed so index could be outside + // the array + index = XPCOM_MIN(index, generation.Length()); + if (index == 0) { + break; + } + --index; + NotifyExpiredLocked(generation[index], aAutoLock); + } + // Any leftover objects from reapGeneration just end up in the new + // newest-generation. This is bad form, though, so warn if there are any. + if (!generation.IsEmpty()) { + NS_WARNING("Expired objects were not removed or marked used"); + } + // Free excess memory used by the generation array, since we probably + // just removed most or all of its elements. + generation.Compact(); + mNewestGeneration = reapGeneration; + mInAgeOneGeneration = false; + } + + /** + * This just calls AgeOneGenerationLocked K times. Under normal circumstances + * this will result in all objects getting NotifyExpiredLocked called on them, + * but if NotifyExpiredLocked itself marks some objects as used, then those + * objects might not expire. This would be a good thing to call if we get into + * a critically-low memory situation. + */ + void AgeAllGenerationsLocked(const AutoLock& aAutoLock) + { + uint32_t i; + for (i = 0; i < K; ++i) { + AgeOneGenerationLocked(aAutoLock); + } + } + + class Iterator + { + private: + ExpirationTrackerImpl<T, K, Mutex, AutoLock>* mTracker; + uint32_t mGeneration; + uint32_t mIndex; + public: + Iterator(ExpirationTrackerImpl<T, K, Mutex, AutoLock>* aTracker, + AutoLock& aAutoLock) + : mTracker(aTracker) + , mGeneration(0) + , mIndex(0) + { + } + + T* Next() + { + while (mGeneration < K) { + nsTArray<T*>* generation = &mTracker->mGenerations[mGeneration]; + if (mIndex < generation->Length()) { + ++mIndex; + return (*generation)[mIndex - 1]; + } + ++mGeneration; + mIndex = 0; + } + return nullptr; + } + }; + + friend class Iterator; + + bool IsEmptyLocked(const AutoLock& aAutoLock) + { + for (uint32_t i = 0; i < K; ++i) { + if (!mGenerations[i].IsEmpty()) { + return false; + } + } + return true; + } + +protected: + /** + * This must be overridden to catch notifications. It is called whenever + * we detect that an object has not been used for at least (K-1)*mTimerPeriod + * milliseconds. If timer events are not delayed, it will be called within + * roughly K*mTimerPeriod milliseconds after the last use. + * (Unless AgeOneGenerationLocked or AgeAllGenerationsLocked have been called + * to accelerate the aging process.) + * + * NOTE: These bounds ignore delays in timer firings due to actual work being + * performed by the browser. We use a slack timer so there is always at least + * mTimerPeriod milliseconds between firings, which gives us (K-1)*mTimerPeriod + * as a pretty solid lower bound. The upper bound is rather loose, however. + * If the maximum amount by which any given timer firing is delayed is D, then + * the upper bound before NotifyExpiredLocked is called is K*(mTimerPeriod + D). + * + * The NotifyExpiredLocked call is expected to remove the object from the tracker, + * but it need not. The object (or other objects) could be "resurrected" + * by calling MarkUsedLocked() on them, or they might just not be removed. + * Any objects left over that have not been resurrected or removed + * are placed in the new newest-generation, but this is considered "bad form" + * and should be avoided (we'll issue a warning). (This recycling counts + * as "a use" for the purposes of the expiry guarantee above...) + * + * For robustness and simplicity, we allow objects to be notified more than + * once here in the same timer tick. + */ + virtual void NotifyExpiredLocked(T*, const AutoLock&) = 0; + + virtual Mutex& GetMutex() = 0; + +private: + class ExpirationTrackerObserver; + RefPtr<ExpirationTrackerObserver> mObserver; + nsTArray<T*> mGenerations[K]; + nsCOMPtr<nsITimer> mTimer; + uint32_t mTimerPeriod; + uint32_t mNewestGeneration; + bool mInAgeOneGeneration; + const char* const mName; // Used for timer firing profiling. + + /** + * Whenever "memory-pressure" is observed, it calls AgeAllGenerationsLocked() + * to minimize memory usage. + */ + class ExpirationTrackerObserver final : public nsIObserver + { + public: + void Init(ExpirationTrackerImpl<T, K, Mutex, AutoLock>* aObj) + { + mOwner = aObj; + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "memory-pressure", false); + } + } + void Destroy() + { + mOwner = nullptr; + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "memory-pressure"); + } + } + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + private: + ExpirationTrackerImpl<T, K, Mutex, AutoLock>* mOwner; + }; + + void HandleLowMemory() { + AutoLock lock(GetMutex()); + AgeAllGenerationsLocked(lock); + } + + void HandleTimeout() { + AutoLock lock(GetMutex()); + AgeOneGenerationLocked(lock); + // Cancel the timer if we have no objects to track + if (IsEmptyLocked(lock)) { + mTimer->Cancel(); + mTimer = nullptr; + } + } + + static void TimerCallback(nsITimer* aTimer, void* aThis) + { + ExpirationTrackerImpl* tracker = static_cast<ExpirationTrackerImpl*>(aThis); + tracker->HandleTimeout(); + } + + nsresult CheckStartTimerLocked(const AutoLock& aAutoLock) + { + if (mTimer || !mTimerPeriod) { + return NS_OK; + } + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (!mTimer) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (!NS_IsMainThread()) { + // TimerCallback should always be run on the main thread to prevent races + // to the destruction of the tracker. + nsCOMPtr<nsIEventTarget> target = do_GetMainThread(); + NS_ENSURE_STATE(target); + mTimer->SetTarget(target); + } + mTimer->InitWithNamedFuncCallback(TimerCallback, this, mTimerPeriod, + nsITimer::TYPE_REPEATING_SLACK, mName); + return NS_OK; + } +}; + +namespace detail { + +class PlaceholderLock { +public: + void Lock() {} + void Unlock() {} +}; + +class PlaceholderAutoLock { +public: + explicit PlaceholderAutoLock(PlaceholderLock&) { } + ~PlaceholderAutoLock() = default; + +}; + +template<typename T, uint32_t K> +using SingleThreadedExpirationTracker = + ExpirationTrackerImpl<T, K, PlaceholderLock, PlaceholderAutoLock>; + +} // namespace detail + +template<typename T, uint32_t K> +class nsExpirationTracker : protected ::detail::SingleThreadedExpirationTracker<T, K> +{ + typedef ::detail::PlaceholderLock Lock; + typedef ::detail::PlaceholderAutoLock AutoLock; + + Lock mLock; + + AutoLock FakeLock() { + return AutoLock(mLock); + } + + Lock& GetMutex() override + { + return mLock; + } + + void NotifyExpiredLocked(T* aObject, const AutoLock&) override + { + NotifyExpired(aObject); + } + +protected: + virtual void NotifyExpired(T* aObj) = 0; + +public: + nsExpirationTracker(uint32_t aTimerPeriod, const char* aName) + : ::detail::SingleThreadedExpirationTracker<T, K>(aTimerPeriod, aName) + { } + + virtual ~nsExpirationTracker() + { } + + nsresult AddObject(T* aObj) + { + return this->AddObjectLocked(aObj, FakeLock()); + } + + void RemoveObject(T* aObj) + { + this->RemoveObjectLocked(aObj, FakeLock()); + } + + nsresult MarkUsed(T* aObj) + { + return this->MarkUsedLocked(aObj, FakeLock()); + } + + void AgeOneGeneration() + { + this->AgeOneGenerationLocked(FakeLock()); + } + + void AgeAllGenerations() + { + this->AgeAllGenerationsLocked(FakeLock()); + } + + class Iterator + { + private: + AutoLock mAutoLock; + typename ExpirationTrackerImpl<T, K, Lock, AutoLock>::Iterator mIterator; + public: + explicit Iterator(nsExpirationTracker<T, K>* aTracker) + : mAutoLock(aTracker->GetMutex()) + , mIterator(aTracker, mAutoLock) + { + } + + T* Next() + { + return mIterator.Next(); + } + }; + + friend class Iterator; + + bool IsEmpty() + { + return this->IsEmptyLocked(FakeLock()); + } +}; + +template<typename T, uint32_t K, typename Mutex, typename AutoLock> +NS_IMETHODIMP +ExpirationTrackerImpl<T, K, Mutex, AutoLock>:: +ExpirationTrackerObserver::Observe( + nsISupports* aSubject, const char* aTopic, const char16_t* aData) +{ + if (!strcmp(aTopic, "memory-pressure") && mOwner) { + mOwner->HandleLowMemory(); + } + return NS_OK; +} + +template<class T, uint32_t K, typename Mutex, typename AutoLock> +NS_IMETHODIMP_(MozExternalRefCountType) +ExpirationTrackerImpl<T, K, Mutex, AutoLock>:: +ExpirationTrackerObserver::AddRef(void) +{ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver); + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "ExpirationTrackerObserver", sizeof(*this)); + return mRefCnt; +} + +template<class T, uint32_t K, typename Mutex, typename AutoLock> +NS_IMETHODIMP_(MozExternalRefCountType) +ExpirationTrackerImpl<T, K, Mutex, AutoLock>:: +ExpirationTrackerObserver::Release(void) +{ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver); + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "ExpirationTrackerObserver"); + if (mRefCnt == 0) { + NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver); + mRefCnt = 1; /* stabilize */ + delete (this); + return 0; + } + return mRefCnt; +} + +template<class T, uint32_t K, typename Mutex, typename AutoLock> +NS_IMETHODIMP +ExpirationTrackerImpl<T, K, Mutex, AutoLock>:: +ExpirationTrackerObserver::QueryInterface( + REFNSIID aIID, void** aInstancePtr) +{ + NS_ASSERTION(aInstancePtr, + "QueryInterface requires a non-NULL destination!"); + nsresult rv = NS_ERROR_FAILURE; + NS_INTERFACE_TABLE(ExpirationTrackerObserver, nsIObserver) + return rv; +} + +#endif /*NSEXPIRATIONTRACKER_H_*/ diff --git a/xpcom/ds/nsHashPropertyBag.cpp b/xpcom/ds/nsHashPropertyBag.cpp new file mode 100644 index 000000000..6f9fc8dec --- /dev/null +++ b/xpcom/ds/nsHashPropertyBag.cpp @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsHashPropertyBag.h" +#include "nsArray.h" +#include "nsArrayEnumerator.h" +#include "nsIVariant.h" +#include "nsIProperty.h" +#include "nsVariant.h" +#include "mozilla/Attributes.h" + +/* + * nsHashPropertyBagBase implementation. + */ + +NS_IMETHODIMP +nsHashPropertyBagBase::HasKey(const nsAString& aName, bool* aResult) +{ + *aResult = mPropertyHash.Get(aName, nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::Get(const nsAString& aName, nsIVariant** aResult) +{ + if (!mPropertyHash.Get(aName, aResult)) { + *aResult = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetProperty(const nsAString& aName, nsIVariant** aResult) +{ + bool isFound = mPropertyHash.Get(aName, aResult); + if (!isFound) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetProperty(const nsAString& aName, nsIVariant* aValue) +{ + if (NS_WARN_IF(!aValue)) { + return NS_ERROR_INVALID_ARG; + } + + mPropertyHash.Put(aName, aValue); + + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::DeleteProperty(const nsAString& aName) +{ + // is it too much to ask for ns*Hashtable to return + // a boolean indicating whether RemoveEntry succeeded + // or not?!?! + bool isFound = mPropertyHash.Get(aName, nullptr); + if (!isFound) { + return NS_ERROR_FAILURE; + } + + // then from the hash + mPropertyHash.Remove(aName); + + return NS_OK; +} + + +// +// nsSimpleProperty class and impl; used for GetEnumerator +// + +class nsSimpleProperty final : public nsIProperty +{ + ~nsSimpleProperty() {} + +public: + nsSimpleProperty(const nsAString& aName, nsIVariant* aValue) + : mName(aName) + , mValue(aValue) + { + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTY +protected: + nsString mName; + nsCOMPtr<nsIVariant> mValue; +}; + +NS_IMPL_ISUPPORTS(nsSimpleProperty, nsIProperty) + +NS_IMETHODIMP +nsSimpleProperty::GetName(nsAString& aName) +{ + aName.Assign(mName); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleProperty::GetValue(nsIVariant** aValue) +{ + NS_IF_ADDREF(*aValue = mValue); + return NS_OK; +} + +// end nsSimpleProperty + +NS_IMETHODIMP +nsHashPropertyBagBase::GetEnumerator(nsISimpleEnumerator** aResult) +{ + nsCOMPtr<nsIMutableArray> propertyArray = nsArray::Create(); + if (!propertyArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) { + const nsAString& key = iter.Key(); + nsIVariant* data = iter.UserData(); + nsSimpleProperty* sprop = new nsSimpleProperty(key, data); + propertyArray->AppendElement(sprop, false); + } + + return NS_NewArrayEnumerator(aResult, propertyArray); +} + +#define IMPL_GETSETPROPERTY_AS(Name, Type) \ +NS_IMETHODIMP \ +nsHashPropertyBagBase::GetPropertyAs ## Name (const nsAString & prop, Type *_retval) \ +{ \ + nsIVariant* v = mPropertyHash.GetWeak(prop); \ + if (!v) \ + return NS_ERROR_NOT_AVAILABLE; \ + return v->GetAs ## Name(_retval); \ +} \ +\ +NS_IMETHODIMP \ +nsHashPropertyBagBase::SetPropertyAs ## Name (const nsAString & prop, Type value) \ +{ \ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); \ + var->SetAs ## Name(value); \ + return SetProperty(prop, var); \ +} + +IMPL_GETSETPROPERTY_AS(Int32, int32_t) +IMPL_GETSETPROPERTY_AS(Uint32, uint32_t) +IMPL_GETSETPROPERTY_AS(Int64, int64_t) +IMPL_GETSETPROPERTY_AS(Uint64, uint64_t) +IMPL_GETSETPROPERTY_AS(Double, double) +IMPL_GETSETPROPERTY_AS(Bool, bool) + + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsAString(const nsAString& aProp, + nsAString& aResult) +{ + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + return v->GetAsAString(aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsACString(const nsAString& aProp, + nsACString& aResult) +{ + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + return v->GetAsACString(aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsAUTF8String(const nsAString& aProp, + nsACString& aResult) +{ + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + return v->GetAsAUTF8String(aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsInterface(const nsAString& aProp, + const nsIID& aIID, + void** aResult) +{ + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + nsCOMPtr<nsISupports> val; + nsresult rv = v->GetAsISupports(getter_AddRefs(val)); + if (NS_FAILED(rv)) { + return rv; + } + if (!val) { + // We have a value, but it's null + *aResult = nullptr; + return NS_OK; + } + return val->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsAString(const nsAString& aProp, + const nsAString& aValue) +{ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsAString(aValue); + return SetProperty(aProp, var); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsACString(const nsAString& aProp, + const nsACString& aValue) +{ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsACString(aValue); + return SetProperty(aProp, var); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsAUTF8String(const nsAString& aProp, + const nsACString& aValue) +{ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsAUTF8String(aValue); + return SetProperty(aProp, var); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsInterface(const nsAString& aProp, + nsISupports* aValue) +{ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsISupports(aValue); + return SetProperty(aProp, var); +} + + +/* + * nsHashPropertyBag implementation. + */ + +NS_IMPL_ADDREF(nsHashPropertyBag) +NS_IMPL_RELEASE(nsHashPropertyBag) + +NS_INTERFACE_MAP_BEGIN(nsHashPropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2) +NS_INTERFACE_MAP_END + + +/* + * nsHashPropertyBagCC implementation. + */ + +NS_IMPL_CYCLE_COLLECTION(nsHashPropertyBagCC, mPropertyHash) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHashPropertyBagCC) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHashPropertyBagCC) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsHashPropertyBagCC) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2) +NS_INTERFACE_MAP_END diff --git a/xpcom/ds/nsHashPropertyBag.h b/xpcom/ds/nsHashPropertyBag.h new file mode 100644 index 000000000..e41c984ba --- /dev/null +++ b/xpcom/ds/nsHashPropertyBag.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsHashPropertyBag_h___ +#define nsHashPropertyBag_h___ + +#include "nsIVariant.h" +#include "nsIWritablePropertyBag.h" +#include "nsIWritablePropertyBag2.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsInterfaceHashtable.h" + +class nsHashPropertyBagBase + : public nsIWritablePropertyBag + , public nsIWritablePropertyBag2 +{ +public: + nsHashPropertyBagBase() {} + + NS_DECL_NSIPROPERTYBAG + NS_DECL_NSIPROPERTYBAG2 + + NS_DECL_NSIWRITABLEPROPERTYBAG + NS_DECL_NSIWRITABLEPROPERTYBAG2 + +protected: + // a hash table of string -> nsIVariant + nsInterfaceHashtable<nsStringHashKey, nsIVariant> mPropertyHash; +}; + +class nsHashPropertyBag : public nsHashPropertyBagBase +{ +public: + nsHashPropertyBag() {} + NS_DECL_THREADSAFE_ISUPPORTS + +protected: + virtual ~nsHashPropertyBag() {} +}; + +/* A cycle collected nsHashPropertyBag for main-thread-only use. */ +class nsHashPropertyBagCC final : public nsHashPropertyBagBase +{ +public: + nsHashPropertyBagCC() {} + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHashPropertyBagCC, + nsIWritablePropertyBag) +protected: + virtual ~nsHashPropertyBagCC() {} +}; + +#endif /* nsHashPropertyBag_h___ */ diff --git a/xpcom/ds/nsIArray.idl b/xpcom/ds/nsIArray.idl new file mode 100644 index 000000000..d591253e9 --- /dev/null +++ b/xpcom/ds/nsIArray.idl @@ -0,0 +1,91 @@ +/* -*- 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 nsISimpleEnumerator; + +/** + * nsIArray + * + * An indexed collection of elements. Provides basic functionality for + * retrieving elements at a specific position, searching for + * elements. Indexes are zero-based, such that the last element in the + * array is stored at the index length-1. + * + * For an array which can be modified, see nsIMutableArray below. + * + * Neither interface makes any attempt to protect the individual + * elements from modification. The convention is that the elements of + * the array should not be modified. Documentation within a specific + * interface should describe variations from this convention. + * + * It is also convention that if an interface provides access to an + * nsIArray, that the array should not be QueryInterfaced to an + * nsIMutableArray for modification. If the interface in question had + * intended the array to be modified, it would have returned an + * nsIMutableArray! + * + * null is a valid entry in the array, and as such any nsISupports + * parameters may be null, except where noted. + */ +[scriptable, uuid(114744d9-c369-456e-b55a-52fe52880d2d)] +interface nsIArray : nsISupports +{ + /** + * length + * + * number of elements in the array. + */ + readonly attribute unsigned long length; + + /** + * queryElementAt() + * + * Retrieve a specific element of the array, and QueryInterface it + * to the specified interface. null is a valid result for + * this method, but exceptions are thrown in other circumstances + * + * @param index position of element + * @param uuid the IID of the requested interface + * @param result the object, QI'd to the requested interface + * + * @throws NS_ERROR_NO_INTERFACE when an entry exists at the + * specified index, but the requested interface is not + * available. + * @throws NS_ERROR_ILLEGAL_VALUE when index > length-1 + * + */ + void queryElementAt(in unsigned long index, + in nsIIDRef uuid, + [iid_is(uuid), retval] out nsQIResult result); + + /** + * indexOf() + * + * Get the position of a specific element. Note that since null is + * a valid input, exceptions are used to indicate that an element + * is not found. + * + * @param startIndex The initial element to search in the array + * To start at the beginning, use 0 as the + * startIndex + * @param element The element you are looking for + * @returns a number >= startIndex which is the position of the + * element in the array. + * @throws NS_ERROR_FAILURE if the element was not in the array. + */ + unsigned long indexOf(in unsigned long startIndex, + in nsISupports element); + + /** + * enumerate the array + * + * @returns a new enumerator positioned at the start of the array + * @throws NS_ERROR_FAILURE if the array is empty (to make it easy + * to detect errors), or NS_ERROR_OUT_OF_MEMORY if out of memory. + */ + nsISimpleEnumerator enumerate(); +}; diff --git a/xpcom/ds/nsIArrayExtensions.idl b/xpcom/ds/nsIArrayExtensions.idl new file mode 100644 index 000000000..3682d2ee7 --- /dev/null +++ b/xpcom/ds/nsIArrayExtensions.idl @@ -0,0 +1,51 @@ +/* -*- 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 "nsIArray.idl" + +/** + * Helper interface for allowing scripts to treat nsIArray instances as if + * they were nsISupportsArray instances while iterating. + * + * nsISupportsArray is convenient to iterate over in JavaScript: + * + * for (let i = 0; i < array.Count(); ++i) { + * let elem = array.GetElementAt(i); + * ... + * } + * + * but doing the same with nsIArray is somewhat less convenient, since + * queryElementAt is not nearly so nice to use from JavaScript. So we provide + * this extension interface so interfaces that currently return + * nsISupportsArray can start returning nsIArrayExtensions and all JavaScript + * should Just Work. Eventually we'll roll this interface into nsIArray + * itself, possibly getting rid of the Count() method, as it duplicates + * nsIArray functionality. + */ +[scriptable, uuid(261d442e-050c-453d-8aaa-b3f23bcc528b)] +interface nsIArrayExtensions : nsIArray +{ + /** + * Count() + * + * Retrieves the length of the array. This is an alias for the + * |nsIArray.length| attribute. + */ + uint32_t Count(); + + /** + * GetElementAt() + * + * Retrieve a specific element of the array. null is a valid result for + * this method. + * + * Note: If the index is out of bounds null will be returned. + * This differs from the behavior of nsIArray.queryElementAt() which + * will throw if an invalid index is specified. + * + * @param index position of element + */ + nsISupports GetElementAt(in uint32_t index); +}; diff --git a/xpcom/ds/nsIAtom.idl b/xpcom/ds/nsIAtom.idl new file mode 100644 index 000000000..c02540838 --- /dev/null +++ b/xpcom/ds/nsIAtom.idl @@ -0,0 +1,166 @@ +/* -*- 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 "nsISupports.idl" + +%{C++ +#include "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsStringBuffer.h" +%} + +native MallocSizeOf(mozilla::MallocSizeOf); + +/* + * Should this really be scriptable? Using atoms from script or proxies + * could be dangerous since double-wrapping could lead to loss of + * pointer identity. + */ + +[scriptable, builtinclass, uuid(8b8c11d4-3ed5-4079-8974-73c7576cdb34)] +interface nsIAtom : nsISupports +{ + /** + * Get the Unicode or UTF8 value for the string + */ + [binaryname(ScriptableToString)] AString toString(); + [noscript] AUTF8String toUTF8String(); + + /** + * Compare the atom to a specific string value + * Note that this will NEVER return/throw an error condition. + */ + [binaryname(ScriptableEquals)] boolean equals(in AString aString); + + [noscript, notxpcom] + size_t SizeOfIncludingThis(in MallocSizeOf aMallocSizeOf); + +%{C++ + // note this is NOT virtual so this won't muck with the vtable! + inline bool Equals(const nsAString& aString) const { + return aString.Equals(nsDependentString(mString, mLength)); + } + + inline bool IsStaticAtom() const { + return mIsStatic; + } + + inline char16ptr_t GetUTF16String() const { + return mString; + } + + inline uint32_t GetLength() const { + return mLength; + } + + inline void ToString(nsAString& aBuf) { + // See the comment on |mString|'s declaration. + nsStringBuffer::FromData(mString)->ToString(mLength, aBuf); + } + + inline nsStringBuffer* GetStringBuffer() const { + // See the comment on |mString|'s declaration. + return nsStringBuffer::FromData(mString); + } + + /** + * A hashcode that is better distributed than the actual atom + * pointer, for use in situations that need a well-distributed + * hashcode. + */ + inline uint32_t hash() const { + return mHash; + } + +protected: + uint32_t mLength:31; + uint32_t mIsStatic:1; + uint32_t mHash; + /** + * WARNING! There is an invisible constraint on |mString|: the chars it + * points to must belong to an nsStringBuffer. This is so that the + * nsStringBuffer::FromData() calls above are valid. + */ + char16_t* mString; +%} +}; + + +%{C++ +/* + * The four forms of NS_Atomize (for use with |nsCOMPtr<nsIAtom>|) return the + * atom for the string given. At any given time there will always be one atom + * representing a given string. Atoms are intended to make string comparison + * cheaper by simplifying it to pointer equality. A pointer to the atom that + * does not own a reference is not guaranteed to be valid. + */ + + +/** + * Find an atom that matches the given UTF-8 string. + * The string is assumed to be zero terminated. Never returns null. + */ +extern already_AddRefed<nsIAtom> NS_Atomize(const char* aUTF8String); + +/** + * Find an atom that matches the given UTF-8 string. Never returns null. + */ +extern already_AddRefed<nsIAtom> NS_Atomize(const nsACString& aUTF8String); + +/** + * Find an atom that matches the given UTF-16 string. + * The string is assumed to be zero terminated. Never returns null. + */ +extern already_AddRefed<nsIAtom> NS_Atomize(const char16_t* aUTF16String); + +/** + * Find an atom that matches the given UTF-16 string. Never returns null. + */ +extern already_AddRefed<nsIAtom> NS_Atomize(const nsAString& aUTF16String); + +/** + * Return a count of the total number of atoms currently + * alive in the system. + */ +extern nsrefcnt NS_GetNumberOfAtoms(void); + +/** + * Return a pointer for a static atom for the string or null if there's + * no static atom for this string. + */ +extern nsIAtom* NS_GetStaticAtom(const nsAString& aUTF16String); + +/** + * Seal the static atom table + */ +extern void NS_SealStaticAtomTable(); + +class nsAtomString : public nsString +{ +public: + explicit nsAtomString(nsIAtom* aAtom) + { + aAtom->ToString(*this); + } +}; + +class nsAtomCString : public nsCString +{ +public: + explicit nsAtomCString(nsIAtom* aAtom) + { + aAtom->ToUTF8String(*this); + } +}; + +class nsDependentAtomString : public nsDependentString +{ +public: + explicit nsDependentAtomString(nsIAtom* aAtom) + : nsDependentString(aAtom->GetUTF16String(), aAtom->GetLength()) + { + } +}; + +%} diff --git a/xpcom/ds/nsIAtomService.idl b/xpcom/ds/nsIAtomService.idl new file mode 100644 index 000000000..a7885a413 --- /dev/null +++ b/xpcom/ds/nsIAtomService.idl @@ -0,0 +1,35 @@ +/* -*- 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 nsIAtom; + +%{C++ +#define NS_ATOMSERVICE_CID \ +{ /* ed3db3fc-0168-4cab-8818-98f5475a490c */ \ + 0xed3db3fc, \ + 0x0168, \ + 0x4cab, \ + {0x88, 0x18, 0x98, 0xf5, 0x47, 0x5a, 0x49, 0x0c} } + +#define NS_ATOMSERVICE_CONTRACTID "@mozilla.org/atom-service;1" +%} + +/* + * Should this really be scriptable? Using atoms from script or proxies + * could be dangerous since double-wrapping could lead to loss of + * pointer identity. + */ + +[scriptable, uuid(9c1f50b9-f9eb-42d4-a8cb-2c7600aeb241)] +interface nsIAtomService : nsISupports { + + /** + * Version of NS_Atomize that doesn't require linking against the + * XPCOM library. See nsIAtom.idl. + */ + nsIAtom getAtom(in AString value); +}; diff --git a/xpcom/ds/nsICollection.idl b/xpcom/ds/nsICollection.idl new file mode 100644 index 000000000..3cd851419 --- /dev/null +++ b/xpcom/ds/nsICollection.idl @@ -0,0 +1,67 @@ +/* -*- Mode: IDL; 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 "nsISerializable.idl" + +interface nsIEnumerator; + +[deprecated, scriptable, uuid(83b6019c-cbc4-11d2-8cca-0060b0fc14a3)] +interface nsICollection : nsISerializable +{ + + uint32_t Count(); + nsISupports GetElementAt(in uint32_t index); + void QueryElementAt(in uint32_t index, in nsIIDRef uuid, + [iid_is(uuid),retval] out nsQIResult result); + void SetElementAt(in uint32_t index, in nsISupports item); + void AppendElement(in nsISupports item); + void RemoveElement(in nsISupports item); + + /** + * This clashes with |nsISimpleEnumerator nsIArray.enumerate()| (only on the + * binary side), so it is renamed with a 'Deprecated' prefix in favor of the + * non-deprecated |nsIArray.enumerate|. + */ + [binaryname(DeprecatedEnumerate)] + nsIEnumerator Enumerate(); + + void Clear(); + +}; + +%{C++ + +#ifndef nsCOMPtr_h__ +#include "nsCOMPtr.h" +#endif + +class MOZ_STACK_CLASS nsQueryElementAt : public nsCOMPtr_helper + { + public: + nsQueryElementAt( nsICollection* aCollection, uint32_t aIndex, nsresult* aErrorPtr ) + : mCollection(aCollection), + mIndex(aIndex), + mErrorPtr(aErrorPtr) + { + // nothing else to do here + } + + virtual nsresult NS_FASTCALL operator()( const nsIID& aIID, void** ) + const override; + + private: + nsICollection* MOZ_NON_OWNING_REF mCollection; + uint32_t mIndex; + nsresult* mErrorPtr; + }; + +inline +const nsQueryElementAt +do_QueryElementAt( nsICollection* aCollection, uint32_t aIndex, nsresult* aErrorPtr = 0 ) + { + return nsQueryElementAt(aCollection, aIndex, aErrorPtr); + } + +%} diff --git a/xpcom/ds/nsIEnumerator.idl b/xpcom/ds/nsIEnumerator.idl new file mode 100644 index 000000000..9482ef436 --- /dev/null +++ b/xpcom/ds/nsIEnumerator.idl @@ -0,0 +1,50 @@ +/* -*- 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" + +%{C++ +#define NS_ENUMERATOR_FALSE 1 +%} +/* + * DO NOT USE THIS INTERFACE. IT IS HORRIBLY BROKEN, USES NS_COMFALSE + * AND IS BASICALLY IMPOSSIBLE TO USE CORRECTLY THROUGH PROXIES OR + * XPCONNECT. IF YOU SEE NEW USES OF THIS INTERFACE IN CODE YOU ARE + * REVIEWING, YOU SHOULD INSIST ON nsISimpleEnumerator. + * + * DON'T MAKE ME COME OVER THERE. + */ +[deprecated, scriptable, uuid(ad385286-cbc4-11d2-8cca-0060b0fc14a3)] +interface nsIEnumerator : nsISupports { + /** First will reset the list. will return NS_FAILED if no items + */ + void first(); + + /** Next will advance the list. will return failed if already at end + */ + void next(); + + /** CurrentItem will return the CurrentItem item it will fail if the + * list is empty + */ + nsISupports currentItem(); + + /** return if the collection is at the end. that is the beginning following + * a call to Prev and it is the end of the list following a call to next + */ + void isDone(); +}; + +[deprecated, uuid(75f158a0-cadd-11d2-8cca-0060b0fc14a3)] +interface nsIBidirectionalEnumerator : nsIEnumerator { + + /** Last will reset the list to the end. will return NS_FAILED if no items + */ + void last(); + + /** Prev will decrement the list. will return failed if already at beginning + */ + void prev(); +}; diff --git a/xpcom/ds/nsIHashable.idl b/xpcom/ds/nsIHashable.idl new file mode 100644 index 000000000..aa223787c --- /dev/null +++ b/xpcom/ds/nsIHashable.idl @@ -0,0 +1,24 @@ +/* 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" + +/** + * Represents an object that can be stored in a hashtable. + */ +[scriptable, uuid(17e595fa-b57a-4933-bd0f-b1812e8ab188)] +interface nsIHashable : nsISupports +{ + /** + * Is this object the equivalent of the other object? + */ + boolean equals(in nsIHashable aOther); + + /** + * A generated hashcode for this object. Objects that are equivalent + * must have the same hash code. Getting this property should never + * throw an exception! + */ + readonly attribute unsigned long hashCode; +}; diff --git a/xpcom/ds/nsIINIParser.idl b/xpcom/ds/nsIINIParser.idl new file mode 100644 index 000000000..166828e5d --- /dev/null +++ b/xpcom/ds/nsIINIParser.idl @@ -0,0 +1,58 @@ +/* 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 nsIUTF8StringEnumerator; +interface nsIFile; + +[scriptable, uuid(7eb955f6-3e78-4d39-b72f-c1bf12a94bce)] +interface nsIINIParser : nsISupports +{ + /** + * Enumerates the [section]s available in the INI file. + */ + nsIUTF8StringEnumerator getSections(); + + /** + * Enumerates the keys available within a section. + */ + nsIUTF8StringEnumerator getKeys(in AUTF8String aSection); + + /** + * Get the value of a string for a particular section and key. + */ + AUTF8String getString(in AUTF8String aSection, in AUTF8String aKey); +}; + +[scriptable, uuid(b67bb24b-31a3-4a6a-a5d9-0485c9af5a04)] +interface nsIINIParserWriter : nsISupports +{ + /** + * Windows and the NSIS installer code sometimes expect INI files to be in + * UTF-16 encoding. On Windows only, this flag to writeFile can be used to + * change the encoding from its default UTF-8. + */ + const unsigned long WRITE_UTF16 = 0x1; + + /** + * Set the value of a string for a particular section and key. + */ + void setString(in AUTF8String aSection, in AUTF8String aKey, in AUTF8String aValue); + + /** + * Write to the INI file. + */ + void writeFile([optional] in nsIFile aINIFile, + [optional] in unsigned long aFlags); +}; + +[scriptable, uuid(ccae7ea5-1218-4b51-aecb-c2d8ecd46af9)] +interface nsIINIParserFactory : nsISupports +{ + /** + * Create an iniparser instance from a local file. + */ + nsIINIParser createINIParser(in nsIFile aINIFile); +}; diff --git a/xpcom/ds/nsIMutableArray.idl b/xpcom/ds/nsIMutableArray.idl new file mode 100644 index 000000000..67e527a81 --- /dev/null +++ b/xpcom/ds/nsIMutableArray.idl @@ -0,0 +1,110 @@ +/* -*- 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 "nsIArrayExtensions.idl" + +/** + * nsIMutableArray + * A separate set of methods that will act on the array. Consumers of + * nsIArray should not QueryInterface to nsIMutableArray unless they + * own the array. + * + * As above, it is legal to add null elements to the array. Note also + * that null elements can be created as a side effect of + * insertElementAt(). Conversely, if insertElementAt() is never used, + * and null elements are never explicitly added to the array, then it + * is guaranteed that queryElementAt() will never return a null value. + * + * Any of these methods may throw NS_ERROR_OUT_OF_MEMORY when the + * array must grow to complete the call, but the allocation fails. + */ +[scriptable, uuid(af059da0-c85b-40ec-af07-ae4bfdc192cc)] +interface nsIMutableArray : nsIArrayExtensions +{ + /** + * appendElement() + * + * Append an element at the end of the array. + * + * @param element The element to append. + * @param weak Whether or not to store the element using a weak + * reference. + * @throws NS_ERROR_FAILURE when a weak reference is requested, + * but the element does not support + * nsIWeakReference. + */ + void appendElement(in nsISupports element, in boolean weak); + + /** + * removeElementAt() + * + * Remove an element at a specific position, moving all elements + * stored at a higher position down one. + * To remove a specific element, use indexOf() to find the index + * first, then call removeElementAt(). + * + * @param index the position of the item + * + */ + void removeElementAt(in unsigned long index); + + /** + * insertElementAt() + * + * Insert an element at the given position, moving the element + * currently located in that position, and all elements in higher + * position, up by one. + * + * @param element The element to insert + * @param index The position in the array: + * If the position is lower than the current length + * of the array, the elements at that position and + * onwards are bumped one position up. + * If the position is equal to the current length + * of the array, the new element is appended. + * An index lower than 0 or higher than the current + * length of the array is invalid and will be ignored. + * + * @throws NS_ERROR_FAILURE when a weak reference is requested, + * but the element does not support + * nsIWeakReference. + */ + void insertElementAt(in nsISupports element, in unsigned long index, + in boolean weak); + + /** + * replaceElementAt() + * + * Replace the element at the given position. + * + * @param element The new element to insert + * @param index The position in the array + * If the position is lower than the current length + * of the array, an existing element will be replaced. + * If the position is equal to the current length + * of the array, the new element is appended. + * If the position is higher than the current length + * of the array, empty elements are appended followed + * by the new element at the specified position. + * An index lower than 0 is invalid and will be ignored. + * + * @param weak Whether or not to store the new element using a weak + * reference. + * + * @throws NS_ERROR_FAILURE when a weak reference is requested, + * but the element does not support + * nsIWeakReference. + */ + void replaceElementAt(in nsISupports element, in unsigned long index, + in boolean weak); + + + /** + * clear() + * + * clear the entire array, releasing all stored objects + */ + void clear(); +}; diff --git a/xpcom/ds/nsINIParserImpl.cpp b/xpcom/ds/nsINIParserImpl.cpp new file mode 100644 index 000000000..24def4e58 --- /dev/null +++ b/xpcom/ds/nsINIParserImpl.cpp @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsINIParserImpl.h" + +#include "nsINIParser.h" +#include "nsStringEnumerator.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +class nsINIParserImpl final + : public nsIINIParser +{ + ~nsINIParserImpl() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINIPARSER + + nsresult Init(nsIFile* aINIFile) { return mParser.Init(aINIFile); } + +private: + nsINIParser mParser; +}; + +NS_IMPL_ISUPPORTS(nsINIParserFactory, + nsIINIParserFactory, + nsIFactory) + +NS_IMETHODIMP +nsINIParserFactory::CreateINIParser(nsIFile* aINIFile, + nsIINIParser** aResult) +{ + *aResult = nullptr; + + RefPtr<nsINIParserImpl> p(new nsINIParserImpl()); + if (!p) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = p->Init(aINIFile); + + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(*aResult = p); + } + + return rv; +} + +NS_IMETHODIMP +nsINIParserFactory::CreateInstance(nsISupports* aOuter, + REFNSIID aIID, + void** aResult) +{ + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + // We are our own singleton. + return QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsINIParserFactory::LockFactory(bool aLock) +{ + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsINIParserImpl, + nsIINIParser) + +static bool +SectionCB(const char* aSection, void* aClosure) +{ + nsTArray<nsCString>* strings = static_cast<nsTArray<nsCString>*>(aClosure); + strings->AppendElement()->Assign(aSection); + return true; +} + +NS_IMETHODIMP +nsINIParserImpl::GetSections(nsIUTF8StringEnumerator** aResult) +{ + nsTArray<nsCString>* strings = new nsTArray<nsCString>; + if (!strings) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = mParser.GetSections(SectionCB, strings); + if (NS_SUCCEEDED(rv)) { + rv = NS_NewAdoptingUTF8StringEnumerator(aResult, strings); + } + + if (NS_FAILED(rv)) { + delete strings; + } + + return rv; +} + +static bool +KeyCB(const char* aKey, const char* aValue, void* aClosure) +{ + nsTArray<nsCString>* strings = static_cast<nsTArray<nsCString>*>(aClosure); + strings->AppendElement()->Assign(aKey); + return true; +} + +NS_IMETHODIMP +nsINIParserImpl::GetKeys(const nsACString& aSection, + nsIUTF8StringEnumerator** aResult) +{ + nsTArray<nsCString>* strings = new nsTArray<nsCString>; + if (!strings) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = mParser.GetStrings(PromiseFlatCString(aSection).get(), + KeyCB, strings); + if (NS_SUCCEEDED(rv)) { + rv = NS_NewAdoptingUTF8StringEnumerator(aResult, strings); + } + + if (NS_FAILED(rv)) { + delete strings; + } + + return rv; + +} + +NS_IMETHODIMP +nsINIParserImpl::GetString(const nsACString& aSection, + const nsACString& aKey, + nsACString& aResult) +{ + return mParser.GetString(PromiseFlatCString(aSection).get(), + PromiseFlatCString(aKey).get(), + aResult); +} diff --git a/xpcom/ds/nsINIParserImpl.h b/xpcom/ds/nsINIParserImpl.h new file mode 100644 index 000000000..8e63f36d7 --- /dev/null +++ b/xpcom/ds/nsINIParserImpl.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsINIParserImpl_h__ +#define nsINIParserImpl_h__ + +#include "nsIINIParser.h" +#include "nsIFactory.h" +#include "mozilla/Attributes.h" + +#define NS_INIPARSERFACTORY_CID \ +{ 0xdfac10a9, 0xdd24, 0x43cf, \ + { 0xa0, 0x95, 0x6f, 0xfa, 0x2e, 0x4b, 0x6a, 0x6c } } + +#define NS_INIPARSERFACTORY_CONTRACTID \ + "@mozilla.org/xpcom/ini-parser-factory;1" + +class nsINIParserFactory final + : public nsIINIParserFactory + , public nsIFactory +{ + ~nsINIParserFactory() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINIPARSERFACTORY + NS_DECL_NSIFACTORY +}; + +#endif // nsINIParserImpl_h__ diff --git a/xpcom/ds/nsINIProcessor.js b/xpcom/ds/nsINIProcessor.js new file mode 100644 index 000000000..832f35a5f --- /dev/null +++ b/xpcom/ds/nsINIProcessor.js @@ -0,0 +1,192 @@ +/* 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/. */ + + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function INIProcessorFactory() { +} + +INIProcessorFactory.prototype = { + classID: Components.ID("{6ec5f479-8e13-4403-b6ca-fe4c2dca14fd}"), + QueryInterface : XPCOMUtils.generateQI([Ci.nsIINIParserFactory]), + + createINIParser : function (aINIFile) { + return new INIProcessor(aINIFile); + } + +}; // end of INIProcessorFactory implementation + +const MODE_WRONLY = 0x02; +const MODE_CREATE = 0x08; +const MODE_TRUNCATE = 0x20; + +// nsIINIParser implementation +function INIProcessor(aFile) { + this._iniFile = aFile; + this._iniData = {}; + this._readFile(); +} + +INIProcessor.prototype = { + QueryInterface : XPCOMUtils.generateQI([Ci.nsIINIParser, Ci.nsIINIParserWriter]), + + __utf8Converter : null, // UCS2 <--> UTF8 string conversion + get _utf8Converter() { + if (!this.__utf8Converter) { + this.__utf8Converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + this.__utf8Converter.charset = "UTF-8"; + } + return this.__utf8Converter; + }, + + __utf16leConverter : null, // UCS2 <--> UTF16LE string conversion + get _utf16leConverter() { + if (!this.__utf16leConverter) { + this.__utf16leConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + this.__utf16leConverter.charset = "UTF-16LE"; + } + return this.__utf16leConverter; + }, + + _utfConverterReset : function() { + this.__utf8Converter = null; + this.__utf16leConverter = null; + }, + + _iniFile : null, + _iniData : null, + + /* + * Reads the INI file and stores the data internally. + */ + _readFile : function() { + // If file doesn't exist, there's nothing to do. + if (!this._iniFile.exists() || 0 == this._iniFile.fileSize) + return; + + let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"] + .getService(Ci.nsIINIParserFactory).createINIParser(this._iniFile); + for (let section of XPCOMUtils.IterStringEnumerator(iniParser.getSections())) { + this._iniData[section] = {}; + for (let key of XPCOMUtils.IterStringEnumerator(iniParser.getKeys(section))) { + this._iniData[section][key] = iniParser.getString(section, key); + } + } + }, + + // nsIINIParser + + getSections : function() { + let sections = []; + for (let section in this._iniData) + sections.push(section); + return new stringEnumerator(sections); + }, + + getKeys : function(aSection) { + let keys = []; + if (aSection in this._iniData) + for (let key in this._iniData[aSection]) + keys.push(key); + return new stringEnumerator(keys); + }, + + getString : function(aSection, aKey) { + if (!(aSection in this._iniData)) + throw Cr.NS_ERROR_FAILURE; + if (!(aKey in this._iniData[aSection])) + throw Cr.NS_ERROR_FAILURE; + return this._iniData[aSection][aKey]; + }, + + + // nsIINIParserWriter + + setString : function(aSection, aKey, aValue) { + const isSectionIllegal = /[\0\r\n\[\]]/; + const isKeyValIllegal = /[\0\r\n=]/; + + if (isSectionIllegal.test(aSection)) + throw Components.Exception("bad character in section name", + Cr.ERROR_ILLEGAL_VALUE); + if (isKeyValIllegal.test(aKey) || isKeyValIllegal.test(aValue)) + throw Components.Exception("bad character in key/value", + Cr.ERROR_ILLEGAL_VALUE); + + if (!(aSection in this._iniData)) + this._iniData[aSection] = {}; + + this._iniData[aSection][aKey] = aValue; + }, + + writeFile : function(aFile, aFlags) { + + let converter; + function writeLine(data) { + data += "\n"; + data = converter.ConvertFromUnicode(data); + data += converter.Finish(); + outputStream.write(data, data.length); + } + + if (!aFile) + aFile = this._iniFile; + + let safeStream = Cc["@mozilla.org/network/safe-file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + safeStream.init(aFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, + 0o600, null); + + var outputStream = Cc["@mozilla.org/network/buffered-output-stream;1"]. + createInstance(Ci.nsIBufferedOutputStream); + outputStream.init(safeStream, 8192); + outputStream.QueryInterface(Ci.nsISafeOutputStream); // for .finish() + + if (Ci.nsIINIParserWriter.WRITE_UTF16 == aFlags + && 'nsIWindowsRegKey' in Ci) { + outputStream.write("\xFF\xFE", 2); + converter = this._utf16leConverter; + } else { + converter = this._utf8Converter; + } + + for (let section in this._iniData) { + writeLine("[" + section + "]"); + for (let key in this._iniData[section]) { + writeLine(key + "=" + this._iniData[section][key]); + } + } + + outputStream.finish(); + } +}; + +function stringEnumerator(stringArray) { + this._strings = stringArray; +} +stringEnumerator.prototype = { + QueryInterface : XPCOMUtils.generateQI([Ci.nsIUTF8StringEnumerator]), + + _strings : null, + _enumIndex: 0, + + hasMore : function() { + return (this._enumIndex < this._strings.length); + }, + + getNext : function() { + return this._strings[this._enumIndex++]; + } +}; + +var component = [INIProcessorFactory]; +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component); diff --git a/xpcom/ds/nsINIProcessor.manifest b/xpcom/ds/nsINIProcessor.manifest new file mode 100644 index 000000000..da19d2aed --- /dev/null +++ b/xpcom/ds/nsINIProcessor.manifest @@ -0,0 +1,2 @@ +component {6ec5f479-8e13-4403-b6ca-fe4c2dca14fd} nsINIProcessor.js +contract @mozilla.org/xpcom/ini-processor-factory;1 {6ec5f479-8e13-4403-b6ca-fe4c2dca14fd} diff --git a/xpcom/ds/nsIObserver.idl b/xpcom/ds/nsIObserver.idl new file mode 100644 index 000000000..cfb4f912b --- /dev/null +++ b/xpcom/ds/nsIObserver.idl @@ -0,0 +1,38 @@ +/* -*- 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" + +/** + * This interface is implemented by an object that wants + * to observe an event corresponding to a topic. + */ + +[scriptable, function, uuid(DB242E01-E4D9-11d2-9DDE-000064657374)] +interface nsIObserver : nsISupports { + + /** + * Observe will be called when there is a notification for the + * topic |aTopic|. This assumes that the object implementing + * this interface has been registered with an observer service + * such as the nsIObserverService. + * + * If you expect multiple topics/subjects, the impl is + * responsible for filtering. + * + * You should not modify, add, remove, or enumerate + * notifications in the implemention of observe. + * + * @param aSubject : Notification specific interface pointer. + * @param aTopic : The notification topic or subject. + * @param aData : Notification specific wide string. + * subject event. + */ + void observe( in nsISupports aSubject, + in string aTopic, + in wstring aData ); + +}; + diff --git a/xpcom/ds/nsIObserverService.idl b/xpcom/ds/nsIObserverService.idl new file mode 100644 index 000000000..d5b40e227 --- /dev/null +++ b/xpcom/ds/nsIObserverService.idl @@ -0,0 +1,77 @@ +/* -*- 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 nsIObserver; +interface nsISimpleEnumerator; + +/** + * nsIObserverService + * + * Service allows a client listener (nsIObserver) to register and unregister for + * notifications of specific string referenced topic. Service also provides a + * way to notify registered listeners and a way to enumerate registered client + * listeners. + */ + +[scriptable, uuid(D07F5192-E3D1-11d2-8ACD-00105A1B8860)] +interface nsIObserverService : nsISupports +{ + + /** + * AddObserver + * + * Registers a given listener for a notifications regarding the specified + * topic. + * + * @param anObserve : The interface pointer which will receive notifications. + * @param aTopic : The notification topic or subject. + * @param ownsWeak : If set to false, the nsIObserverService will hold a + * strong reference to |anObserver|. If set to true and + * |anObserver| supports the nsIWeakReference interface, + * a weak reference will be held. Otherwise an error will be + * returned. + */ + void addObserver( in nsIObserver anObserver, in string aTopic, in boolean ownsWeak); + + /** + * removeObserver + * + * Unregisters a given listener from notifications regarding the specified + * topic. + * + * @param anObserver : The interface pointer which will stop recieving + * notifications. + * @param aTopic : The notification topic or subject. + */ + void removeObserver( in nsIObserver anObserver, in string aTopic ); + + /** + * notifyObservers + * + * Notifies all registered listeners of the given topic. + * + * @param aSubject : Notification specific interface pointer. + * @param aTopic : The notification topic or subject. + * @param someData : Notification specific wide string. + */ + void notifyObservers( in nsISupports aSubject, + in string aTopic, + in wstring someData ); + + /** + * enumerateObservers + * + * Returns an enumeration of all registered listeners. + * + * @param aTopic : The notification topic or subject. + */ + nsISimpleEnumerator enumerateObservers( in string aTopic ); + + +}; + + diff --git a/xpcom/ds/nsIPersistentProperties.h b/xpcom/ds/nsIPersistentProperties.h new file mode 100644 index 000000000..e886076e9 --- /dev/null +++ b/xpcom/ds/nsIPersistentProperties.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 __gen_nsIPersistentProperties_h__ +#define __gen_nsIPersistentProperties_h__ + +// "soft" switch over to an IDL generated header file +#include "nsIPersistentProperties2.h" + +#endif /* __gen_nsIPersistentProperties_h__ */ diff --git a/xpcom/ds/nsIPersistentProperties2.idl b/xpcom/ds/nsIPersistentProperties2.idl new file mode 100644 index 000000000..3f24bb773 --- /dev/null +++ b/xpcom/ds/nsIPersistentProperties2.idl @@ -0,0 +1,63 @@ +/* -*- 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 "nsIProperties.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsISimpleEnumerator; + +[scriptable, uuid(283EE646-1AEF-11D4-98B3-00C04fA0CE9A)] +interface nsIPropertyElement : nsISupports { + attribute AUTF8String key; + attribute AString value; +}; + +[scriptable, uuid(706867af-0400-4faa-beb1-0dae87308784)] +interface nsIPersistentProperties : nsIProperties +{ + /** + * load a set of name/value pairs from the input stream + * names and values should be in UTF8 + */ + void load(in nsIInputStream input); + + /** + * output the values to the stream - results will be in UTF8 + */ + void save(in nsIOutputStream output, in AUTF8String header); + + /** + * get an enumeration of nsIPropertyElement objects, + * which are read-only (i.e. setting properties on the element will + * not make changes back into the source nsIPersistentProperties + */ + nsISimpleEnumerator enumerate(); + + /** + * shortcut to nsIProperty's get() which retrieves a string value + * directly (and thus faster) + */ + AString getStringProperty(in AUTF8String key); + + /** + * shortcut to nsIProperty's set() which sets a string value + * directly (and thus faster). If the given property already exists, + * then the old value will be returned + */ + AString setStringProperty(in AUTF8String key, in AString value); +}; + + +%{C++ + +#define NS_IPERSISTENTPROPERTIES_CID \ +{ 0x2245e573, 0x9464, 0x11d2, \ + { 0x9b, 0x8b, 0x0, 0x80, 0x5f, 0x8a, 0x16, 0xd9 } } + +#define NS_PERSISTENTPROPERTIES_CONTRACTID "@mozilla.org/persistent-properties;1" + +%} + diff --git a/xpcom/ds/nsIProperties.idl b/xpcom/ds/nsIProperties.idl new file mode 100644 index 000000000..a87e14443 --- /dev/null +++ b/xpcom/ds/nsIProperties.idl @@ -0,0 +1,46 @@ +/* -*- Mode: IDL; 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 "nsISupports.idl" + +/* + * Simple mapping service interface. + */ + +[scriptable, uuid(78650582-4e93-4b60-8e85-26ebd3eb14ca)] +interface nsIProperties : nsISupports +{ + /** + * Gets a property with a given name. + * + * @throws NS_ERROR_FAILURE if a property with that name doesn't exist. + * @throws NS_ERROR_NO_INTERFACE if the found property fails to QI to the + * given iid. + */ + void get(in string prop, in nsIIDRef iid, + [iid_is(iid),retval] out nsQIResult result); + + /** + * Sets a property with a given name to a given value. + */ + void set(in string prop, in nsISupports value); + + /** + * Returns true if the property with the given name exists. + */ + boolean has(in string prop); + + /** + * Undefines a property. + * @throws NS_ERROR_FAILURE if a property with that name doesn't + * already exist. + */ + void undefine(in string prop); + + /** + * Returns an array of the keys. + */ + void getKeys(out uint32_t count, [array, size_is(count), retval] out string keys); +}; diff --git a/xpcom/ds/nsIProperty.idl b/xpcom/ds/nsIProperty.idl new file mode 100644 index 000000000..9e6e1e487 --- /dev/null +++ b/xpcom/ds/nsIProperty.idl @@ -0,0 +1,25 @@ +/* -*- Mode: IDL; 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/. */ + +/* nsIVariant based Property support. */ + +#include "nsISupports.idl" + +interface nsIVariant; + +[scriptable, uuid(6dcf9030-a49f-11d5-910d-0010a4e73d9a)] +interface nsIProperty : nsISupports +{ + /** + * Get the name of the property. + */ + readonly attribute AString name; + + /** + * Get the value of the property. + */ + readonly attribute nsIVariant value; +}; diff --git a/xpcom/ds/nsIPropertyBag.idl b/xpcom/ds/nsIPropertyBag.idl new file mode 100644 index 000000000..1d1dfced7 --- /dev/null +++ b/xpcom/ds/nsIPropertyBag.idl @@ -0,0 +1,30 @@ +/* -*- Mode: IDL; 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/. */ + +/* nsIVariant based Property Bag support. */ + +#include "nsISupports.idl" + +interface nsIVariant; +interface nsISimpleEnumerator; + +[scriptable, uuid(bfcd37b0-a49f-11d5-910d-0010a4e73d9a)] +interface nsIPropertyBag : nsISupports +{ + /** + * Get a nsISimpleEnumerator whose elements are nsIProperty objects. + */ + readonly attribute nsISimpleEnumerator enumerator; + + /** + * Get a property value for the given name. + * @throws NS_ERROR_FAILURE if a property with that name doesn't + * exist. + */ + nsIVariant getProperty(in AString name); +}; + + diff --git a/xpcom/ds/nsIPropertyBag2.idl b/xpcom/ds/nsIPropertyBag2.idl new file mode 100644 index 000000000..7b4be7f4a --- /dev/null +++ b/xpcom/ds/nsIPropertyBag2.idl @@ -0,0 +1,42 @@ +/* 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/. */ + +/* nsIVariant based Property Bag support. */ + +#include "nsIPropertyBag.idl" + +[scriptable, uuid(625cfd1e-da1e-4417-9ee9-dbc8e0b3fd79)] +interface nsIPropertyBag2 : nsIPropertyBag +{ + // Accessing a property as a different type may attempt conversion to the + // requested value + + int32_t getPropertyAsInt32 (in AString prop); + uint32_t getPropertyAsUint32 (in AString prop); + int64_t getPropertyAsInt64 (in AString prop); + uint64_t getPropertyAsUint64 (in AString prop); + double getPropertyAsDouble (in AString prop); + AString getPropertyAsAString (in AString prop); + ACString getPropertyAsACString (in AString prop); + AUTF8String getPropertyAsAUTF8String (in AString prop); + boolean getPropertyAsBool (in AString prop); + + /** + * This method returns null if the value exists, but is null. + */ + void getPropertyAsInterface (in AString prop, + in nsIIDRef iid, + [iid_is(iid), retval] out nsQIResult result); + + /** + * This method returns null if the value does not exist, + * or exists but is null. + */ + nsIVariant get (in AString prop); + + /** + * Check for the existence of a key. + */ + boolean hasKey (in AString prop); +}; diff --git a/xpcom/ds/nsISerializable.idl b/xpcom/ds/nsISerializable.idl new file mode 100644 index 000000000..f525f3a8f --- /dev/null +++ b/xpcom/ds/nsISerializable.idl @@ -0,0 +1,32 @@ +/* -*- 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 nsIObjectInputStream; +interface nsIObjectOutputStream; + +[scriptable, uuid(91cca981-c26d-44a8-bebe-d9ed4891503a)] +interface nsISerializable : nsISupports +{ + /** + * Initialize the object implementing nsISerializable, which must have + * been freshly constructed via CreateInstance. All data members that + * can't be set to default values must have been serialized by write, + * and should be read from aInputStream in the same order by this method. + */ + void read(in nsIObjectInputStream aInputStream); + + /** + * Serialize the object implementing nsISerializable to aOutputStream, by + * writing each data member that must be recovered later to reconstitute + * a working replica of this object, in a canonical member and byte order, + * to aOutputStream. + * + * NB: a class that implements nsISerializable *must* also implement + * nsIClassInfo, in particular nsIClassInfo::GetClassID. + */ + void write(in nsIObjectOutputStream aOutputStream); +}; diff --git a/xpcom/ds/nsISimpleEnumerator.idl b/xpcom/ds/nsISimpleEnumerator.idl new file mode 100644 index 000000000..cbb0cd200 --- /dev/null +++ b/xpcom/ds/nsISimpleEnumerator.idl @@ -0,0 +1,46 @@ +/* -*- 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" + +/** + * Used to enumerate over elements defined by its implementor. + * Although hasMoreElements() can be called independently of getNext(), + * getNext() must be pre-ceeded by a call to hasMoreElements(). There is + * no way to "reset" an enumerator, once you obtain one. + * + * @version 1.0 + */ + +[scriptable, uuid(D1899240-F9D2-11D2-BDD6-000064657374)] +interface nsISimpleEnumerator : nsISupports { + /** + * Called to determine whether or not the enumerator has + * any elements that can be returned via getNext(). This method + * is generally used to determine whether or not to initiate or + * continue iteration over the enumerator, though it can be + * called without subsequent getNext() calls. Does not affect + * internal state of enumerator. + * + * @see getNext() + * @return true if there are remaining elements in the enumerator. + * false if there are no more elements in the enumerator. + */ + boolean hasMoreElements(); + + /** + * Called to retrieve the next element in the enumerator. The "next" + * element is the first element upon the first call. Must be + * pre-ceeded by a call to hasMoreElements() which returns PR_TRUE. + * This method is generally called within a loop to iterate over + * the elements in the enumerator. + * + * @see hasMoreElements() + * @throws NS_ERROR_FAILURE if there are no more elements + * to enumerate. + * @return the next element in the enumeration. + */ + nsISupports getNext(); +}; diff --git a/xpcom/ds/nsIStringEnumerator.idl b/xpcom/ds/nsIStringEnumerator.idl new file mode 100644 index 000000000..d9c4130ee --- /dev/null +++ b/xpcom/ds/nsIStringEnumerator.idl @@ -0,0 +1,25 @@ +/* -*- Mode: IDL; 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" + +/** + * Used to enumerate over an ordered list of strings. + */ + +[scriptable, uuid(50d3ef6c-9380-4f06-9fb2-95488f7d141c)] +interface nsIStringEnumerator : nsISupports +{ + boolean hasMore(); + AString getNext(); +}; + +[scriptable, uuid(9bdf1010-3695-4907-95ed-83d0410ec307)] +interface nsIUTF8StringEnumerator : nsISupports +{ + boolean hasMore(); + AUTF8String getNext(); +}; + diff --git a/xpcom/ds/nsISupportsArray.idl b/xpcom/ds/nsISupportsArray.idl new file mode 100644 index 000000000..30e2a5035 --- /dev/null +++ b/xpcom/ds/nsISupportsArray.idl @@ -0,0 +1,60 @@ +/* -*- 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 "nsICollection.idl" + +/* + * This entire interface is deprecated and should not be used. + * See nsIArray and nsIMutableArray for the new implementations. + * + * http://groups.google.com/groups?q=nsisupportsarray+group:netscape.public.mozilla.xpcom&hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=3D779491.3050506%40netscape.com&rnum=2 + * http://groups.google.com/groups?q=nsisupportsarray+group:netscape.public.mozilla.xpcom&hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=al8412%245ab2%40ripley.netscape.com&rnum=8 + */ + +%{C++ + +class nsIBidirectionalEnumerator; +class nsISupportsArray; + +#define NS_SUPPORTSARRAY_CID \ +{ /* bda17d50-0d6b-11d3-9331-00104ba0fd40 */ \ + 0xbda17d50, \ + 0x0d6b, \ + 0x11d3, \ + {0x93, 0x31, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \ +} +#define NS_SUPPORTSARRAY_CONTRACTID "@mozilla.org/supports-array;1" + +%} + +[deprecated, scriptable, uuid(241addc8-3608-4e73-8083-2fd6fa09eba2)] +interface nsISupportsArray : nsICollection { + + [notxpcom] long IndexOf([const] in nsISupports aPossibleElement); + + // xpcom-compatible versions + long GetIndexOf(in nsISupports aPossibleElement); + + [notxpcom] boolean InsertElementAt(in nsISupports aElement, + in unsigned long aIndex); + [notxpcom] boolean ReplaceElementAt(in nsISupports aElement, + in unsigned long aIndex); + + [notxpcom] boolean RemoveElementAt(in unsigned long aIndex); + + // xpcom-compatible versions + void DeleteElementAt(in unsigned long aIndex); + + nsISupportsArray clone(); +}; + +%{C++ + +// Construct and return a default implementation of nsISupportsArray: +extern MOZ_MUST_USE nsresult +NS_NewISupportsArray(nsISupportsArray** aInstancePtrResult); + +%} diff --git a/xpcom/ds/nsISupportsIterators.idl b/xpcom/ds/nsISupportsIterators.idl new file mode 100644 index 000000000..8d47375d5 --- /dev/null +++ b/xpcom/ds/nsISupportsIterators.idl @@ -0,0 +1,292 @@ +/* -*- 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/. */ + +/* nsISupportsIterators.idl --- IDL defining general purpose iterators */ + + +#include "nsISupports.idl" + + + /* + ... + */ + + + /** + * ... + */ +[scriptable, uuid(7330650e-1dd2-11b2-a0c2-9ff86ee97bed)] +interface nsIOutputIterator : nsISupports + { + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + }; + + /** + * ... + */ +[scriptable, uuid(85585e12-1dd2-11b2-a930-f6929058269a)] +interface nsIInputIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + + /** + * ... + */ +[scriptable, uuid(8da01646-1dd2-11b2-98a7-c7009045be7e)] +interface nsIForwardIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + + /** + * ... + */ +[scriptable, uuid(948defaa-1dd1-11b2-89f6-8ce81f5ebda9)] +interface nsIBidirectionalIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Move this iterator to the previous position in the underlying container or sequence. + */ + void stepBackward(); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + + /** + * ... + */ +[scriptable, uuid(9bd6fdb0-1dd1-11b2-9101-d15375968230)] +interface nsIRandomAccessIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Retrieve (and |AddRef()|) an element at some offset from where this iterator currently points. + * The offset may be negative. |getElementAt(0)| is equivalent to |getElement()|. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + * @result a new reference to the indicated element (if any) + */ + nsISupports getElementAt( in int32_t anOffset ); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position |anOffset| away from that currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * |putElementAt(0, obj)| is equivalent to |putElement(obj)|. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElementAt( in int32_t anOffset, in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Move this iterator by |anOffset| positions in the underlying container or sequence. + * |anOffset| may be negative. |stepForwardBy(1)| is equivalent to |stepForward()|. + * |stepForwardBy(0)| is a no-op. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + */ + void stepForwardBy( in int32_t anOffset ); + + /** + * Move this iterator to the previous position in the underlying container or sequence. + */ + void stepBackward(); + + /** + * Move this iterator backwards by |anOffset| positions in the underlying container or sequence. + * |anOffset| may be negative. |stepBackwardBy(1)| is equivalent to |stepBackward()|. + * |stepBackwardBy(n)| is equivalent to |stepForwardBy(-n)|. |stepBackwardBy(0)| is a no-op. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + */ + void stepBackwardBy( in int32_t anOffset ); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + +%{C++ +%} diff --git a/xpcom/ds/nsISupportsPrimitives.idl b/xpcom/ds/nsISupportsPrimitives.idl new file mode 100644 index 000000000..71940739c --- /dev/null +++ b/xpcom/ds/nsISupportsPrimitives.idl @@ -0,0 +1,235 @@ +/* -*- Mode: IDL; 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/. */ + +/* nsISupports wrappers for single primitive pieces of data. */ + +#include "nsISupports.idl" + +/** + * Primitive base interface. + * + * These first three are pointer types and do data copying + * using the nsIMemory. Be careful! + */ + +[scriptable, uuid(d0d4b136-1dd1-11b2-9371-f0727ef827c0)] +interface nsISupportsPrimitive : nsISupports +{ + const unsigned short TYPE_ID = 1; + const unsigned short TYPE_CSTRING = 2; + const unsigned short TYPE_STRING = 3; + const unsigned short TYPE_PRBOOL = 4; + const unsigned short TYPE_PRUINT8 = 5; + const unsigned short TYPE_PRUINT16 = 6; + const unsigned short TYPE_PRUINT32 = 7; + const unsigned short TYPE_PRUINT64 = 8; + const unsigned short TYPE_PRTIME = 9; + const unsigned short TYPE_CHAR = 10; + const unsigned short TYPE_PRINT16 = 11; + const unsigned short TYPE_PRINT32 = 12; + const unsigned short TYPE_PRINT64 = 13; + const unsigned short TYPE_FLOAT = 14; + const unsigned short TYPE_DOUBLE = 15; + const unsigned short TYPE_VOID = 16; + const unsigned short TYPE_INTERFACE_POINTER = 17; + + readonly attribute unsigned short type; +}; + +/** + * Scriptable storage for nsID structures + */ + +[scriptable, uuid(d18290a0-4a1c-11d3-9890-006008962422)] +interface nsISupportsID : nsISupportsPrimitive +{ + attribute nsIDPtr data; + string toString(); +}; + +/** + * Scriptable storage for ASCII strings + */ + +[scriptable, uuid(d65ff270-4a1c-11d3-9890-006008962422)] +interface nsISupportsCString : nsISupportsPrimitive +{ + attribute ACString data; + string toString(); +}; + +/** + * Scriptable storage for Unicode strings + */ + +[scriptable, uuid(d79dc970-4a1c-11d3-9890-006008962422)] +interface nsISupportsString : nsISupportsPrimitive +{ + attribute AString data; + wstring toString(); +}; + +/** + * The rest are truly primitive and are passed by value + */ + +/** + * Scriptable storage for booleans + */ + +[scriptable, uuid(ddc3b490-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRBool : nsISupportsPrimitive +{ + attribute boolean data; + string toString(); +}; + +/** + * Scriptable storage for 8-bit integers + */ + +[scriptable, uuid(dec2e4e0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint8 : nsISupportsPrimitive +{ + attribute uint8_t data; + string toString(); +}; + +/** + * Scriptable storage for unsigned 16-bit integers + */ + +[scriptable, uuid(dfacb090-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint16 : nsISupportsPrimitive +{ + attribute uint16_t data; + string toString(); +}; + +/** + * Scriptable storage for unsigned 32-bit integers + */ + +[scriptable, uuid(e01dc470-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint32 : nsISupportsPrimitive +{ + attribute uint32_t data; + string toString(); +}; + +/** + * Scriptable storage for 64-bit integers + */ + +[scriptable, uuid(e13567c0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint64 : nsISupportsPrimitive +{ + attribute uint64_t data; + string toString(); +}; + +/** + * Scriptable storage for NSPR date/time values + */ + +[scriptable, uuid(e2563630-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRTime : nsISupportsPrimitive +{ + attribute PRTime data; + string toString(); +}; + +/** + * Scriptable storage for single character values + * (often used to store an ASCII character) + */ + +[scriptable, uuid(e2b05e40-4a1c-11d3-9890-006008962422)] +interface nsISupportsChar : nsISupportsPrimitive +{ + attribute char data; + string toString(); +}; + +/** + * Scriptable storage for 16-bit integers + */ + +[scriptable, uuid(e30d94b0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt16 : nsISupportsPrimitive +{ + attribute int16_t data; + string toString(); +}; + +/** + * Scriptable storage for 32-bit integers + */ + +[scriptable, uuid(e36c5250-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt32 : nsISupportsPrimitive +{ + attribute int32_t data; + string toString(); +}; + +/** + * Scriptable storage for 64-bit integers + */ + +[scriptable, uuid(e3cb0ff0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt64 : nsISupportsPrimitive +{ + attribute int64_t data; + string toString(); +}; + +/** + * Scriptable storage for floating point numbers + */ + +[scriptable, uuid(abeaa390-4ac0-11d3-baea-00805f8a5dd7)] +interface nsISupportsFloat : nsISupportsPrimitive +{ + attribute float data; + string toString(); +}; + +/** + * Scriptable storage for doubles + */ + +[scriptable, uuid(b32523a0-4ac0-11d3-baea-00805f8a5dd7)] +interface nsISupportsDouble : nsISupportsPrimitive +{ + attribute double data; + string toString(); +}; + +/** + * Scriptable storage for generic pointers + */ + +[scriptable, uuid(464484f0-568d-11d3-baf8-00805f8a5dd7)] +interface nsISupportsVoid : nsISupportsPrimitive +{ + [noscript] attribute voidPtr data; + string toString(); +}; + +/** + * Scriptable storage for other XPCOM objects + */ + +[scriptable, uuid(995ea724-1dd1-11b2-9211-c21bdd3e7ed0)] +interface nsISupportsInterfacePointer : nsISupportsPrimitive +{ + attribute nsISupports data; + attribute nsIDPtr dataIID; + + string toString(); +}; + + diff --git a/xpcom/ds/nsIVariant.idl b/xpcom/ds/nsIVariant.idl new file mode 100644 index 000000000..ef5528463 --- /dev/null +++ b/xpcom/ds/nsIVariant.idl @@ -0,0 +1,155 @@ +/* -*- Mode: IDL; 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/. */ + +/* The long avoided variant support for xpcom. */ + +#include "nsISupports.idl" + +[scriptable,uuid(4d12e540-83d7-11d5-90ed-0010a4e73d9a)] +interface nsIDataType : nsISupports +{ + // These MUST match the declarations in xpt_struct.h. + // Otherwise the world is likely to explode. + // From xpt_struct.h ... + const uint16_t VTYPE_INT8 = 0; // TD_INT8 = 0, + const uint16_t VTYPE_INT16 = 1; // TD_INT16 = 1, + const uint16_t VTYPE_INT32 = 2; // TD_INT32 = 2, + const uint16_t VTYPE_INT64 = 3; // TD_INT64 = 3, + const uint16_t VTYPE_UINT8 = 4; // TD_UINT8 = 4, + const uint16_t VTYPE_UINT16 = 5; // TD_UINT16 = 5, + const uint16_t VTYPE_UINT32 = 6; // TD_UINT32 = 6, + const uint16_t VTYPE_UINT64 = 7; // TD_UINT64 = 7, + const uint16_t VTYPE_FLOAT = 8; // TD_FLOAT = 8, + const uint16_t VTYPE_DOUBLE = 9; // TD_DOUBLE = 9, + const uint16_t VTYPE_BOOL = 10; // TD_BOOL = 10, + const uint16_t VTYPE_CHAR = 11; // TD_CHAR = 11, + const uint16_t VTYPE_WCHAR = 12; // TD_WCHAR = 12, + const uint16_t VTYPE_VOID = 13; // TD_VOID = 13, + const uint16_t VTYPE_ID = 14; // TD_PNSIID = 14, + const uint16_t VTYPE_DOMSTRING = 15; // TD_DOMSTRING = 15, + const uint16_t VTYPE_CHAR_STR = 16; // TD_PSTRING = 16, + const uint16_t VTYPE_WCHAR_STR = 17; // TD_PWSTRING = 17, + const uint16_t VTYPE_INTERFACE = 18; // TD_INTERFACE_TYPE = 18, + const uint16_t VTYPE_INTERFACE_IS = 19; // TD_INTERFACE_IS_TYPE = 19, + const uint16_t VTYPE_ARRAY = 20; // TD_ARRAY = 20, + const uint16_t VTYPE_STRING_SIZE_IS = 21; // TD_PSTRING_SIZE_IS = 21, + const uint16_t VTYPE_WSTRING_SIZE_IS = 22; // TD_PWSTRING_SIZE_IS = 22, + const uint16_t VTYPE_UTF8STRING = 23; // TD_UTF8STRING = 23, + const uint16_t VTYPE_CSTRING = 24; // TD_CSTRING = 24, + const uint16_t VTYPE_ASTRING = 25; // TD_ASTRING = 25, + const uint16_t VTYPE_EMPTY_ARRAY = 254; + const uint16_t VTYPE_EMPTY = 255; +}; + +/** + * XPConnect has magic to transparently convert between nsIVariant and JS types. + * We mark the interface [scriptable] so that JS can use methods + * that refer to this interface. But we mark all the methods and attributes + * [noscript] since any nsIVariant object will be automatically converted to a + * JS type anyway. + */ + +[scriptable, uuid(81e4c2de-acac-4ad6-901a-b5fb1b851a0d)] +interface nsIVariant : nsISupports +{ + [noscript] readonly attribute uint16_t dataType; + + [noscript] uint8_t getAsInt8(); + [noscript] int16_t getAsInt16(); + [noscript] int32_t getAsInt32(); + [noscript] int64_t getAsInt64(); + [noscript] uint8_t getAsUint8(); + [noscript] uint16_t getAsUint16(); + [noscript] uint32_t getAsUint32(); + [noscript] uint64_t getAsUint64(); + [noscript] float getAsFloat(); + [noscript] double getAsDouble(); + [noscript] boolean getAsBool(); + [noscript] char getAsChar(); + [noscript] wchar getAsWChar(); + [notxpcom] nsresult getAsID(out nsID retval); + [noscript] AString getAsAString(); + [noscript] DOMString getAsDOMString(); + [noscript] ACString getAsACString(); + [noscript] AUTF8String getAsAUTF8String(); + [noscript] string getAsString(); + [noscript] wstring getAsWString(); + [noscript] nsISupports getAsISupports(); + [noscript] jsval getAsJSVal(); + + [noscript] void getAsInterface(out nsIIDPtr iid, + [iid_is(iid), retval] out nsQIResult iface); + + [notxpcom] nsresult getAsArray(out uint16_t type, out nsIID iid, + out uint32_t count, out voidPtr ptr); + + [noscript] void getAsStringWithSize(out uint32_t size, + [size_is(size), retval] out string str); + + [noscript] void getAsWStringWithSize(out uint32_t size, + [size_is(size), retval] out wstring str); +}; + +/** + * An object that implements nsIVariant may or may NOT also implement this + * nsIWritableVariant. + * + * If the 'writable' attribute is false then attempts to call any of the 'set' + * methods can be expected to fail. Setting the 'writable' attribute may or + * may not succeed. + * + */ + +[scriptable, uuid(5586a590-8c82-11d5-90f3-0010a4e73d9a)] +interface nsIWritableVariant : nsIVariant +{ + attribute boolean writable; + + void setAsInt8(in uint8_t aValue); + void setAsInt16(in int16_t aValue); + void setAsInt32(in int32_t aValue); + void setAsInt64(in int64_t aValue); + void setAsUint8(in uint8_t aValue); + void setAsUint16(in uint16_t aValue); + void setAsUint32(in uint32_t aValue); + void setAsUint64(in uint64_t aValue); + void setAsFloat(in float aValue); + void setAsDouble(in double aValue); + void setAsBool(in boolean aValue); + void setAsChar(in char aValue); + void setAsWChar(in wchar aValue); + void setAsID(in nsIDRef aValue); + void setAsAString(in AString aValue); + void setAsDOMString(in DOMString aValue); + void setAsACString(in ACString aValue); + void setAsAUTF8String(in AUTF8String aValue); + void setAsString(in string aValue); + void setAsWString(in wstring aValue); + void setAsISupports(in nsISupports aValue); + + void setAsInterface(in nsIIDRef iid, + [iid_is(iid)] in nsQIResult iface); + + [noscript] void setAsArray(in uint16_t type, in nsIIDPtr iid, + in uint32_t count, in voidPtr ptr); + + void setAsStringWithSize(in uint32_t size, + [size_is(size)] in string str); + + void setAsWStringWithSize(in uint32_t size, + [size_is(size)] in wstring str); + + void setAsVoid(); + void setAsEmpty(); + void setAsEmptyArray(); + + void setFromVariant(in nsIVariant aValue); +}; + +%{C++ +// The contractID for the generic implementation built in to xpcom. +#define NS_VARIANT_CONTRACTID "@mozilla.org/variant;1" +%} diff --git a/xpcom/ds/nsIWindowsRegKey.idl b/xpcom/ds/nsIWindowsRegKey.idl new file mode 100644 index 000000000..25be0995b --- /dev/null +++ b/xpcom/ds/nsIWindowsRegKey.idl @@ -0,0 +1,332 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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" + +native HKEY(HKEY); + +/** + * This interface is designed to provide scriptable access to the Windows + * registry system ("With Great Power Comes Great Responsibility"). The + * interface represents a single key in the registry. + * + * This interface is highly Win32 specific. + */ +[scriptable, uuid(2555b930-d64f-437e-9be7-0a2cb252c1f4)] +interface nsIWindowsRegKey : nsISupports +{ + /** + * Root keys. The values for these keys correspond to the values from + * WinReg.h in the MS Platform SDK. The ROOT_KEY_ prefix corresponds to the + * HKEY_ prefix in the MS Platform SDK. + * + * This interface is not restricted to using only these root keys. + */ + const unsigned long ROOT_KEY_CLASSES_ROOT = 0x80000000; + const unsigned long ROOT_KEY_CURRENT_USER = 0x80000001; + const unsigned long ROOT_KEY_LOCAL_MACHINE = 0x80000002; + + /** + * Values for the mode parameter passed to the open and create methods. + * The values defined here correspond to the REGSAM values defined in + * WinNT.h in the MS Platform SDK, where ACCESS_ is replaced with KEY_. + * + * This interface is not restricted to using only these access types. + */ + const unsigned long ACCESS_BASIC = 0x00020000; + const unsigned long ACCESS_QUERY_VALUE = 0x00000001; + const unsigned long ACCESS_SET_VALUE = 0x00000002; + const unsigned long ACCESS_CREATE_SUB_KEY = 0x00000004; + const unsigned long ACCESS_ENUMERATE_SUB_KEYS = 0x00000008; + const unsigned long ACCESS_NOTIFY = 0x00000010; + const unsigned long ACCESS_READ = ACCESS_BASIC | + ACCESS_QUERY_VALUE | + ACCESS_ENUMERATE_SUB_KEYS | + ACCESS_NOTIFY; + const unsigned long ACCESS_WRITE = ACCESS_BASIC | + ACCESS_SET_VALUE | + ACCESS_CREATE_SUB_KEY; + const unsigned long ACCESS_ALL = ACCESS_READ | + ACCESS_WRITE; + const unsigned long WOW64_32 = 0x00000200; + const unsigned long WOW64_64 = 0x00000100; + + + /** + * Values for the type of a registry value. The numeric values of these + * constants are taken directly from WinNT.h in the MS Platform SDK. + * The Microsoft documentation should be consulted for the exact meaning of + * these value types. + * + * This interface is somewhat restricted to using only these value types. + * There is no method that is directly equivalent to RegQueryValueEx or + * RegSetValueEx. In particular, this interface does not support the + * REG_MULTI_SZ and REG_EXPAND_SZ value types. It is still possible to + * enumerate values that have other types (i.e., getValueType may return a + * type not defined below). + */ + const unsigned long TYPE_NONE = 0; // REG_NONE + const unsigned long TYPE_STRING = 1; // REG_SZ + const unsigned long TYPE_BINARY = 3; // REG_BINARY + const unsigned long TYPE_INT = 4; // REG_DWORD + const unsigned long TYPE_INT64 = 11; // REG_QWORD + + /** + * This attribute exposes the native HKEY and is available to provide C++ + * consumers with the flexibility of making other Windows registry API calls + * that are not exposed via this interface. + * + * It is possible to initialize this object by setting an HKEY on it. In + * that case, it is the responsibility of the consumer setting the HKEY to + * ensure that it is a valid HKEY. + * + * WARNING: Setting the key does not close the old key. + */ + [noscript] attribute HKEY key; + + /** + * This method closes the key. If the key is already closed, then this + * method does nothing. + */ + void close(); + + /** + * This method opens an existing key. This method fails if the key + * does not exist. + * + * NOTE: On 32-bit Windows, it is valid to pass any HKEY as the rootKey + * parameter of this function. However, for compatibility with 64-bit + * Windows, that usage should probably be avoided in favor of openChild. + * + * @param rootKey + * A root key defined above or any valid HKEY on 32-bit Windows. + * @param relPath + * A relative path from the given root key. + * @param mode + * Access mode, which is a bit-wise OR of the ACCESS_ values defined + * above. + */ + void open(in unsigned long rootKey, in AString relPath, in unsigned long mode); + + /** + * This method opens an existing key or creates a new key. + * + * NOTE: On 32-bit Windows, it is valid to pass any HKEY as the rootKey + * parameter of this function. However, for compatibility with 64-bit + * Windows, that usage should probably be avoided in favor of createChild. + * + * @param rootKey + * A root key defined above or any valid HKEY on 32-bit Windows. + * @param relPath + * A relative path from the given root key. + * @param mode + * Access mode, which is a bit-wise OR of the ACCESS_ values defined + * above. + */ + void create(in unsigned long rootKey, in AString relPath, in unsigned long mode); + + /** + * This method opens a subkey relative to this key. This method fails if the + * key does not exist. + * + * @return nsIWindowsRegKey for the newly opened subkey. + */ + nsIWindowsRegKey openChild(in AString relPath, in unsigned long mode); + + /** + * This method opens or creates a subkey relative to this key. + * + * @return nsIWindowsRegKey for the newly opened or created subkey. + */ + nsIWindowsRegKey createChild(in AString relPath, in unsigned long mode); + + /** + * This attribute returns the number of child keys. + */ + readonly attribute unsigned long childCount; + + /** + * This method returns the name of the n'th child key. + * + * @param index + * The index of the requested child key. + */ + AString getChildName(in unsigned long index); + + /** + * This method checks to see if the key has a child by the given name. + * + * @param name + * The name of the requested child key. + */ + boolean hasChild(in AString name); + + /** + * This attribute returns the number of values under this key. + */ + readonly attribute unsigned long valueCount; + + /** + * This method returns the name of the n'th value under this key. + * + * @param index + * The index of the requested value. + */ + AString getValueName(in unsigned long index); + + /** + * This method checks to see if the key has a value by the given name. + * + * @param name + * The name of the requested value. + */ + boolean hasValue(in AString name); + + /** + * This method removes a child key and all of its values. This method will + * fail if the key has any children of its own. + * + * @param relPath + * The relative path from this key to the key to be removed. + */ + void removeChild(in AString relPath); + + /** + * This method removes the value with the given name. + * + * @param name + * The name of the value to be removed. + */ + void removeValue(in AString name); + + /** + * This method returns the type of the value with the given name. The return + * value is one of the "TYPE_" constants defined above. + * + * @param name + * The name of the value to query. + */ + unsigned long getValueType(in AString name); + + /** + * This method reads the string contents of the named value as a Unicode + * string. + * + * @param name + * The name of the value to query. This parameter can be the empty + * string to request the key's default value. + */ + AString readStringValue(in AString name); + + /** + * This method reads the integer contents of the named value. + * + * @param name + * The name of the value to query. + */ + unsigned long readIntValue(in AString name); + + /** + * This method reads the 64-bit integer contents of the named value. + * + * @param name + * The name of the value to query. + */ + unsigned long long readInt64Value(in AString name); + + /** + * This method reads the binary contents of the named value under this key. + * + * JavaScript callers should take care with the result of this method since + * it will be byte-expanded to form a JS string. (The binary data will be + * treated as an ISO-Latin-1 character string, which it is not). + * + * @param name + * The name of the value to query. + */ + ACString readBinaryValue(in AString name); + + /** + * This method writes the unicode string contents of the named value. The + * value will be created if it does not already exist. + * + * @param name + * The name of the value to modify. This parameter can be the empty + * string to modify the key's default value. + * @param data + * The data for the value to modify. + */ + void writeStringValue(in AString name, in AString data); + + /** + * This method writes the integer contents of the named value. The value + * will be created if it does not already exist. + * + * @param name + * The name of the value to modify. + * @param data + * The data for the value to modify. + */ + void writeIntValue(in AString name, in unsigned long data); + + /** + * This method writes the 64-bit integer contents of the named value. The + * value will be created if it does not already exist. + * + * @param name + * The name of the value to modify. + * @param data + * The data for the value to modify. + */ + void writeInt64Value(in AString name, in unsigned long long data); + + /** + * This method writes the binary contents of the named value. The value will + * be created if it does not already exist. + * + * JavaScript callers should take care with the value passed to this method + * since it will be truncated from a JS string (unicode) to a ISO-Latin-1 + * string. (The binary data will be treated as an ISO-Latin-1 character + * string, which it is not). So, JavaScript callers should only pass + * character values in the range \u0000 to \u00FF, or else data loss will + * occur. + * + * @param name + * The name of the value to modify. + * @param data + * The data for the value to modify. + */ + void writeBinaryValue(in AString name, in ACString data); + + /** + * This method starts watching the key to see if any of its values have + * changed. The key must have been opened with mode including ACCESS_NOTIFY. + * If recurse is true, then this key and any of its descendant keys are + * watched. Otherwise, only this key is watched. + * + * @param recurse + * Indicates whether or not to also watch child keys. + */ + void startWatching(in boolean recurse); + + /** + * This method stops any watching of the key initiated by a call to + * startWatching. This method does nothing if the key is not being watched. + */ + void stopWatching(); + + /** + * This method returns true if the key is being watched for changes (i.e., + * if startWatching() was called). + */ + boolean isWatching(); + + /** + * This method returns true if the key has changed and false otherwise. + * This method will always return false if startWatching was not called. + */ + boolean hasChanged(); +}; diff --git a/xpcom/ds/nsIWritablePropertyBag.idl b/xpcom/ds/nsIWritablePropertyBag.idl new file mode 100644 index 000000000..e916b7ccd --- /dev/null +++ b/xpcom/ds/nsIWritablePropertyBag.idl @@ -0,0 +1,27 @@ +/* -*- Mode: IDL; 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/. */ + +/* nsIVariant based writable Property Bag support. */ + +#include "nsIPropertyBag.idl" + +[scriptable, uuid(96fc4671-eeb4-4823-9421-e50fb70ad353)] +interface nsIWritablePropertyBag : nsIPropertyBag +{ + /** + * Set a property with the given name to the given value. If + * a property already exists with the given name, it is + * overwritten. + */ + void setProperty(in AString name, in nsIVariant value); + + /** + * Delete a property with the given name. + * @throws NS_ERROR_FAILURE if a property with that name doesn't + * exist. + */ + void deleteProperty(in AString name); +}; diff --git a/xpcom/ds/nsIWritablePropertyBag2.idl b/xpcom/ds/nsIWritablePropertyBag2.idl new file mode 100644 index 000000000..50f093905 --- /dev/null +++ b/xpcom/ds/nsIWritablePropertyBag2.idl @@ -0,0 +1,22 @@ +/* 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/. */ + +/* nsIVariant based Property Bag support. */ + +#include "nsIPropertyBag2.idl" + +[scriptable, uuid(9cfd1587-360e-4957-a58f-4c2b1c5e7ed9)] +interface nsIWritablePropertyBag2 : nsIPropertyBag2 +{ + void setPropertyAsInt32 (in AString prop, in int32_t value); + void setPropertyAsUint32 (in AString prop, in uint32_t value); + void setPropertyAsInt64 (in AString prop, in int64_t value); + void setPropertyAsUint64 (in AString prop, in uint64_t value); + void setPropertyAsDouble (in AString prop, in double value); + void setPropertyAsAString (in AString prop, in AString value); + void setPropertyAsACString (in AString prop, in ACString value); + void setPropertyAsAUTF8String (in AString prop, in AUTF8String value); + void setPropertyAsBool (in AString prop, in boolean value); + void setPropertyAsInterface (in AString prop, in nsISupports value); +}; diff --git a/xpcom/ds/nsMathUtils.h b/xpcom/ds/nsMathUtils.h new file mode 100644 index 000000000..b10b8144e --- /dev/null +++ b/xpcom/ds/nsMathUtils.h @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsMathUtils_h__ +#define nsMathUtils_h__ + +#include "nscore.h" +#include <cmath> +#include <float.h> + +#ifdef SOLARIS +#include <ieeefp.h> +#endif + +/* + * round + */ +inline double +NS_round(double aNum) +{ + return aNum >= 0.0 ? floor(aNum + 0.5) : ceil(aNum - 0.5); +} +inline float +NS_roundf(float aNum) +{ + return aNum >= 0.0f ? floorf(aNum + 0.5f) : ceilf(aNum - 0.5f); +} +inline int32_t +NS_lround(double aNum) +{ + return aNum >= 0.0 ? int32_t(aNum + 0.5) : int32_t(aNum - 0.5); +} + +/* NS_roundup30 rounds towards infinity for positive and */ +/* negative numbers. */ + +#if defined(XP_WIN32) && defined(_M_IX86) && !defined(__GNUC__) && !defined(__clang__) +inline int32_t NS_lroundup30(float x) +{ + /* Code derived from Laurent de Soras' paper at */ + /* http://ldesoras.free.fr/doc/articles/rounding_en.pdf */ + + /* Rounding up on Windows is expensive using the float to */ + /* int conversion and the floor function. A faster */ + /* approach is to use f87 rounding while assuming the */ + /* default rounding mode of rounding to the nearest */ + /* integer. This rounding mode, however, actually rounds */ + /* to the nearest integer so we add the floating point */ + /* number to itself and add our rounding factor before */ + /* doing the conversion to an integer. We then do a right */ + /* shift of one bit on the integer to divide by two. */ + + /* This routine doesn't handle numbers larger in magnitude */ + /* than 2^30 but this is fine for NSToCoordRound because */ + /* Coords are limited to 2^30 in magnitude. */ + + static const double round_to_nearest = 0.5f; + int i; + + __asm { + fld x ; load fp argument + fadd st, st(0) ; double it + fadd round_to_nearest ; add the rounding factor + fistp dword ptr i ; convert the result to int + } + return i >> 1; /* divide by 2 */ +} +#endif /* XP_WIN32 && _M_IX86 && !__GNUC__ */ + +inline int32_t +NS_lroundf(float aNum) +{ + return aNum >= 0.0f ? int32_t(aNum + 0.5f) : int32_t(aNum - 0.5f); +} + +/* + * hypot. We don't need a super accurate version of this, if a platform + * turns up with none of the possibilities below it would be okay to fall + * back to sqrt(x*x + y*y). + */ +inline double +NS_hypot(double aNum1, double aNum2) +{ +#ifdef __GNUC__ + return __builtin_hypot(aNum1, aNum2); +#elif defined _WIN32 + return _hypot(aNum1, aNum2); +#else + return hypot(aNum1, aNum2); +#endif +} + +/** + * Check whether a floating point number is finite (not +/-infinity and not a + * NaN value). + */ +inline bool +NS_finite(double aNum) +{ +#ifdef WIN32 + // NOTE: '!!' casts an int to bool without spamming MSVC warning C4800. + return !!_finite(aNum); +#elif defined(XP_DARWIN) + // Darwin has deprecated |finite| and recommends |isfinite|. The former is + // not present in the iOS SDK. + return std::isfinite(aNum); +#else + return finite(aNum); +#endif +} + +/** + * Returns the result of the modulo of x by y using a floored division. + * fmod(x, y) is using a truncated division. + * The main difference is that the result of this method will have the sign of + * y while the result of fmod(x, y) will have the sign of x. + */ +inline double +NS_floorModulo(double aNum1, double aNum2) +{ + return (aNum1 - aNum2 * floor(aNum1 / aNum2)); +} + +#endif diff --git a/xpcom/ds/nsObserverList.cpp b/xpcom/ds/nsObserverList.cpp new file mode 100644 index 000000000..fef8831b8 --- /dev/null +++ b/xpcom/ds/nsObserverList.cpp @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsObserverList.h" + +#include "nsAutoPtr.h" +#include "nsCOMArray.h" +#include "nsISimpleEnumerator.h" +#include "xpcpublic.h" + +nsresult +nsObserverList::AddObserver(nsIObserver* anObserver, bool ownsWeak) +{ + NS_ASSERTION(anObserver, "Null input"); + + if (!ownsWeak) { + ObserverRef* o = mObservers.AppendElement(anObserver); + if (!o) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; + } + + nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(anObserver); + if (!weak) { + return NS_NOINTERFACE; + } + + ObserverRef* o = mObservers.AppendElement(weak); + if (!o) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +nsresult +nsObserverList::RemoveObserver(nsIObserver* anObserver) +{ + NS_ASSERTION(anObserver, "Null input"); + + if (mObservers.RemoveElement(static_cast<nsISupports*>(anObserver))) { + return NS_OK; + } + + nsCOMPtr<nsIWeakReference> observerRef = do_GetWeakReference(anObserver); + if (!observerRef) { + return NS_ERROR_FAILURE; + } + + if (!mObservers.RemoveElement(observerRef)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +nsObserverList::GetObserverList(nsISimpleEnumerator** anEnumerator) +{ + RefPtr<nsObserverEnumerator> e(new nsObserverEnumerator(this)); + e.forget(anEnumerator); +} + +void +nsObserverList::FillObserverArray(nsCOMArray<nsIObserver>& aArray) +{ + aArray.SetCapacity(mObservers.Length()); + + nsTArray<ObserverRef> observers(mObservers); + + for (int32_t i = observers.Length() - 1; i >= 0; --i) { + if (observers[i].isWeakRef) { + nsCOMPtr<nsIObserver> o(do_QueryReferent(observers[i].asWeak())); + if (o) { + aArray.AppendObject(o); + } else { + // the object has gone away, remove the weakref + mObservers.RemoveElement(observers[i].asWeak()); + } + } else { + aArray.AppendObject(observers[i].asObserver()); + } + } +} + +void +nsObserverList::AppendStrongObservers(nsCOMArray<nsIObserver>& aArray) +{ + aArray.SetCapacity(aArray.Length() + mObservers.Length()); + + for (int32_t i = mObservers.Length() - 1; i >= 0; --i) { + if (!mObservers[i].isWeakRef) { + aArray.AppendObject(mObservers[i].asObserver()); + } + } +} + +void +nsObserverList::NotifyObservers(nsISupports* aSubject, + const char* aTopic, + const char16_t* someData) +{ + nsCOMArray<nsIObserver> observers; + FillObserverArray(observers); + + for (int32_t i = 0; i < observers.Count(); ++i) { + observers[i]->Observe(aSubject, aTopic, someData); + } +} + +NS_IMPL_ISUPPORTS(nsObserverEnumerator, nsISimpleEnumerator) + +nsObserverEnumerator::nsObserverEnumerator(nsObserverList* aObserverList) + : mIndex(0) +{ + aObserverList->FillObserverArray(mObservers); +} + +NS_IMETHODIMP +nsObserverEnumerator::HasMoreElements(bool* aResult) +{ + *aResult = (mIndex < mObservers.Count()); + return NS_OK; +} + +NS_IMETHODIMP +nsObserverEnumerator::GetNext(nsISupports** aResult) +{ + if (mIndex == mObservers.Count()) { + NS_ERROR("Enumerating after HasMoreElements returned false."); + return NS_ERROR_UNEXPECTED; + } + + NS_ADDREF(*aResult = mObservers[mIndex]); + ++mIndex; + return NS_OK; +} diff --git a/xpcom/ds/nsObserverList.h b/xpcom/ds/nsObserverList.h new file mode 100644 index 000000000..90a685ea6 --- /dev/null +++ b/xpcom/ds/nsObserverList.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsObserverList_h___ +#define nsObserverList_h___ + +#include "nsISupports.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIObserver.h" +#include "nsIWeakReference.h" +#include "nsHashKeys.h" +#include "nsISimpleEnumerator.h" +#include "mozilla/Attributes.h" + +struct ObserverRef +{ + ObserverRef(const ObserverRef& aO) : isWeakRef(aO.isWeakRef), ref(aO.ref) {} + explicit ObserverRef(nsIObserver* aObserver) : isWeakRef(false), ref(aObserver) {} + explicit ObserverRef(nsIWeakReference* aWeak) : isWeakRef(true), ref(aWeak) {} + + bool isWeakRef; + nsCOMPtr<nsISupports> ref; + + nsIObserver* asObserver() + { + NS_ASSERTION(!isWeakRef, "Isn't a strong ref."); + return static_cast<nsIObserver*>((nsISupports*)ref); + } + + nsIWeakReference* asWeak() + { + NS_ASSERTION(isWeakRef, "Isn't a weak ref."); + return static_cast<nsIWeakReference*>((nsISupports*)ref); + } + + bool operator==(nsISupports* aRhs) const { return ref == aRhs; } +}; + +class nsObserverList : public nsCharPtrHashKey +{ + friend class nsObserverService; + +public: + explicit nsObserverList(const char* aKey) : nsCharPtrHashKey(aKey) + { + MOZ_COUNT_CTOR(nsObserverList); + } + + ~nsObserverList() + { + MOZ_COUNT_DTOR(nsObserverList); + } + + MOZ_MUST_USE nsresult AddObserver(nsIObserver* aObserver, bool aOwnsWeak); + MOZ_MUST_USE nsresult RemoveObserver(nsIObserver* aObserver); + + void NotifyObservers(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData); + void GetObserverList(nsISimpleEnumerator** aEnumerator); + + // Fill an array with the observers of this category. + // The array is filled in last-added-first order. + void FillObserverArray(nsCOMArray<nsIObserver>& aArray); + + // Like FillObserverArray(), but only for strongly held observers. + void AppendStrongObservers(nsCOMArray<nsIObserver>& aArray); + +private: + nsTArray<ObserverRef> mObservers; +}; + +class nsObserverEnumerator final : public nsISimpleEnumerator +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + + explicit nsObserverEnumerator(nsObserverList* aObserverList); + +private: + ~nsObserverEnumerator() {} + + int32_t mIndex; // Counts up from 0 + nsCOMArray<nsIObserver> mObservers; +}; + +#endif /* nsObserverList_h___ */ diff --git a/xpcom/ds/nsObserverService.cpp b/xpcom/ds/nsObserverService.cpp new file mode 100644 index 000000000..23cc54fa7 --- /dev/null +++ b/xpcom/ds/nsObserverService.cpp @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/Logging.h" +#include "nsAutoPtr.h" +#include "nsIConsoleService.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsIScriptError.h" +#include "nsObserverService.h" +#include "nsObserverList.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsEnumeratorUtils.h" +#include "xpcpublic.h" +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/Services.h" + +#define NOTIFY_GLOBAL_OBSERVERS + +// Log module for nsObserverService logging... +// +// To enable logging (see prlog.h for full details): +// +// set MOZ_LOG=ObserverService:5 +// set MOZ_LOG_FILE=service.log +// +// This enables LogLevel::Debug level information and places all output in +// the file service.log. +static mozilla::LazyLogModule sObserverServiceLog("ObserverService"); +#define LOG(x) MOZ_LOG(sObserverServiceLog, mozilla::LogLevel::Debug, x) + +using namespace mozilla; + +NS_IMETHODIMP +nsObserverService::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) +{ + struct SuspectObserver + { + SuspectObserver(const char* aTopic, size_t aReferentCount) + : mTopic(aTopic) + , mReferentCount(aReferentCount) + {} + const char* mTopic; + size_t mReferentCount; + }; + + size_t totalNumStrong = 0; + size_t totalNumWeakAlive = 0; + size_t totalNumWeakDead = 0; + nsTArray<SuspectObserver> suspectObservers; + + for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) { + nsObserverList* observerList = iter.Get(); + if (!observerList) { + continue; + } + + size_t topicNumStrong = 0; + size_t topicNumWeakAlive = 0; + size_t topicNumWeakDead = 0; + + nsTArray<ObserverRef>& observers = observerList->mObservers; + for (uint32_t i = 0; i < observers.Length(); i++) { + if (observers[i].isWeakRef) { + nsCOMPtr<nsIObserver> observerRef( + do_QueryReferent(observers[i].asWeak())); + if (observerRef) { + topicNumWeakAlive++; + } else { + topicNumWeakDead++; + } + } else { + topicNumStrong++; + } + } + + totalNumStrong += topicNumStrong; + totalNumWeakAlive += topicNumWeakAlive; + totalNumWeakDead += topicNumWeakDead; + + // Keep track of topics that have a suspiciously large number + // of referents (symptom of leaks). + size_t topicTotal = topicNumStrong + topicNumWeakAlive + topicNumWeakDead; + if (topicTotal > kSuspectReferentCount) { + SuspectObserver suspect(observerList->GetKey(), topicTotal); + suspectObservers.AppendElement(suspect); + } + } + + // These aren't privacy-sensitive and so don't need anonymizing. + for (uint32_t i = 0; i < suspectObservers.Length(); i++) { + SuspectObserver& suspect = suspectObservers[i]; + nsPrintfCString suspectPath("observer-service-suspect/referent(topic=%s)", + suspect.mTopic); + aHandleReport->Callback( + /* process */ EmptyCString(), + suspectPath, KIND_OTHER, UNITS_COUNT, suspect.mReferentCount, + NS_LITERAL_CSTRING("A topic with a suspiciously large number of " + "referents. This may be symptomatic of a leak " + "if the number of referents is high with " + "respect to the number of windows."), + aData); + } + + MOZ_COLLECT_REPORT( + "observer-service/referent/strong", KIND_OTHER, UNITS_COUNT, + totalNumStrong, + "The number of strong references held by the observer service."); + + MOZ_COLLECT_REPORT( + "observer-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT, + totalNumWeakAlive, + "The number of weak references held by the observer service that are " + "still alive."); + + MOZ_COLLECT_REPORT( + "observer-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT, + totalNumWeakDead, + "The number of weak references held by the observer service that are " + "dead."); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsObserverService Implementation + +NS_IMPL_ISUPPORTS(nsObserverService, + nsIObserverService, + nsObserverService, + nsIMemoryReporter) + +nsObserverService::nsObserverService() + : mShuttingDown(false) +{ +} + +nsObserverService::~nsObserverService(void) +{ + Shutdown(); +} + +void +nsObserverService::RegisterReporter() +{ + RegisterWeakMemoryReporter(this); +} + +void +nsObserverService::Shutdown() +{ + UnregisterWeakMemoryReporter(this); + + mShuttingDown = true; + + mObserverTopicTable.Clear(); +} + +nsresult +nsObserverService::Create(nsISupports* aOuter, const nsIID& aIID, + void** aInstancePtr) +{ + LOG(("nsObserverService::Create()")); + + RefPtr<nsObserverService> os = new nsObserverService(); + + if (!os) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // The memory reporter can not be immediately registered here because + // the nsMemoryReporterManager may attempt to get the nsObserverService + // during initialization, causing a recursive GetService. + NS_DispatchToCurrentThread(NewRunnableMethod(os, &nsObserverService::RegisterReporter)); + + return os->QueryInterface(aIID, aInstancePtr); +} + +#define NS_ENSURE_VALIDCALL \ + if (!NS_IsMainThread()) { \ + MOZ_CRASH("Using observer service off the main thread!"); \ + return NS_ERROR_UNEXPECTED; \ + } \ + if (mShuttingDown) { \ + NS_ERROR("Using observer service after XPCOM shutdown!"); \ + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; \ + } + +NS_IMETHODIMP +nsObserverService::AddObserver(nsIObserver* aObserver, const char* aTopic, + bool aOwnsWeak) +{ + LOG(("nsObserverService::AddObserver(%p: %s)", + (void*)aObserver, aTopic)); + + NS_ENSURE_VALIDCALL + if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + // Specifically allow http-on-opening-request in the child process; + // see bug 1269765. + if (mozilla::net::IsNeckoChild() && !strncmp(aTopic, "http-on-", 8) && + strcmp(aTopic, "http-on-opening-request")) { + nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + error->Init(NS_LITERAL_STRING("http-on-* observers only work in the parent process"), + EmptyString(), EmptyString(), 0, 0, + nsIScriptError::warningFlag, "chrome javascript"); + console->LogMessage(error); + + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsObserverList* observerList = mObserverTopicTable.PutEntry(aTopic); + if (!observerList) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return observerList->AddObserver(aObserver, aOwnsWeak); +} + +NS_IMETHODIMP +nsObserverService::RemoveObserver(nsIObserver* aObserver, const char* aTopic) +{ + LOG(("nsObserverService::RemoveObserver(%p: %s)", + (void*)aObserver, aTopic)); + NS_ENSURE_VALIDCALL + if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic); + if (!observerList) { + return NS_ERROR_FAILURE; + } + + /* This death grip is to protect against stupid consumers who call + RemoveObserver from their Destructor, see bug 485834/bug 325392. */ + nsCOMPtr<nsIObserver> kungFuDeathGrip(aObserver); + return observerList->RemoveObserver(aObserver); +} + +NS_IMETHODIMP +nsObserverService::EnumerateObservers(const char* aTopic, + nsISimpleEnumerator** anEnumerator) +{ + NS_ENSURE_VALIDCALL + if (NS_WARN_IF(!anEnumerator) || NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic); + if (!observerList) { + return NS_NewEmptyEnumerator(anEnumerator); + } + + observerList->GetObserverList(anEnumerator); + return NS_OK; +} + +// Enumerate observers of aTopic and call Observe on each. +NS_IMETHODIMP nsObserverService::NotifyObservers(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData) +{ + LOG(("nsObserverService::NotifyObservers(%s)", aTopic)); + + NS_ENSURE_VALIDCALL + if (NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic); + if (observerList) { + observerList->NotifyObservers(aSubject, aTopic, aSomeData); + } + +#ifdef NOTIFY_GLOBAL_OBSERVERS + observerList = mObserverTopicTable.GetEntry("*"); + if (observerList) { + observerList->NotifyObservers(aSubject, aTopic, aSomeData); + } +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsObserverService::UnmarkGrayStrongObservers() +{ + NS_ENSURE_VALIDCALL + + nsCOMArray<nsIObserver> strongObservers; + for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) { + nsObserverList* aObserverList = iter.Get(); + if (aObserverList) { + aObserverList->AppendStrongObservers(strongObservers); + } + } + + for (uint32_t i = 0; i < strongObservers.Length(); ++i) { + xpc_TryUnmarkWrappedGrayObject(strongObservers[i]); + } + + return NS_OK; +} diff --git a/xpcom/ds/nsObserverService.h b/xpcom/ds/nsObserverService.h new file mode 100644 index 000000000..fa0d9b9d8 --- /dev/null +++ b/xpcom/ds/nsObserverService.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsObserverService_h___ +#define nsObserverService_h___ + +#include "nsIObserverService.h" +#include "nsObserverList.h" +#include "nsIMemoryReporter.h" +#include "nsTHashtable.h" +#include "mozilla/Attributes.h" + +// {D07F5195-E3D1-11d2-8ACD-00105A1B8860} +#define NS_OBSERVERSERVICE_CID \ + { 0xd07f5195, 0xe3d1, 0x11d2, { 0x8a, 0xcd, 0x0, 0x10, 0x5a, 0x1b, 0x88, 0x60 } } + +class nsIMemoryReporter; + +class nsObserverService final + : public nsIObserverService + , public nsIMemoryReporter +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_OBSERVERSERVICE_CID) + + nsObserverService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVERSERVICE + NS_DECL_NSIMEMORYREPORTER + + void Shutdown(); + + static MOZ_MUST_USE nsresult Create(nsISupports* aOuter, const nsIID& aIID, + void** aInstancePtr); + + // Unmark any strongly held observers implemented in JS so the cycle + // collector will not traverse them. + NS_IMETHOD UnmarkGrayStrongObservers(); + +private: + ~nsObserverService(void); + void RegisterReporter(); + + static const size_t kSuspectReferentCount = 100; + bool mShuttingDown; + nsTHashtable<nsObserverList> mObserverTopicTable; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsObserverService, NS_OBSERVERSERVICE_CID) + +#endif /* nsObserverService_h___ */ diff --git a/xpcom/ds/nsPersistentProperties.cpp b/xpcom/ds/nsPersistentProperties.cpp new file mode 100644 index 000000000..ea925874c --- /dev/null +++ b/xpcom/ds/nsPersistentProperties.cpp @@ -0,0 +1,666 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsArrayEnumerator.h" +#include "nsID.h" +#include "nsCOMArray.h" +#include "nsUnicharInputStream.h" +#include "nsPrintfCString.h" +#include "nsAutoPtr.h" + +#define PL_ARENA_CONST_ALIGN_MASK 3 +#include "nsPersistentProperties.h" +#include "nsIProperties.h" + +struct PropertyTableEntry : public PLDHashEntryHdr +{ + // both of these are arena-allocated + const char* mKey; + const char16_t* mValue; +}; + +static char16_t* +ArenaStrdup(const nsAFlatString& aString, PLArenaPool* aArena) +{ + void* mem; + // add one to include the null terminator + int32_t len = (aString.Length() + 1) * sizeof(char16_t); + PL_ARENA_ALLOCATE(mem, aArena, len); + NS_ASSERTION(mem, "Couldn't allocate space!\n"); + if (mem) { + memcpy(mem, aString.get(), len); + } + return static_cast<char16_t*>(mem); +} + +static char* +ArenaStrdup(const nsAFlatCString& aString, PLArenaPool* aArena) +{ + void* mem; + // add one to include the null terminator + int32_t len = (aString.Length() + 1) * sizeof(char); + PL_ARENA_ALLOCATE(mem, aArena, len); + NS_ASSERTION(mem, "Couldn't allocate space!\n"); + if (mem) { + memcpy(mem, aString.get(), len); + } + return static_cast<char*>(mem); +} + +static const struct PLDHashTableOps property_HashTableOps = { + PLDHashTable::HashStringKey, + PLDHashTable::MatchStringKey, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr, +}; + +// +// parser stuff +// +enum EParserState +{ + eParserState_AwaitingKey, + eParserState_Key, + eParserState_AwaitingValue, + eParserState_Value, + eParserState_Comment +}; + +enum EParserSpecial +{ + eParserSpecial_None, // not parsing a special character + eParserSpecial_Escaped, // awaiting a special character + eParserSpecial_Unicode // parsing a \Uxxx value +}; + +class MOZ_STACK_CLASS nsPropertiesParser +{ +public: + explicit nsPropertiesParser(nsIPersistentProperties* aProps) + : mHaveMultiLine(false) + , mState(eParserState_AwaitingKey) + , mProps(aProps) + { + } + + void FinishValueState(nsAString& aOldValue) + { + static const char trimThese[] = " \t"; + mKey.Trim(trimThese, false, true); + + // This is really ugly hack but it should be fast + char16_t backup_char; + uint32_t minLength = mMinLength; + if (minLength) { + backup_char = mValue[minLength - 1]; + mValue.SetCharAt('x', minLength - 1); + } + mValue.Trim(trimThese, false, true); + if (minLength) { + mValue.SetCharAt(backup_char, minLength - 1); + } + + mProps->SetStringProperty(NS_ConvertUTF16toUTF8(mKey), mValue, aOldValue); + mSpecialState = eParserSpecial_None; + WaitForKey(); + } + + EParserState GetState() { return mState; } + + static nsresult SegmentWriter(nsIUnicharInputStream* aStream, + void* aClosure, + const char16_t* aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t* aWriteCount); + + nsresult ParseBuffer(const char16_t* aBuffer, uint32_t aBufferLength); + +private: + bool ParseValueCharacter( + char16_t aChar, // character that is just being parsed + const char16_t* aCur, // pointer to character aChar in the buffer + const char16_t*& aTokenStart, // string copying is done in blocks as big as + // possible, aTokenStart points to the beginning + // of this block + nsAString& aOldValue); // when duplicate property is found, new value + // is stored into hashtable and the old one is + // placed in this variable + + void WaitForKey() + { + mState = eParserState_AwaitingKey; + } + + void EnterKeyState() + { + mKey.Truncate(); + mState = eParserState_Key; + } + + void WaitForValue() + { + mState = eParserState_AwaitingValue; + } + + void EnterValueState() + { + mValue.Truncate(); + mMinLength = 0; + mState = eParserState_Value; + mSpecialState = eParserSpecial_None; + } + + void EnterCommentState() + { + mState = eParserState_Comment; + } + + nsAutoString mKey; + nsAutoString mValue; + + uint32_t mUnicodeValuesRead; // should be 4! + char16_t mUnicodeValue; // currently parsed unicode value + bool mHaveMultiLine; // is TRUE when last processed characters form + // any of following sequences: + // - "\\\r" + // - "\\\n" + // - "\\\r\n" + // - any sequence above followed by any + // combination of ' ' and '\t' + bool mMultiLineCanSkipN; // TRUE if "\\\r" was detected + uint32_t mMinLength; // limit right trimming at the end to not trim + // escaped whitespaces + EParserState mState; + // if we see a '\' then we enter this special state + EParserSpecial mSpecialState; + nsCOMPtr<nsIPersistentProperties> mProps; +}; + +inline bool +IsWhiteSpace(char16_t aChar) +{ + return (aChar == ' ') || (aChar == '\t') || + (aChar == '\r') || (aChar == '\n'); +} + +inline bool +IsEOL(char16_t aChar) +{ + return (aChar == '\r') || (aChar == '\n'); +} + + +bool +nsPropertiesParser::ParseValueCharacter(char16_t aChar, const char16_t* aCur, + const char16_t*& aTokenStart, + nsAString& aOldValue) +{ + switch (mSpecialState) { + // the normal state - look for special characters + case eParserSpecial_None: + switch (aChar) { + case '\\': + if (mHaveMultiLine) { + // there is nothing to append to mValue yet + mHaveMultiLine = false; + } else { + mValue += Substring(aTokenStart, aCur); + } + + mSpecialState = eParserSpecial_Escaped; + break; + + case '\n': + // if we detected multiline and got only "\\\r" ignore next "\n" if any + if (mHaveMultiLine && mMultiLineCanSkipN) { + // but don't allow another '\n' to be skipped + mMultiLineCanSkipN = false; + // Now there is nothing to append to the mValue since we are skipping + // whitespaces at the beginning of the new line of the multiline + // property. Set aTokenStart properly to ensure that nothing is appended + // if we find regular line-end or the end of the buffer. + aTokenStart = aCur + 1; + break; + } + MOZ_FALLTHROUGH; + + case '\r': + // we're done! We have a key and value + mValue += Substring(aTokenStart, aCur); + FinishValueState(aOldValue); + mHaveMultiLine = false; + break; + + default: + // there is nothing to do with normal characters, + // but handle multilines correctly + if (mHaveMultiLine) { + if (aChar == ' ' || aChar == '\t') { + // don't allow another '\n' to be skipped + mMultiLineCanSkipN = false; + // Now there is nothing to append to the mValue since we are skipping + // whitespaces at the beginning of the new line of the multiline + // property. Set aTokenStart properly to ensure that nothing is appended + // if we find regular line-end or the end of the buffer. + aTokenStart = aCur + 1; + break; + } + mHaveMultiLine = false; + aTokenStart = aCur; + } + break; // from switch on (aChar) + } + break; // from switch on (mSpecialState) + + // saw a \ character, so parse the character after that + case eParserSpecial_Escaped: + // probably want to start parsing at the next token + // other characters, like 'u' might override this + aTokenStart = aCur + 1; + mSpecialState = eParserSpecial_None; + + switch (aChar) { + // the easy characters - \t, \n, and so forth + case 't': + mValue += char16_t('\t'); + mMinLength = mValue.Length(); + break; + case 'n': + mValue += char16_t('\n'); + mMinLength = mValue.Length(); + break; + case 'r': + mValue += char16_t('\r'); + mMinLength = mValue.Length(); + break; + case '\\': + mValue += char16_t('\\'); + break; + + // switch to unicode mode! + case 'u': + case 'U': + mSpecialState = eParserSpecial_Unicode; + mUnicodeValuesRead = 0; + mUnicodeValue = 0; + break; + + // a \ immediately followed by a newline means we're going multiline + case '\r': + case '\n': + mHaveMultiLine = true; + mMultiLineCanSkipN = (aChar == '\r'); + mSpecialState = eParserSpecial_None; + break; + + default: + // don't recognize the character, so just append it + mValue += aChar; + break; + } + break; + + // we're in the middle of parsing a 4-character unicode value + // like \u5f39 + case eParserSpecial_Unicode: + if ('0' <= aChar && aChar <= '9') { + mUnicodeValue = + (mUnicodeValue << 4) | (aChar - '0'); + } else if ('a' <= aChar && aChar <= 'f') { + mUnicodeValue = + (mUnicodeValue << 4) | (aChar - 'a' + 0x0a); + } else if ('A' <= aChar && aChar <= 'F') { + mUnicodeValue = + (mUnicodeValue << 4) | (aChar - 'A' + 0x0a); + } else { + // non-hex character. Append what we have, and move on. + mValue += mUnicodeValue; + mMinLength = mValue.Length(); + mSpecialState = eParserSpecial_None; + + // leave aTokenStart at this unknown character, so it gets appended + aTokenStart = aCur; + + // ensure parsing this non-hex character again + return false; + } + + if (++mUnicodeValuesRead >= 4) { + aTokenStart = aCur + 1; + mSpecialState = eParserSpecial_None; + mValue += mUnicodeValue; + mMinLength = mValue.Length(); + } + + break; + } + + return true; +} + +nsresult +nsPropertiesParser::SegmentWriter(nsIUnicharInputStream* aStream, + void* aClosure, + const char16_t* aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t* aWriteCount) +{ + nsPropertiesParser* parser = static_cast<nsPropertiesParser*>(aClosure); + parser->ParseBuffer(aFromSegment, aCount); + + *aWriteCount = aCount; + return NS_OK; +} + +nsresult +nsPropertiesParser::ParseBuffer(const char16_t* aBuffer, + uint32_t aBufferLength) +{ + const char16_t* cur = aBuffer; + const char16_t* end = aBuffer + aBufferLength; + + // points to the start/end of the current key or value + const char16_t* tokenStart = nullptr; + + // if we're in the middle of parsing a key or value, make sure + // the current token points to the beginning of the current buffer + if (mState == eParserState_Key || + mState == eParserState_Value) { + tokenStart = aBuffer; + } + + nsAutoString oldValue; + + while (cur != end) { + + char16_t c = *cur; + + switch (mState) { + case eParserState_AwaitingKey: + if (c == '#' || c == '!') { + EnterCommentState(); + } + + else if (!IsWhiteSpace(c)) { + // not a comment, not whitespace, we must have found a key! + EnterKeyState(); + tokenStart = cur; + } + break; + + case eParserState_Key: + if (c == '=' || c == ':') { + mKey += Substring(tokenStart, cur); + WaitForValue(); + } + break; + + case eParserState_AwaitingValue: + if (IsEOL(c)) { + // no value at all! mimic the normal value-ending + EnterValueState(); + FinishValueState(oldValue); + } + + // ignore white space leading up to the value + else if (!IsWhiteSpace(c)) { + tokenStart = cur; + EnterValueState(); + + // make sure to handle this first character + if (ParseValueCharacter(c, cur, tokenStart, oldValue)) { + cur++; + } + // If the character isn't consumed, don't do cur++ and parse + // the character again. This can happen f.e. for char 'X' in sequence + // "\u00X". This character can be control character and must be + // processed again. + continue; + } + break; + + case eParserState_Value: + if (ParseValueCharacter(c, cur, tokenStart, oldValue)) { + cur++; + } + // See few lines above for reason of doing this + continue; + + case eParserState_Comment: + // stay in this state till we hit EOL + if (c == '\r' || c == '\n') { + WaitForKey(); + } + break; + } + + // finally, advance to the next character + cur++; + } + + // if we're still parsing the value and are in eParserSpecial_None, then + // append whatever we have.. + if (mState == eParserState_Value && tokenStart && + mSpecialState == eParserSpecial_None) { + mValue += Substring(tokenStart, cur); + } + // if we're still parsing the key, then append whatever we have.. + else if (mState == eParserState_Key && tokenStart) { + mKey += Substring(tokenStart, cur); + } + + return NS_OK; +} + +nsPersistentProperties::nsPersistentProperties() + : mIn(nullptr) + , mTable(&property_HashTableOps, sizeof(PropertyTableEntry), 16) +{ + PL_INIT_ARENA_POOL(&mArena, "PersistentPropertyArena", 2048); +} + +nsPersistentProperties::~nsPersistentProperties() +{ + PL_FinishArenaPool(&mArena); +} + +nsresult +nsPersistentProperties::Create(nsISupports* aOuter, REFNSIID aIID, + void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + RefPtr<nsPersistentProperties> props = new nsPersistentProperties(); + return props->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsPersistentProperties, nsIPersistentProperties, nsIProperties) + +NS_IMETHODIMP +nsPersistentProperties::Load(nsIInputStream* aIn) +{ + nsresult rv = NS_NewUnicharInputStream(aIn, getter_AddRefs(mIn)); + + if (rv != NS_OK) { + NS_WARNING("Error creating UnicharInputStream"); + return NS_ERROR_FAILURE; + } + + nsPropertiesParser parser(this); + + uint32_t nProcessed; + // If this 4096 is changed to some other value, make sure to adjust + // the bug121341.properties test file accordingly. + while (NS_SUCCEEDED(rv = mIn->ReadSegments(nsPropertiesParser::SegmentWriter, + &parser, 4096, &nProcessed)) && + nProcessed != 0); + mIn = nullptr; + if (NS_FAILED(rv)) { + return rv; + } + + // We may have an unprocessed value at this point + // if the last line did not have a proper line ending. + if (parser.GetState() == eParserState_Value) { + nsAutoString oldValue; + parser.FinishValueState(oldValue); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::SetStringProperty(const nsACString& aKey, + const nsAString& aNewValue, + nsAString& aOldValue) +{ + const nsAFlatCString& flatKey = PromiseFlatCString(aKey); + auto entry = static_cast<PropertyTableEntry*> + (mTable.Add(flatKey.get())); + + if (entry->mKey) { + aOldValue = entry->mValue; + NS_WARNING(nsPrintfCString("the property %s already exists", + flatKey.get()).get()); + } else { + aOldValue.Truncate(); + } + + entry->mKey = ArenaStrdup(flatKey, &mArena); + entry->mValue = ArenaStrdup(PromiseFlatString(aNewValue), &mArena); + + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::Save(nsIOutputStream* aOut, const nsACString& aHeader) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPersistentProperties::GetStringProperty(const nsACString& aKey, + nsAString& aValue) +{ + const nsAFlatCString& flatKey = PromiseFlatCString(aKey); + + auto entry = static_cast<PropertyTableEntry*>(mTable.Search(flatKey.get())); + if (!entry) { + return NS_ERROR_FAILURE; + } + + aValue = entry->mValue; + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::Enumerate(nsISimpleEnumerator** aResult) +{ + nsCOMArray<nsIPropertyElement> props; + + // We know the necessary size; we can avoid growing it while adding elements + props.SetCapacity(mTable.EntryCount()); + + // Step through hash entries populating a transient array + for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<PropertyTableEntry*>(iter.Get()); + + RefPtr<nsPropertyElement> element = + new nsPropertyElement(nsDependentCString(entry->mKey), + nsDependentString(entry->mValue)); + + if (!props.AppendObject(element)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return NS_NewArrayEnumerator(aResult, props); +} + +//////////////////////////////////////////////////////////////////////////////// +// XXX Some day we'll unify the nsIPersistentProperties interface with +// nsIProperties, but until now... + +NS_IMETHODIMP +nsPersistentProperties::Get(const char* aProp, const nsIID& aUUID, + void** aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPersistentProperties::Set(const char* aProp, nsISupports* value) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +nsPersistentProperties::Undefine(const char* aProp) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPersistentProperties::Has(const char* aProp, bool* aResult) +{ + *aResult = !!mTable.Search(aProp); + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::GetKeys(uint32_t* aCount, char*** aKeys) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +//////////////////////////////////////////////////////////////////////////////// +// PropertyElement +//////////////////////////////////////////////////////////////////////////////// + +nsresult +nsPropertyElement::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + RefPtr<nsPropertyElement> propElem = new nsPropertyElement(); + return propElem->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsPropertyElement, nsIPropertyElement) + +NS_IMETHODIMP +nsPropertyElement::GetKey(nsACString& aReturnKey) +{ + aReturnKey = mKey; + return NS_OK; +} + +NS_IMETHODIMP +nsPropertyElement::GetValue(nsAString& aReturnValue) +{ + aReturnValue = mValue; + return NS_OK; +} + +NS_IMETHODIMP +nsPropertyElement::SetKey(const nsACString& aKey) +{ + mKey = aKey; + return NS_OK; +} + +NS_IMETHODIMP +nsPropertyElement::SetValue(const nsAString& aValue) +{ + mValue = aValue; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/ds/nsPersistentProperties.h b/xpcom/ds/nsPersistentProperties.h new file mode 100644 index 000000000..6c9e9cb51 --- /dev/null +++ b/xpcom/ds/nsPersistentProperties.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsPersistentProperties_h___ +#define nsPersistentProperties_h___ + +#include "nsIPersistentProperties2.h" +#include "PLDHashTable.h" +#include "plarena.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +class nsIUnicharInputStream; + +class nsPersistentProperties final : public nsIPersistentProperties +{ +public: + nsPersistentProperties(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROPERTIES + NS_DECL_NSIPERSISTENTPROPERTIES + + static MOZ_MUST_USE nsresult + Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + +private: + ~nsPersistentProperties(); + +protected: + nsCOMPtr<nsIUnicharInputStream> mIn; + + PLDHashTable mTable; + PLArenaPool mArena; +}; + +class nsPropertyElement final : public nsIPropertyElement +{ +public: + nsPropertyElement() + { + } + + nsPropertyElement(const nsACString& aKey, const nsAString& aValue) + : mKey(aKey) + , mValue(aValue) + { + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTYELEMENT + + static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + +private: + ~nsPropertyElement() {} + +protected: + nsCString mKey; + nsString mValue; +}; + +#endif /* nsPersistentProperties_h___ */ diff --git a/xpcom/ds/nsProperties.cpp b/xpcom/ds/nsProperties.cpp new file mode 100644 index 000000000..3a73ac7da --- /dev/null +++ b/xpcom/ds/nsProperties.cpp @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsProperties.h" + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_AGGREGATED(nsProperties) +NS_INTERFACE_MAP_BEGIN_AGGREGATED(nsProperties) + NS_INTERFACE_MAP_ENTRY(nsIProperties) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +nsProperties::Get(const char* prop, const nsIID& uuid, void** result) +{ + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsISupports> value; + if (!nsProperties_HashBase::Get(prop, getter_AddRefs(value))) { + return NS_ERROR_FAILURE; + } + return (value) ? value->QueryInterface(uuid, result) : NS_ERROR_NO_INTERFACE; +} + +NS_IMETHODIMP +nsProperties::Set(const char* prop, nsISupports* value) +{ + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + Put(prop, value); + return NS_OK; +} + +NS_IMETHODIMP +nsProperties::Undefine(const char* prop) +{ + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsISupports> value; + if (!nsProperties_HashBase::Get(prop, getter_AddRefs(value))) { + return NS_ERROR_FAILURE; + } + + Remove(prop); + return NS_OK; +} + +NS_IMETHODIMP +nsProperties::Has(const char* prop, bool* result) +{ + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsISupports> value; + *result = nsProperties_HashBase::Get(prop, getter_AddRefs(value)); + return NS_OK; +} + +NS_IMETHODIMP +nsProperties::GetKeys(uint32_t* aCount, char*** aKeys) +{ + if (NS_WARN_IF(!aCount) || NS_WARN_IF(!aKeys)) { + return NS_ERROR_INVALID_ARG; + } + + uint32_t count = Count(); + char** keys = (char**)moz_xmalloc(count * sizeof(char*)); + uint32_t j = 0; + + for (auto iter = this->Iter(); !iter.Done(); iter.Next()) { + const char* key = iter.Key(); + keys[j] = strdup(key); + + if (!keys[j]) { + // Free 'em all + for (uint32_t i = 0; i < j; i++) { + free(keys[i]); + } + free(keys); + return NS_ERROR_OUT_OF_MEMORY; + } + j++; + } + + *aCount = count; + *aKeys = keys; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/ds/nsProperties.h b/xpcom/ds/nsProperties.h new file mode 100644 index 000000000..6949017dd --- /dev/null +++ b/xpcom/ds/nsProperties.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsProperties_h___ +#define nsProperties_h___ + +#include "nsIProperties.h" +#include "nsInterfaceHashtable.h" +#include "nsHashKeys.h" +#include "nsAgg.h" +#include "mozilla/Attributes.h" + +#define NS_PROPERTIES_CID \ +{ /* 4de2bc90-b1bf-11d3-93b6-00104ba0fd40 */ \ + 0x4de2bc90, \ + 0xb1bf, \ + 0x11d3, \ + {0x93, 0xb6, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \ +} + +typedef nsInterfaceHashtable<nsCharPtrHashKey, + nsISupports> nsProperties_HashBase; + +class nsProperties final + : public nsIProperties + , public nsProperties_HashBase +{ +public: + NS_DECL_AGGREGATED + NS_DECL_NSIPROPERTIES + + explicit nsProperties(nsISupports *aOuter) { NS_INIT_AGGREGATED(aOuter); } + ~nsProperties() {} +}; + +#endif /* nsProperties_h___ */ diff --git a/xpcom/ds/nsStaticAtom.h b/xpcom/ds/nsStaticAtom.h new file mode 100644 index 000000000..7e31623db --- /dev/null +++ b/xpcom/ds/nsStaticAtom.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsStaticAtom_h__ +#define nsStaticAtom_h__ + +#include "nsIAtom.h" +#include "nsStringBuffer.h" + +#define NS_STATIC_ATOM(buffer_name, atom_ptr) \ + { (nsStringBuffer*) &buffer_name, atom_ptr } + +#define NS_STATIC_ATOM_BUFFER(buffer_name, str_data) \ + static nsFakeStringBuffer<sizeof(str_data)> buffer_name = \ + { 1, sizeof(str_data) * sizeof(char16_t), (u"" str_data) }; + +/** + * Holds data used to initialize large number of atoms during startup. Use + * the above macros to initialize these structs. They should never be accessed + * directly other than from AtomTable.cpp + */ +struct nsStaticAtom +{ + // mStringBuffer points to the string buffer for a permanent atom, and is + // therefore safe as a non-owning reference. + nsStringBuffer* MOZ_NON_OWNING_REF mStringBuffer; + nsIAtom** mAtom; +}; + +/** + * This is a struct with the same binary layout as a nsStringBuffer. + */ +template<uint32_t size> +struct nsFakeStringBuffer +{ + int32_t mRefCnt; + uint32_t mSize; + char16_t mStringData[size]; +}; + +// Register an array of static atoms with the atom table +template<uint32_t N> +void +NS_RegisterStaticAtoms(const nsStaticAtom (&aAtoms)[N]) +{ + extern void RegisterStaticAtoms(const nsStaticAtom*, uint32_t aAtomCount); + RegisterStaticAtoms(aAtoms, N); +} + +#endif diff --git a/xpcom/ds/nsStaticNameTable.cpp b/xpcom/ds/nsStaticNameTable.cpp new file mode 100644 index 000000000..26bd172a7 --- /dev/null +++ b/xpcom/ds/nsStaticNameTable.cpp @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +/* Class to manage lookup of static names in a table. */ + +#include "nsCRT.h" + +#include "nscore.h" +#include "mozilla/HashFunctions.h" +#include "nsISupportsImpl.h" + +#define PL_ARENA_CONST_ALIGN_MASK 3 +#include "nsStaticNameTable.h" + +using namespace mozilla; + +struct NameTableKey +{ + NameTableKey(const nsDependentCString aNameArray[], + const nsAFlatCString* aKeyStr) + : mNameArray(aNameArray) + , mIsUnichar(false) + { + mKeyStr.m1b = aKeyStr; + } + + NameTableKey(const nsDependentCString aNameArray[], + const nsAFlatString* aKeyStr) + : mNameArray(aNameArray) + , mIsUnichar(true) + { + mKeyStr.m2b = aKeyStr; + } + + const nsDependentCString* mNameArray; + union + { + const nsAFlatCString* m1b; + const nsAFlatString* m2b; + } mKeyStr; + bool mIsUnichar; +}; + +struct NameTableEntry : public PLDHashEntryHdr +{ + int32_t mIndex; +}; + +static bool +matchNameKeysCaseInsensitive(const PLDHashEntryHdr* aHdr, const void* aVoidKey) +{ + auto entry = static_cast<const NameTableEntry*>(aHdr); + auto key = static_cast<const NameTableKey*>(aVoidKey); + const nsDependentCString* name = &key->mNameArray[entry->mIndex]; + + return key->mIsUnichar + ? key->mKeyStr.m2b->LowerCaseEqualsASCII(name->get(), name->Length()) + : key->mKeyStr.m1b->LowerCaseEqualsASCII(name->get(), name->Length()); +} + +/* + * caseInsensitiveHashKey is just like PLDHashTable::HashStringKey except it + * uses (*s & ~0x20) instead of simply *s. This means that "aFOO" and + * "afoo" and "aFoo" will all hash to the same thing. It also means + * that some strings that aren't case-insensensitively equal will hash + * to the same value, but it's just a hash function so it doesn't + * matter. + */ +static PLDHashNumber +caseInsensitiveStringHashKey(const void* aKey) +{ + PLDHashNumber h = 0; + const NameTableKey* tableKey = static_cast<const NameTableKey*>(aKey); + if (tableKey->mIsUnichar) { + for (const char16_t* s = tableKey->mKeyStr.m2b->get(); + *s != '\0'; + s++) { + h = AddToHash(h, *s & ~0x20); + } + } else { + for (const unsigned char* s = reinterpret_cast<const unsigned char*>( + tableKey->mKeyStr.m1b->get()); + *s != '\0'; + s++) { + h = AddToHash(h, *s & ~0x20); + } + } + return h; +} + +static const struct PLDHashTableOps nametable_CaseInsensitiveHashTableOps = { + caseInsensitiveStringHashKey, + matchNameKeysCaseInsensitive, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr, +}; + +nsStaticCaseInsensitiveNameTable::nsStaticCaseInsensitiveNameTable( + const char* const aNames[], int32_t aLength) + : mNameArray(nullptr) + , mNameTable(&nametable_CaseInsensitiveHashTableOps, + sizeof(NameTableEntry), aLength) + , mNullStr("") +{ + MOZ_COUNT_CTOR(nsStaticCaseInsensitiveNameTable); + + MOZ_ASSERT(aNames, "null name table"); + MOZ_ASSERT(aLength, "0 length"); + + mNameArray = (nsDependentCString*) + moz_xmalloc(aLength * sizeof(nsDependentCString)); + + for (int32_t index = 0; index < aLength; ++index) { + const char* raw = aNames[index]; +#ifdef DEBUG + { + // verify invariants of contents + nsAutoCString temp1(raw); + nsDependentCString temp2(raw); + ToLowerCase(temp1); + MOZ_ASSERT(temp1.Equals(temp2), "upper case char in table"); + MOZ_ASSERT(nsCRT::IsAscii(raw), + "non-ascii string in table -- " + "case-insensitive matching won't work right"); + } +#endif + // use placement-new to initialize the string object + nsDependentCString* strPtr = &mNameArray[index]; + new (strPtr) nsDependentCString(raw); + + NameTableKey key(mNameArray, strPtr); + + auto entry = static_cast<NameTableEntry*>(mNameTable.Add(&key, fallible)); + if (!entry) { + continue; + } + + // If the entry already exists it's unlikely but possible that its index is + // zero, in which case this assertion won't fail. But if the index is + // non-zero (highly likely) then it will fail. In other words, this + // assertion is likely but not guaranteed to detect if an entry is already + // used. + MOZ_ASSERT(entry->mIndex == 0, "Entry already exists!"); + + entry->mIndex = index; + } +#ifdef DEBUG + mNameTable.MarkImmutable(); +#endif +} + +nsStaticCaseInsensitiveNameTable::~nsStaticCaseInsensitiveNameTable() +{ + // manually call the destructor on placement-new'ed objects + for (uint32_t index = 0; index < mNameTable.EntryCount(); index++) { + mNameArray[index].~nsDependentCString(); + } + free((void*)mNameArray); + MOZ_COUNT_DTOR(nsStaticCaseInsensitiveNameTable); +} + +int32_t +nsStaticCaseInsensitiveNameTable::Lookup(const nsACString& aName) +{ + NS_ASSERTION(mNameArray, "not inited"); + + const nsAFlatCString& str = PromiseFlatCString(aName); + + NameTableKey key(mNameArray, &str); + auto entry = static_cast<NameTableEntry*>(mNameTable.Search(&key)); + + return entry ? entry->mIndex : nsStaticCaseInsensitiveNameTable::NOT_FOUND; +} + +int32_t +nsStaticCaseInsensitiveNameTable::Lookup(const nsAString& aName) +{ + NS_ASSERTION(mNameArray, "not inited"); + + const nsAFlatString& str = PromiseFlatString(aName); + + NameTableKey key(mNameArray, &str); + auto entry = static_cast<NameTableEntry*>(mNameTable.Search(&key)); + + return entry ? entry->mIndex : nsStaticCaseInsensitiveNameTable::NOT_FOUND; +} + +const nsAFlatCString& +nsStaticCaseInsensitiveNameTable::GetStringValue(int32_t aIndex) +{ + NS_ASSERTION(mNameArray, "not inited"); + + if ((NOT_FOUND < aIndex) && ((uint32_t)aIndex < mNameTable.EntryCount())) { + return mNameArray[aIndex]; + } + return mNullStr; +} diff --git a/xpcom/ds/nsStaticNameTable.h b/xpcom/ds/nsStaticNameTable.h new file mode 100644 index 000000000..0b9df3039 --- /dev/null +++ b/xpcom/ds/nsStaticNameTable.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +/* Classes to manage lookup of static names in a table. */ + +#ifndef nsStaticNameTable_h___ +#define nsStaticNameTable_h___ + +#include "PLDHashTable.h" +#include "nsString.h" + +/* This class supports case insensitive lookup. + * + * It differs from atom tables: + * - It supports case insensitive lookup. + * - It has minimal footprint by not copying the string table. + * - It does no locking. + * - It returns zero based indexes and const nsCString& as required by its + * callers in the parser. + * - It is not an xpcom interface - meant for fast lookup in static tables. + * + * ***REQUIREMENTS*** + * - It *requires* that all entries in the table be lowercase only. + * - It *requires* that the table of strings be in memory that lives at least + * as long as this table object - typically a static string array. + */ + +class nsStaticCaseInsensitiveNameTable +{ +public: + enum { NOT_FOUND = -1 }; + + int32_t Lookup(const nsACString& aName); + int32_t Lookup(const nsAString& aName); + const nsAFlatCString& GetStringValue(int32_t aIndex); + + nsStaticCaseInsensitiveNameTable(const char* const aNames[], int32_t aLength); + ~nsStaticCaseInsensitiveNameTable(); + +private: + nsDependentCString* mNameArray; + PLDHashTable mNameTable; + nsDependentCString mNullStr; +}; + +#endif /* nsStaticNameTable_h___ */ diff --git a/xpcom/ds/nsStringEnumerator.cpp b/xpcom/ds/nsStringEnumerator.cpp new file mode 100644 index 000000000..bdcd53e1a --- /dev/null +++ b/xpcom/ds/nsStringEnumerator.cpp @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsStringEnumerator.h" +#include "nsISimpleEnumerator.h" +#include "nsSupportsPrimitives.h" +#include "mozilla/Attributes.h" +#include "nsTArray.h" + +// +// nsStringEnumerator +// + +class nsStringEnumerator final + : public nsIStringEnumerator + , public nsIUTF8StringEnumerator + , public nsISimpleEnumerator +{ +public: + nsStringEnumerator(const nsTArray<nsString>* aArray, bool aOwnsArray) + : mArray(aArray) + , mIndex(0) + , mOwnsArray(aOwnsArray) + , mIsUnicode(true) + {} + + nsStringEnumerator(const nsTArray<nsCString>* aArray, bool aOwnsArray) + : mCArray(aArray) + , mIndex(0) + , mOwnsArray(aOwnsArray) + , mIsUnicode(false) + {} + + nsStringEnumerator(const nsTArray<nsString>* aArray, nsISupports* aOwner) + : mArray(aArray) + , mIndex(0) + , mOwner(aOwner) + , mOwnsArray(false) + , mIsUnicode(true) + {} + + nsStringEnumerator(const nsTArray<nsCString>* aArray, nsISupports* aOwner) + : mCArray(aArray) + , mIndex(0) + , mOwner(aOwner) + , mOwnsArray(false) + , mIsUnicode(false) + {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIUTF8STRINGENUMERATOR + + // have to declare nsIStringEnumerator manually, because of + // overlapping method names + NS_IMETHOD GetNext(nsAString& aResult) override; + NS_DECL_NSISIMPLEENUMERATOR + +private: + ~nsStringEnumerator() + { + if (mOwnsArray) { + // const-casting is safe here, because the NS_New* + // constructors make sure mOwnsArray is consistent with + // the constness of the objects + if (mIsUnicode) { + delete const_cast<nsTArray<nsString>*>(mArray); + } else { + delete const_cast<nsTArray<nsCString>*>(mCArray); + } + } + } + + union + { + const nsTArray<nsString>* mArray; + const nsTArray<nsCString>* mCArray; + }; + + inline uint32_t Count() + { + return mIsUnicode ? mArray->Length() : mCArray->Length(); + } + + uint32_t mIndex; + + // the owner allows us to hold a strong reference to the object + // that owns the array. Having a non-null value in mOwner implies + // that mOwnsArray is false, because we rely on the real owner + // to release the array + nsCOMPtr<nsISupports> mOwner; + bool mOwnsArray; + bool mIsUnicode; +}; + +NS_IMPL_ISUPPORTS(nsStringEnumerator, + nsIStringEnumerator, + nsIUTF8StringEnumerator, + nsISimpleEnumerator) + +NS_IMETHODIMP +nsStringEnumerator::HasMore(bool* aResult) +{ + *aResult = mIndex < Count(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::HasMoreElements(bool* aResult) +{ + return HasMore(aResult); +} + +NS_IMETHODIMP +nsStringEnumerator::GetNext(nsISupports** aResult) +{ + if (mIsUnicode) { + nsSupportsString* stringImpl = new nsSupportsString(); + if (!stringImpl) { + return NS_ERROR_OUT_OF_MEMORY; + } + + stringImpl->SetData(mArray->ElementAt(mIndex++)); + *aResult = stringImpl; + } else { + nsSupportsCString* cstringImpl = new nsSupportsCString(); + if (!cstringImpl) { + return NS_ERROR_OUT_OF_MEMORY; + } + + cstringImpl->SetData(mCArray->ElementAt(mIndex++)); + *aResult = cstringImpl; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::GetNext(nsAString& aResult) +{ + if (NS_WARN_IF(mIndex >= Count())) { + return NS_ERROR_UNEXPECTED; + } + + if (mIsUnicode) { + aResult = mArray->ElementAt(mIndex++); + } else { + CopyUTF8toUTF16(mCArray->ElementAt(mIndex++), aResult); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::GetNext(nsACString& aResult) +{ + if (NS_WARN_IF(mIndex >= Count())) { + return NS_ERROR_UNEXPECTED; + } + + if (mIsUnicode) { + CopyUTF16toUTF8(mArray->ElementAt(mIndex++), aResult); + } else { + aResult = mCArray->ElementAt(mIndex++); + } + + return NS_OK; +} + +template<class T> +static inline nsresult +StringEnumeratorTail(T** aResult) +{ + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +// +// constructors +// + +nsresult +NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray<nsString>* aArray, nsISupports* aOwner) +{ + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, aOwner); + return StringEnumeratorTail(aResult); +} + + +nsresult +NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + const nsTArray<nsCString>* aArray, + nsISupports* aOwner) +{ + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, aOwner); + return StringEnumeratorTail(aResult); +} + +nsresult +NS_NewAdoptingStringEnumerator(nsIStringEnumerator** aResult, + nsTArray<nsString>* aArray) +{ + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, true); + return StringEnumeratorTail(aResult); +} + +nsresult +NS_NewAdoptingUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + nsTArray<nsCString>* aArray) +{ + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, true); + return StringEnumeratorTail(aResult); +} + +// const ones internally just forward to the non-const equivalents +nsresult +NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray<nsString>* aArray) +{ + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, false); + return StringEnumeratorTail(aResult); +} + +nsresult +NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + const nsTArray<nsCString>* aArray) +{ + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, false); + return StringEnumeratorTail(aResult); +} + diff --git a/xpcom/ds/nsStringEnumerator.h b/xpcom/ds/nsStringEnumerator.h new file mode 100644 index 000000000..226afd7f4 --- /dev/null +++ b/xpcom/ds/nsStringEnumerator.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsIStringEnumerator.h" +#include "nsStringFwd.h" +#include "nsTArrayForwardDeclare.h" + +// nsIStringEnumerator/nsIUTF8StringEnumerator implementations +// +// Currently all implementations support both interfaces. The +// constructors below provide the most common interface for the given +// type (i.e. nsIStringEnumerator for char16_t* strings, and so +// forth) but any resulting enumerators can be queried to the other +// type. Internally, the enumerators will hold onto the type that was +// passed in and do conversion if GetNext() for the other type of +// string is called. + +// There are a few different types of enumerators: + +// +// These enumerators hold a pointer to the array. Be careful +// because modifying the array may confuse the iterator, especially if +// you insert or remove elements in the middle of the array. +// + +// The non-adopting enumerator requires that the array sticks around +// at least as long as the enumerator does. These are for constant +// string arrays that the enumerator does not own, this could be used +// in VERY specialized cases such as when the provider KNOWS that the +// string enumerator will be consumed immediately, or will at least +// outlast the array. +// For example: +// +// nsTArray<nsCString> array; +// array.AppendCString("abc"); +// array.AppendCString("def"); +// NS_NewStringEnumerator(&enumerator, &array, true); +// +// // call some internal method which iterates the enumerator +// InternalMethod(enumerator); +// NS_RELEASE(enumerator); +// +MOZ_MUST_USE nsresult +NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray<nsString>* aArray, + nsISupports* aOwner); +MOZ_MUST_USE nsresult +NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + const nsTArray<nsCString>* aArray); + +MOZ_MUST_USE nsresult +NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray<nsString>* aArray); + +// Adopting string enumerators assume ownership of the array and will +// call |operator delete| on the array when the enumerator is destroyed +// this is useful when the provider creates an array solely for the +// purpose of creating the enumerator. +// For example: +// +// nsTArray<nsCString>* array = new nsTArray<nsCString>; +// array->AppendString("abcd"); +// NS_NewAdoptingStringEnumerator(&result, array); +MOZ_MUST_USE nsresult +NS_NewAdoptingStringEnumerator(nsIStringEnumerator** aResult, + nsTArray<nsString>* aArray); + +MOZ_MUST_USE nsresult +NS_NewAdoptingUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + nsTArray<nsCString>* aArray); + + +// these versions take a refcounted "owner" which will be addreffed +// when the enumerator is created, and destroyed when the enumerator +// is released. This allows providers to give non-owning pointers to +// ns*StringArray member variables without worrying about lifetime +// issues +// For example: +// +// nsresult MyClass::Enumerate(nsIUTF8StringEnumerator** aResult) { +// mCategoryList->AppendString("abcd"); +// return NS_NewStringEnumerator(aResult, mCategoryList, this); +// } +// +MOZ_MUST_USE nsresult +NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + const nsTArray<nsCString>* aArray, + nsISupports* aOwner); diff --git a/xpcom/ds/nsSupportsArray.cpp b/xpcom/ds/nsSupportsArray.cpp new file mode 100644 index 000000000..67de8ae16 --- /dev/null +++ b/xpcom/ds/nsSupportsArray.cpp @@ -0,0 +1,264 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 <stdint.h> +#include <string.h> + +#include "nsArrayEnumerator.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsSupportsArray.h" +#include "nsSupportsArrayEnumerator.h" + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +nsresult +nsQueryElementAt::operator()(const nsIID& aIID, void** aResult) const +{ + nsresult status = + mCollection ? mCollection->QueryElementAt(mIndex, aIID, aResult) : + NS_ERROR_NULL_POINTER; + + if (mErrorPtr) { + *mErrorPtr = status; + } + + return status; +} + +nsSupportsArray::nsSupportsArray() +{ +} + +nsSupportsArray::~nsSupportsArray() +{ + Clear(); +} + +nsresult +nsSupportsArray::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + nsCOMPtr<nsISupportsArray> it = new nsSupportsArray(); + + return it->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsSupportsArray, nsIArray, nsISupportsArray, nsICollection, + nsISerializable) + +NS_IMETHODIMP +nsSupportsArray::Read(nsIObjectInputStream* aStream) +{ + nsresult rv; + + uint32_t newArraySize; + rv = aStream->Read32(&newArraySize); + if (NS_FAILED(rv)) { + return rv; + } + + uint32_t count; + rv = aStream->Read32(&count); + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(count <= newArraySize, "overlarge mCount!"); + if (count > newArraySize) { + count = newArraySize; + } + + // Don't clear out our array until we know we have enough space for the new + // one and have successfully copied everything out of the stream. + nsCOMArray<nsISupports> tmp; + tmp.SetCapacity(newArraySize); + tmp.SetCount(count); + + auto elems = tmp.Elements(); + for (uint32_t i = 0; i < count; i++) { + rv = aStream->ReadObject(true, &elems[i]); + if (NS_FAILED(rv)) { + return rv; + } + } + + // Now clear out existing refs and replace with the new array. + mArray.Clear(); + mArray.SwapElements(tmp); + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArray::Write(nsIObjectOutputStream* aStream) +{ + nsresult rv; + + rv = aStream->Write32(mArray.Capacity()); + if (NS_FAILED(rv)) { + return rv; + } + + rv = aStream->Write32(mArray.Length()); + if (NS_FAILED(rv)) { + return rv; + } + + for (size_t i = 0; i < mArray.Length(); i++) { + rv = aStream->WriteObject(mArray[i], true); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArray::GetElementAt(uint32_t aIndex, nsISupports** aOutPtr) +{ + nsCOMPtr<nsISupports> elm = mArray.SafeElementAt(aIndex); + elm.forget(aOutPtr); + return NS_OK; +} + +NS_IMETHODIMP_(int32_t) +nsSupportsArray::IndexOf(const nsISupports* aPossibleElement) +{ + // nsCOMArray takes a non-const param, but it just passes through to + // nsTArray which takes a const param. + return mArray.IndexOf(const_cast<nsISupports*>(aPossibleElement)); +} + +NS_IMETHODIMP_(bool) +nsSupportsArray::InsertElementAt(nsISupports* aElement, uint32_t aIndex) +{ + return mArray.InsertObjectAt(aElement, aIndex); +} + +NS_IMETHODIMP_(bool) +nsSupportsArray::ReplaceElementAt(nsISupports* aElement, uint32_t aIndex) +{ + // nsCOMArray::ReplaceObjectAt will grow the array if necessary. Instead + // we do the bounds check and only replace if it's in range. + if (aIndex < mArray.Length()) { + mArray.ReplaceElementAt(aIndex, aElement); + return true; + } + return false; +} + +NS_IMETHODIMP_(bool) +nsSupportsArray::RemoveElementAt(uint32_t aIndex) +{ + return mArray.RemoveObjectAt(aIndex); +} + +NS_IMETHODIMP +nsSupportsArray::RemoveElement(nsISupports* aElement) +{ + return mArray.RemoveObject(aElement) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSupportsArray::Clear(void) +{ + mArray.Clear(); + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArray::DeprecatedEnumerate(nsIEnumerator** aResult) +{ + RefPtr<nsSupportsArrayEnumerator> e = new nsSupportsArrayEnumerator(this); + e.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArray::Clone(nsISupportsArray** aResult) +{ + nsCOMPtr<nsISupportsArray> newArray; + nsresult rv = NS_NewISupportsArray(getter_AddRefs(newArray)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (size_t i = 0; i < mArray.Length(); i++) { + // AppendElement does an odd cast of bool to nsresult, we just cast back + // here. + if (!(bool)newArray->AppendElement(mArray[i])) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + newArray.forget(aResult); + return NS_OK; +} + +nsresult +NS_NewISupportsArray(nsISupportsArray** aInstancePtrResult) +{ + nsresult rv; + rv = nsSupportsArray::Create(nullptr, NS_GET_IID(nsISupportsArray), + (void**)aInstancePtrResult); + return rv; +} + +/** + * nsIArray adapters. + */ +NS_IMETHODIMP +nsSupportsArray::GetLength(uint32_t* aLength) { + return Count(aLength); +} + +NS_IMETHODIMP +nsSupportsArray::QueryElementAt(uint32_t aIndex, const nsIID& aIID, void** aResult) +{ + nsISupports* element = mArray.SafeElementAt(aIndex); + if (element) { + return element->QueryInterface(aIID, aResult); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSupportsArray::IndexOf(uint32_t aStartIndex, nsISupports* aElement, uint32_t* aResult) +{ + int32_t idx = mArray.IndexOf(aElement, aStartIndex); + if (idx < 0) { + return NS_ERROR_FAILURE; + } + + *aResult = static_cast<uint32_t>(idx); + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArray::Enumerate(nsISimpleEnumerator** aResult) +{ + return NS_NewArrayEnumerator(aResult, this); +} + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif + diff --git a/xpcom/ds/nsSupportsArray.h b/xpcom/ds/nsSupportsArray.h new file mode 100644 index 000000000..eed611104 --- /dev/null +++ b/xpcom/ds/nsSupportsArray.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsSupportsArray_h__ +#define nsSupportsArray_h__ + +#include "nsIArray.h" +#include "nsCOMArray.h" +#include "mozilla/Attributes.h" + + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +#include "nsISupportsArray.h" + +class nsSupportsArray final : public nsISupportsArray, + public nsIArray +{ + ~nsSupportsArray(void); // nonvirtual since we're not subclassed + +public: + nsSupportsArray(void); + + static MOZ_MUST_USE nsresult + Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSISERIALIZABLE + + // nsICollection methods: + NS_IMETHOD Count(uint32_t* aResult) override + { + *aResult = mArray.Length(); + return NS_OK; + } + NS_IMETHOD GetElementAt(uint32_t aIndex, nsISupports** aResult) override; + MOZ_MUST_USE NS_IMETHOD + SetElementAt(uint32_t aIndex, nsISupports* aValue) override + { + return ReplaceElementAt(aValue, aIndex) ? NS_OK : NS_ERROR_FAILURE; + } + MOZ_MUST_USE NS_IMETHOD AppendElement(nsISupports* aElement) override + { + // XXX Invalid cast of bool to nsresult (bug 778110) + return (nsresult)InsertElementAt(aElement, mArray.Length())/* ? NS_OK : NS_ERROR_FAILURE*/; + } + // XXX this is badly named - should be RemoveFirstElement + MOZ_MUST_USE NS_IMETHOD RemoveElement(nsISupports* aElement) override; + NS_IMETHOD DeprecatedEnumerate(nsIEnumerator** aResult) override; + NS_IMETHOD Clear(void) override; + + // nsISupportsArray methods: + NS_IMETHOD_(int32_t) IndexOf(const nsISupports* aPossibleElement) override; + + NS_IMETHOD GetIndexOf(nsISupports* aPossibleElement, int32_t* aResult) override + { + *aResult = IndexOf(aPossibleElement); + return NS_OK; + } + + MOZ_MUST_USE NS_IMETHOD_(bool) + InsertElementAt(nsISupports* aElement, uint32_t aIndex) override; + + MOZ_MUST_USE NS_IMETHOD_(bool) + ReplaceElementAt(nsISupports* aElement, uint32_t aIndex) override; + + MOZ_MUST_USE NS_IMETHOD_(bool) + RemoveElementAt(uint32_t aIndex) override; + + MOZ_MUST_USE NS_IMETHOD DeleteElementAt(uint32_t aIndex) override + { + return (RemoveElementAt(aIndex) ? NS_OK : NS_ERROR_FAILURE); + } + + MOZ_MUST_USE NS_IMETHOD Clone(nsISupportsArray** aResult) override; + + /** + * nsIArray adapters. + */ + NS_DECL_NSIARRAY + +private: + // Copy constructors are not allowed + explicit nsSupportsArray(const nsISupportsArray& aOther); + + nsCOMArray<nsISupports> mArray; +}; + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif + +#endif // nsSupportsArray_h__ diff --git a/xpcom/ds/nsSupportsArrayEnumerator.cpp b/xpcom/ds/nsSupportsArrayEnumerator.cpp new file mode 100644 index 000000000..3eb6a7a58 --- /dev/null +++ b/xpcom/ds/nsSupportsArrayEnumerator.cpp @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsSupportsArrayEnumerator.h" + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +#include "nsISupportsArray.h" + +nsSupportsArrayEnumerator::nsSupportsArrayEnumerator(nsISupportsArray* array) + : mArray(array) + , mCursor(0) +{ + NS_ASSERTION(array, "null array"); +} + +nsSupportsArrayEnumerator::~nsSupportsArrayEnumerator() +{ +} + +NS_IMPL_ISUPPORTS(nsSupportsArrayEnumerator, nsIBidirectionalEnumerator, + nsIEnumerator) + +NS_IMETHODIMP +nsSupportsArrayEnumerator::First() +{ + mCursor = 0; + uint32_t cnt; + nsresult rv = mArray->Count(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + int32_t end = (int32_t)cnt; + if (mCursor < end) { + return NS_OK; + } else { + return NS_ERROR_FAILURE; + } +} + +NS_IMETHODIMP +nsSupportsArrayEnumerator::Next() +{ + uint32_t cnt; + nsresult rv = mArray->Count(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + int32_t end = (int32_t)cnt; + if (mCursor < end) { // don't count upward forever + mCursor++; + } + if (mCursor < end) { + return NS_OK; + } else { + return NS_ERROR_FAILURE; + } +} + +NS_IMETHODIMP +nsSupportsArrayEnumerator::CurrentItem(nsISupports** aItem) +{ + NS_ASSERTION(aItem, "null out parameter"); + uint32_t cnt; + nsresult rv = mArray->Count(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + if (mCursor >= 0 && mCursor < (int32_t)cnt) { + return mArray->GetElementAt(mCursor, aItem); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSupportsArrayEnumerator::IsDone() +{ + uint32_t cnt; + nsresult rv = mArray->Count(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + // XXX This is completely incompatible with the meaning of nsresult. + // NS_ENUMERATOR_FALSE is defined to be 1. (bug 778111) + return (mCursor >= 0 && mCursor < (int32_t)cnt) + ? (nsresult)NS_ENUMERATOR_FALSE : NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsSupportsArrayEnumerator::Last() +{ + uint32_t cnt; + nsresult rv = mArray->Count(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + mCursor = cnt - 1; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsArrayEnumerator::Prev() +{ + if (mCursor >= 0) { + --mCursor; + } + if (mCursor >= 0) { + return NS_OK; + } else { + return NS_ERROR_FAILURE; + } +} + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif diff --git a/xpcom/ds/nsSupportsArrayEnumerator.h b/xpcom/ds/nsSupportsArrayEnumerator.h new file mode 100644 index 000000000..bd316d6b9 --- /dev/null +++ b/xpcom/ds/nsSupportsArrayEnumerator.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsSupportsArrayEnumerator_h___ +#define nsSupportsArrayEnumerator_h___ + +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +#include "nsIEnumerator.h" + +class nsISupportsArray; + +class nsSupportsArrayEnumerator final : public nsIBidirectionalEnumerator +{ +public: + NS_DECL_ISUPPORTS + + explicit nsSupportsArrayEnumerator(nsISupportsArray* aArray); + + // nsIEnumerator methods: + NS_DECL_NSIENUMERATOR + + // nsIBidirectionalEnumerator methods: + NS_DECL_NSIBIDIRECTIONALENUMERATOR + +private: + ~nsSupportsArrayEnumerator(); + +protected: + nsCOMPtr<nsISupportsArray> mArray; + int32_t mCursor; + +}; + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif + +#endif // __nsSupportsArrayEnumerator_h + diff --git a/xpcom/ds/nsSupportsPrimitives.cpp b/xpcom/ds/nsSupportsPrimitives.cpp new file mode 100644 index 000000000..aa929b9de --- /dev/null +++ b/xpcom/ds/nsSupportsPrimitives.cpp @@ -0,0 +1,849 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsSupportsPrimitives.h" +#include "nsMemory.h" +#include "mozilla/Assertions.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Sprintf.h" +#include <algorithm> + +template<typename T> +static char* +DataToString(const char* aFormat, T aData) +{ + static const int size = 32; + char buf[size]; + + int len = SprintfLiteral(buf, aFormat, aData); + MOZ_ASSERT(len >= 0); + + return static_cast<char*>(nsMemory::Clone(buf, std::min(len + 1, size) * + sizeof(char))); +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsID, nsISupportsID, nsISupportsPrimitive) + +nsSupportsID::nsSupportsID() + : mData(nullptr) +{ +} + +NS_IMETHODIMP +nsSupportsID::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_ID; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsID::GetData(nsID** aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + + if (mData) { + *aData = static_cast<nsID*>(nsMemory::Clone(mData, sizeof(nsID))); + } else { + *aData = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsID::SetData(const nsID* aData) +{ + if (mData) { + free(mData); + } + + if (aData) { + mData = static_cast<nsID*>(nsMemory::Clone(aData, sizeof(nsID))); + } else { + mData = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsID::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + + if (mData) { + *aResult = mData->ToString(); + } else { + static const char nullStr[] = "null"; + *aResult = static_cast<char*>(nsMemory::Clone(nullStr, sizeof(nullStr))); + } + + return NS_OK; +} + +/***************************************************************************** + * nsSupportsCString + *****************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsCString, nsISupportsCString, + nsISupportsPrimitive) + +NS_IMETHODIMP +nsSupportsCString::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_CSTRING; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsCString::GetData(nsACString& aData) +{ + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsCString::ToString(char** aResult) +{ + *aResult = ToNewCString(mData); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsCString::SetData(const nsACString& aData) +{ + bool ok = mData.Assign(aData, mozilla::fallible); + if (!ok) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +/***************************************************************************** + * nsSupportsString + *****************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsString, nsISupportsString, + nsISupportsPrimitive) + +NS_IMETHODIMP +nsSupportsString::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_STRING; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsString::GetData(nsAString& aData) +{ + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsString::ToString(char16_t** aResult) +{ + *aResult = ToNewUnicode(mData); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsString::SetData(const nsAString& aData) +{ + bool ok = mData.Assign(aData, mozilla::fallible); + if (!ok) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRBool, nsISupportsPRBool, + nsISupportsPrimitive) + +nsSupportsPRBool::nsSupportsPRBool() + : mData(false) +{ +} + +NS_IMETHODIMP +nsSupportsPRBool::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRBOOL; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRBool::GetData(bool* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRBool::SetData(bool aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRBool::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + const char* str = mData ? "true" : "false"; + *aResult = static_cast<char*>(nsMemory::Clone(str, (strlen(str) + 1) * + sizeof(char))); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint8, nsISupportsPRUint8, + nsISupportsPrimitive) + +nsSupportsPRUint8::nsSupportsPRUint8() + : mData(0) +{ +} + +NS_IMETHODIMP +nsSupportsPRUint8::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT8; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint8::GetData(uint8_t* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint8::SetData(uint8_t aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint8::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%u", static_cast<unsigned int>(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint16, nsISupportsPRUint16, + nsISupportsPrimitive) + +nsSupportsPRUint16::nsSupportsPRUint16() + : mData(0) +{ +} + +NS_IMETHODIMP +nsSupportsPRUint16::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT16; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint16::GetData(uint16_t* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint16::SetData(uint16_t aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint16::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%u", static_cast<unsigned int>(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint32, nsISupportsPRUint32, + nsISupportsPrimitive) + +nsSupportsPRUint32::nsSupportsPRUint32() + : mData(0) +{ +} + +NS_IMETHODIMP +nsSupportsPRUint32::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT32; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint32::GetData(uint32_t* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint32::SetData(uint32_t aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint32::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%u", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint64, nsISupportsPRUint64, + nsISupportsPrimitive) + +nsSupportsPRUint64::nsSupportsPRUint64() + : mData(0) +{ +} + +NS_IMETHODIMP +nsSupportsPRUint64::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT64; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint64::GetData(uint64_t* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint64::SetData(uint64_t aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint64::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%llu", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRTime, nsISupportsPRTime, + nsISupportsPrimitive) + +nsSupportsPRTime::nsSupportsPRTime() + : mData(0) +{ +} + +NS_IMETHODIMP +nsSupportsPRTime::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRTIME; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRTime::GetData(PRTime* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRTime::SetData(PRTime aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRTime::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%" PRIu64, mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsChar, nsISupportsChar, + nsISupportsPrimitive) + +nsSupportsChar::nsSupportsChar() + : mData(0) +{ +} + +NS_IMETHODIMP +nsSupportsChar::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_CHAR; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsChar::GetData(char* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsChar::SetData(char aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsChar::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = static_cast<char*>(moz_xmalloc(2 * sizeof(char))); + *aResult[0] = mData; + *aResult[1] = '\0'; + + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRInt16, nsISupportsPRInt16, + nsISupportsPrimitive) + +nsSupportsPRInt16::nsSupportsPRInt16() + : mData(0) +{ +} + +NS_IMETHODIMP +nsSupportsPRInt16::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRINT16; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt16::GetData(int16_t* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt16::SetData(int16_t aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt16::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%d", static_cast<int>(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRInt32, nsISupportsPRInt32, + nsISupportsPrimitive) + +nsSupportsPRInt32::nsSupportsPRInt32() + : mData(0) +{ +} + +NS_IMETHODIMP +nsSupportsPRInt32::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRINT32; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt32::GetData(int32_t* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt32::SetData(int32_t aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt32::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%d", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRInt64, nsISupportsPRInt64, + nsISupportsPrimitive) + +nsSupportsPRInt64::nsSupportsPRInt64() + : mData(0) +{ +} + +NS_IMETHODIMP +nsSupportsPRInt64::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRINT64; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt64::GetData(int64_t* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt64::SetData(int64_t aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt64::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%" PRId64, mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsFloat, nsISupportsFloat, + nsISupportsPrimitive) + +nsSupportsFloat::nsSupportsFloat() + : mData(float(0.0)) +{ +} + +NS_IMETHODIMP +nsSupportsFloat::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_FLOAT; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsFloat::GetData(float* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsFloat::SetData(float aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsFloat::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%f", static_cast<double>(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsDouble, nsISupportsDouble, + nsISupportsPrimitive) + +nsSupportsDouble::nsSupportsDouble() + : mData(double(0.0)) +{ +} + +NS_IMETHODIMP +nsSupportsDouble::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_DOUBLE; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDouble::GetData(double* aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDouble::SetData(double aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDouble::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%f", mData); + return NS_OK; +} + +/***************************************************************************/ + + +NS_IMPL_ISUPPORTS(nsSupportsVoid, nsISupportsVoid, + nsISupportsPrimitive) + +nsSupportsVoid::nsSupportsVoid() + : mData(nullptr) +{ +} + +NS_IMETHODIMP +nsSupportsVoid::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_VOID; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsVoid::GetData(void** aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsVoid::SetData(void* aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsVoid::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + static const char str[] = "[raw data]"; + *aResult = static_cast<char*>(nsMemory::Clone(str, sizeof(str))); + return NS_OK; +} + +/***************************************************************************/ + + +NS_IMPL_ISUPPORTS(nsSupportsInterfacePointer, + nsISupportsInterfacePointer, + nsISupportsPrimitive) + +nsSupportsInterfacePointer::nsSupportsInterfacePointer() + : mIID(nullptr) +{ +} + +nsSupportsInterfacePointer::~nsSupportsInterfacePointer() +{ + if (mIID) { + free(mIID); + } +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::GetType(uint16_t* aType) +{ + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_INTERFACE_POINTER; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::GetData(nsISupports** aData) +{ + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + NS_IF_ADDREF(*aData); + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::SetData(nsISupports* aData) +{ + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::GetDataIID(nsID** aIID) +{ + NS_ASSERTION(aIID, "Bad pointer"); + + if (mIID) { + *aIID = static_cast<nsID*>(nsMemory::Clone(mIID, sizeof(nsID))); + } else { + *aIID = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::SetDataIID(const nsID* aIID) +{ + if (mIID) { + free(mIID); + } + + if (aIID) { + mIID = static_cast<nsID*>(nsMemory::Clone(aIID, sizeof(nsID))); + } else { + mIID = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::ToString(char** aResult) +{ + NS_ASSERTION(aResult, "Bad pointer"); + + static const char str[] = "[interface pointer]"; + // jband sez: think about asking nsIInterfaceInfoManager whether + // the interface has a known human-readable name + *aResult = static_cast<char*>(nsMemory::Clone(str, sizeof(str))); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsDependentCString, nsISupportsCString, + nsISupportsPrimitive) + +nsSupportsDependentCString::nsSupportsDependentCString(const char* aStr) + : mData(aStr) +{ } + +NS_IMETHODIMP +nsSupportsDependentCString::GetType(uint16_t* aType) +{ + if (NS_WARN_IF(!aType)) { + return NS_ERROR_INVALID_ARG; + } + + *aType = TYPE_CSTRING; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDependentCString::GetData(nsACString& aData) +{ + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDependentCString::ToString(char** aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = ToNewCString(mData); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDependentCString::SetData(const nsACString& aData) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/xpcom/ds/nsSupportsPrimitives.h b/xpcom/ds/nsSupportsPrimitives.h new file mode 100644 index 000000000..17ed0f47f --- /dev/null +++ b/xpcom/ds/nsSupportsPrimitives.h @@ -0,0 +1,327 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsSupportsPrimitives_h__ +#define nsSupportsPrimitives_h__ + +#include "mozilla/Attributes.h" + +#include "nsISupportsPrimitives.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +class nsSupportsID final : public nsISupportsID +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSID + + nsSupportsID(); + +private: + ~nsSupportsID() {} + + nsID* mData; +}; + +/***************************************************************************/ + +class nsSupportsCString final : public nsISupportsCString +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + + nsSupportsCString() {} + +private: + ~nsSupportsCString() {} + + nsCString mData; +}; + +/***************************************************************************/ + +class nsSupportsString final : public nsISupportsString +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSSTRING + + nsSupportsString() {} + +private: + ~nsSupportsString() {} + + nsString mData; +}; + +/***************************************************************************/ + +class nsSupportsPRBool final : public nsISupportsPRBool +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRBOOL + + nsSupportsPRBool(); + +private: + ~nsSupportsPRBool() {} + + bool mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint8 final : public nsISupportsPRUint8 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT8 + + nsSupportsPRUint8(); + +private: + ~nsSupportsPRUint8() {} + + uint8_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint16 final : public nsISupportsPRUint16 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT16 + + nsSupportsPRUint16(); + +private: + ~nsSupportsPRUint16() {} + + uint16_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint32 final : public nsISupportsPRUint32 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT32 + + nsSupportsPRUint32(); + +private: + ~nsSupportsPRUint32() {} + + uint32_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint64 final : public nsISupportsPRUint64 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT64 + + nsSupportsPRUint64(); + +private: + ~nsSupportsPRUint64() {} + + uint64_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRTime final : public nsISupportsPRTime +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRTIME + + nsSupportsPRTime(); + +private: + ~nsSupportsPRTime() {} + + PRTime mData; +}; + +/***************************************************************************/ + +class nsSupportsChar final : public nsISupportsChar +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCHAR + + nsSupportsChar(); + +private: + ~nsSupportsChar() {} + + char mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt16 final : public nsISupportsPRInt16 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT16 + + nsSupportsPRInt16(); + +private: + ~nsSupportsPRInt16() {} + + int16_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt32 final : public nsISupportsPRInt32 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT32 + + nsSupportsPRInt32(); + +private: + ~nsSupportsPRInt32() {} + + int32_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt64 final : public nsISupportsPRInt64 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT64 + + nsSupportsPRInt64(); + +private: + ~nsSupportsPRInt64() {} + + int64_t mData; +}; + +/***************************************************************************/ + +class nsSupportsFloat final : public nsISupportsFloat +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSFLOAT + + nsSupportsFloat(); + +private: + ~nsSupportsFloat() {} + + float mData; +}; + +/***************************************************************************/ + +class nsSupportsDouble final : public nsISupportsDouble +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSDOUBLE + + nsSupportsDouble(); + +private: + ~nsSupportsDouble() {} + + double mData; +}; + +/***************************************************************************/ + +class nsSupportsVoid final : public nsISupportsVoid +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSVOID + + nsSupportsVoid(); + +private: + ~nsSupportsVoid() {} + + void* mData; +}; + +/***************************************************************************/ + +class nsSupportsInterfacePointer final : public nsISupportsInterfacePointer +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSINTERFACEPOINTER + + nsSupportsInterfacePointer(); + +private: + ~nsSupportsInterfacePointer(); + + nsCOMPtr<nsISupports> mData; + nsID* mIID; +}; + +/***************************************************************************/ + +/** + * Wraps a static const char* buffer for use with nsISupportsCString + * + * Only use this class with static buffers, or arena-allocated buffers of + * permanent lifetime! + */ +class nsSupportsDependentCString final : public nsISupportsCString +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + + explicit nsSupportsDependentCString(const char* aStr); + +private: + ~nsSupportsDependentCString() {} + + nsDependentCString mData; +}; + +#endif /* nsSupportsPrimitives_h__ */ diff --git a/xpcom/ds/nsVariant.cpp b/xpcom/ds/nsVariant.cpp new file mode 100644 index 000000000..edb020139 --- /dev/null +++ b/xpcom/ds/nsVariant.cpp @@ -0,0 +1,2220 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsVariant.h" +#include "prprf.h" +#include "prdtoa.h" +#include <math.h> +#include "nsCycleCollectionParticipant.h" +#include "xpt_struct.h" +#include "nsReadableUtils.h" +#include "nsMemory.h" +#include "nsString.h" +#include "nsCRTGlue.h" + +/***************************************************************************/ +// Helpers for static convert functions... + +static nsresult +String2Double(const char* aString, double* aResult) +{ + char* next; + double value = PR_strtod(aString, &next); + if (next == aString) { + return NS_ERROR_CANNOT_CONVERT_DATA; + } + *aResult = value; + return NS_OK; +} + +static nsresult +AString2Double(const nsAString& aString, double* aResult) +{ + char* pChars = ToNewCString(aString); + if (!pChars) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsresult rv = String2Double(pChars, aResult); + free(pChars); + return rv; +} + +static nsresult +AUTF8String2Double(const nsAUTF8String& aString, double* aResult) +{ + return String2Double(PromiseFlatUTF8String(aString).get(), aResult); +} + +static nsresult +ACString2Double(const nsACString& aString, double* aResult) +{ + return String2Double(PromiseFlatCString(aString).get(), aResult); +} + +// Fills aOutData with double, uint32_t, or int32_t. +// Returns NS_OK, an error code, or a non-NS_OK success code +nsresult +nsDiscriminatedUnion::ToManageableNumber(nsDiscriminatedUnion* aOutData) const +{ + nsresult rv; + + switch (mType) { + // This group results in a int32_t... + +#define CASE__NUMBER_INT32(type_, member_) \ + case nsIDataType::type_ : \ + aOutData->u.mInt32Value = u.member_ ; \ + aOutData->mType = nsIDataType::VTYPE_INT32; \ + return NS_OK; + + CASE__NUMBER_INT32(VTYPE_INT8, mInt8Value) + CASE__NUMBER_INT32(VTYPE_INT16, mInt16Value) + CASE__NUMBER_INT32(VTYPE_INT32, mInt32Value) + CASE__NUMBER_INT32(VTYPE_UINT8, mUint8Value) + CASE__NUMBER_INT32(VTYPE_UINT16, mUint16Value) + CASE__NUMBER_INT32(VTYPE_BOOL, mBoolValue) + CASE__NUMBER_INT32(VTYPE_CHAR, mCharValue) + CASE__NUMBER_INT32(VTYPE_WCHAR, mWCharValue) + +#undef CASE__NUMBER_INT32 + + // This group results in a uint32_t... + + case nsIDataType::VTYPE_UINT32: + aOutData->u.mInt32Value = u.mUint32Value; + aOutData->mType = nsIDataType::VTYPE_INT32; + return NS_OK; + + // This group results in a double... + + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT64: + // XXX Need boundary checking here. + // We may need to return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA + aOutData->u.mDoubleValue = double(u.mInt64Value); + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_FLOAT: + aOutData->u.mDoubleValue = u.mFloatValue; + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_DOUBLE: + aOutData->u.mDoubleValue = u.mDoubleValue; + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + rv = String2Double(u.str.mStringValue, &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_ASTRING: + rv = AString2Double(*u.mAStringValue, &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + rv = AUTF8String2Double(*u.mUTF8StringValue, + &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + rv = ACString2Double(*u.mCStringValue, + &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + rv = AString2Double(nsDependentString(u.wstr.mWStringValue), + &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + + // This group fails... + + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +/***************************************************************************/ +// Array helpers... + +void +nsDiscriminatedUnion::FreeArray() +{ + NS_ASSERTION(mType == nsIDataType::VTYPE_ARRAY, "bad FreeArray call"); + NS_ASSERTION(u.array.mArrayValue, "bad array"); + NS_ASSERTION(u.array.mArrayCount, "bad array count"); + +#define CASE__FREE_ARRAY_PTR(type_, ctype_) \ + case nsIDataType::type_ : \ + { \ + ctype_** p = (ctype_**) u.array.mArrayValue; \ + for (uint32_t i = u.array.mArrayCount; i > 0; p++, i--) \ + if (*p) \ + free((char*)*p); \ + break; \ + } + +#define CASE__FREE_ARRAY_IFACE(type_, ctype_) \ + case nsIDataType::type_ : \ + { \ + ctype_** p = (ctype_**) u.array.mArrayValue; \ + for (uint32_t i = u.array.mArrayCount; i > 0; p++, i--) \ + if (*p) \ + (*p)->Release(); \ + break; \ + } + + switch (u.array.mArrayType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + break; + + // XXX We ASSUME that "array of nsID" means "array of pointers to nsID". + CASE__FREE_ARRAY_PTR(VTYPE_ID, nsID) + CASE__FREE_ARRAY_PTR(VTYPE_CHAR_STR, char) + CASE__FREE_ARRAY_PTR(VTYPE_WCHAR_STR, char16_t) + CASE__FREE_ARRAY_IFACE(VTYPE_INTERFACE, nsISupports) + CASE__FREE_ARRAY_IFACE(VTYPE_INTERFACE_IS, nsISupports) + + // The rest are illegal. + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + NS_ERROR("bad type in array!"); + break; + } + + // Free the array memory. + free((char*)u.array.mArrayValue); + +#undef CASE__FREE_ARRAY_PTR +#undef CASE__FREE_ARRAY_IFACE +} + +static nsresult +CloneArray(uint16_t aInType, const nsIID* aInIID, + uint32_t aInCount, void* aInValue, + uint16_t* aOutType, nsIID* aOutIID, + uint32_t* aOutCount, void** aOutValue) +{ + NS_ASSERTION(aInCount, "bad param"); + NS_ASSERTION(aInValue, "bad param"); + NS_ASSERTION(aOutType, "bad param"); + NS_ASSERTION(aOutCount, "bad param"); + NS_ASSERTION(aOutValue, "bad param"); + + uint32_t allocatedValueCount = 0; + nsresult rv = NS_OK; + uint32_t i; + + // First we figure out the size of the elements for the new u.array. + + size_t elementSize; + size_t allocSize; + + switch (aInType) { + case nsIDataType::VTYPE_INT8: + elementSize = sizeof(int8_t); + break; + case nsIDataType::VTYPE_INT16: + elementSize = sizeof(int16_t); + break; + case nsIDataType::VTYPE_INT32: + elementSize = sizeof(int32_t); + break; + case nsIDataType::VTYPE_INT64: + elementSize = sizeof(int64_t); + break; + case nsIDataType::VTYPE_UINT8: + elementSize = sizeof(uint8_t); + break; + case nsIDataType::VTYPE_UINT16: + elementSize = sizeof(uint16_t); + break; + case nsIDataType::VTYPE_UINT32: + elementSize = sizeof(uint32_t); + break; + case nsIDataType::VTYPE_UINT64: + elementSize = sizeof(uint64_t); + break; + case nsIDataType::VTYPE_FLOAT: + elementSize = sizeof(float); + break; + case nsIDataType::VTYPE_DOUBLE: + elementSize = sizeof(double); + break; + case nsIDataType::VTYPE_BOOL: + elementSize = sizeof(bool); + break; + case nsIDataType::VTYPE_CHAR: + elementSize = sizeof(char); + break; + case nsIDataType::VTYPE_WCHAR: + elementSize = sizeof(char16_t); + break; + + // XXX We ASSUME that "array of nsID" means "array of pointers to nsID". + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + elementSize = sizeof(void*); + break; + + // The rest are illegal. + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + NS_ERROR("bad type in array!"); + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + + // Alloc the u.array. + + allocSize = aInCount * elementSize; + *aOutValue = moz_xmalloc(allocSize); + if (!*aOutValue) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Clone the elements. + + switch (aInType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + memcpy(*aOutValue, aInValue, allocSize); + break; + + case nsIDataType::VTYPE_INTERFACE_IS: + if (aOutIID) { + *aOutIID = *aInIID; + } + MOZ_FALLTHROUGH; + + case nsIDataType::VTYPE_INTERFACE: { + memcpy(*aOutValue, aInValue, allocSize); + + nsISupports** p = (nsISupports**)*aOutValue; + for (i = aInCount; i > 0; ++p, --i) + if (*p) { + (*p)->AddRef(); + } + break; + } + + // XXX We ASSUME that "array of nsID" means "array of pointers to nsID". + case nsIDataType::VTYPE_ID: { + nsID** inp = (nsID**)aInValue; + nsID** outp = (nsID**)*aOutValue; + for (i = aInCount; i > 0; --i) { + nsID* idp = *(inp++); + if (idp) { + if (!(*(outp++) = (nsID*)nsMemory::Clone((char*)idp, sizeof(nsID)))) { + goto bad; + } + } else { + *(outp++) = nullptr; + } + allocatedValueCount++; + } + break; + } + + case nsIDataType::VTYPE_CHAR_STR: { + char** inp = (char**)aInValue; + char** outp = (char**)*aOutValue; + for (i = aInCount; i > 0; i--) { + char* str = *(inp++); + if (str) { + if (!(*(outp++) = (char*)nsMemory::Clone( + str, (strlen(str) + 1) * sizeof(char)))) { + goto bad; + } + } else { + *(outp++) = nullptr; + } + allocatedValueCount++; + } + break; + } + + case nsIDataType::VTYPE_WCHAR_STR: { + char16_t** inp = (char16_t**)aInValue; + char16_t** outp = (char16_t**)*aOutValue; + for (i = aInCount; i > 0; i--) { + char16_t* str = *(inp++); + if (str) { + if (!(*(outp++) = (char16_t*)nsMemory::Clone( + str, (NS_strlen(str) + 1) * sizeof(char16_t)))) { + goto bad; + } + } else { + *(outp++) = nullptr; + } + allocatedValueCount++; + } + break; + } + + // The rest are illegal. + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + default: + NS_ERROR("bad type in array!"); + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + *aOutType = aInType; + *aOutCount = aInCount; + return NS_OK; + +bad: + if (*aOutValue) { + char** p = (char**)*aOutValue; + for (i = allocatedValueCount; i > 0; ++p, --i) + if (*p) { + free(*p); + } + free((char*)*aOutValue); + *aOutValue = nullptr; + } + return rv; +} + +/***************************************************************************/ + +#define TRIVIAL_DATA_CONVERTER(type_, member_, retval_) \ + if (mType == nsIDataType::type_) { \ + *retval_ = u.member_; \ + return NS_OK; \ + } + +#define NUMERIC_CONVERSION_METHOD_BEGIN(type_, Ctype_, name_) \ +nsresult \ +nsDiscriminatedUnion::ConvertTo##name_ (Ctype_* aResult) const \ +{ \ + TRIVIAL_DATA_CONVERTER(type_, m##name_##Value, aResult) \ + nsDiscriminatedUnion tempData; \ + nsresult rv = ToManageableNumber(&tempData); \ + /* */ \ + /* NOTE: rv may indicate a success code that we want to preserve */ \ + /* For the final return. So all the return cases below should return */ \ + /* this rv when indicating success. */ \ + /* */ \ + if (NS_FAILED(rv)) \ + return rv; \ + switch(tempData.mType) \ + { + +#define CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(Ctype_) \ + case nsIDataType::VTYPE_INT32: \ + *aResult = ( Ctype_ ) tempData.u.mInt32Value; \ + return rv; + +#define CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(Ctype_, min_, max_) \ + case nsIDataType::VTYPE_INT32: \ + { \ + int32_t value = tempData.u.mInt32Value; \ + if (value < min_ || value > max_) \ + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + *aResult = ( Ctype_ ) value; \ + return rv; \ + } + +#define CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(Ctype_) \ + case nsIDataType::VTYPE_UINT32: \ + *aResult = ( Ctype_ ) tempData.u.mUint32Value; \ + return rv; + +#define CASE__NUMERIC_CONVERSION_UINT32_MAX(Ctype_, max_) \ + case nsIDataType::VTYPE_UINT32: \ + { \ + uint32_t value = tempData.u.mUint32Value; \ + if (value > max_) \ + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + *aResult = ( Ctype_ ) value; \ + return rv; \ + } + +#define CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(Ctype_) \ + case nsIDataType::VTYPE_DOUBLE: \ + *aResult = ( Ctype_ ) tempData.u.mDoubleValue; \ + return rv; + +#define CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX(Ctype_, min_, max_) \ + case nsIDataType::VTYPE_DOUBLE: \ + { \ + double value = tempData.u.mDoubleValue; \ + if (value < min_ || value > max_) \ + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + *aResult = ( Ctype_ ) value; \ + return rv; \ + } + +#define CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(Ctype_, min_, max_) \ + case nsIDataType::VTYPE_DOUBLE: \ + { \ + double value = tempData.u.mDoubleValue; \ + if (value < min_ || value > max_) \ + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + *aResult = ( Ctype_ ) value; \ + return (0.0 == fmod(value,1.0)) ? \ + rv : NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA; \ + } + +#define CASES__NUMERIC_CONVERSION_NORMAL(Ctype_, min_, max_) \ + CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(Ctype_, min_, max_) \ + CASE__NUMERIC_CONVERSION_UINT32_MAX(Ctype_, max_) \ + CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(Ctype_, min_, max_) + +#define NUMERIC_CONVERSION_METHOD_END \ + default: \ + NS_ERROR("bad type returned from ToManageableNumber"); \ + return NS_ERROR_CANNOT_CONVERT_DATA; \ + } \ +} + +#define NUMERIC_CONVERSION_METHOD_NORMAL(type_, Ctype_, name_, min_, max_) \ + NUMERIC_CONVERSION_METHOD_BEGIN(type_, Ctype_, name_) \ + CASES__NUMERIC_CONVERSION_NORMAL(Ctype_, min_, max_) \ + NUMERIC_CONVERSION_METHOD_END + +/***************************************************************************/ +// These expand into full public methods... + +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_INT8, uint8_t, Int8, (-127 - 1), 127) +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_INT16, int16_t, Int16, (-32767 - 1), 32767) + +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_INT32, int32_t, Int32) + CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(int32_t) + CASE__NUMERIC_CONVERSION_UINT32_MAX(int32_t, 2147483647) + CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(int32_t, (-2147483647 - 1), 2147483647) +NUMERIC_CONVERSION_METHOD_END + +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_UINT8, uint8_t, Uint8, 0, 255) +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_UINT16, uint16_t, Uint16, 0, 65535) + +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_UINT32, uint32_t, Uint32) + CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(uint32_t, 0, 2147483647) + CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(uint32_t) + CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(uint32_t, 0, 4294967295U) +NUMERIC_CONVERSION_METHOD_END + +// XXX toFloat convertions need to be fixed! +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_FLOAT, float, Float) + CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(float) + CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(float) + CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(float) +NUMERIC_CONVERSION_METHOD_END + +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_DOUBLE, double, Double) + CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(double) + CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(double) + CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(double) +NUMERIC_CONVERSION_METHOD_END + +// XXX toChar convertions need to be fixed! +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_CHAR, char, Char) + CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(char) + CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(char) + CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(char) +NUMERIC_CONVERSION_METHOD_END + +// XXX toWChar convertions need to be fixed! +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_WCHAR, char16_t, WChar) + CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(char16_t) + CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(char16_t) + CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(char16_t) +NUMERIC_CONVERSION_METHOD_END + +#undef NUMERIC_CONVERSION_METHOD_BEGIN +#undef CASE__NUMERIC_CONVERSION_INT32_JUST_CAST +#undef CASE__NUMERIC_CONVERSION_INT32_MIN_MAX +#undef CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST +#undef CASE__NUMERIC_CONVERSION_UINT32_MIN_MAX +#undef CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST +#undef CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX +#undef CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT +#undef CASES__NUMERIC_CONVERSION_NORMAL +#undef NUMERIC_CONVERSION_METHOD_END +#undef NUMERIC_CONVERSION_METHOD_NORMAL + +/***************************************************************************/ + +// Just leverage a numeric converter for bool (but restrict the values). +// XXX Is this really what we want to do? + +nsresult +nsDiscriminatedUnion::ConvertToBool(bool* aResult) const +{ + TRIVIAL_DATA_CONVERTER(VTYPE_BOOL, mBoolValue, aResult) + + double val; + nsresult rv = ConvertToDouble(&val); + if (NS_FAILED(rv)) { + return rv; + } + *aResult = 0.0 != val; + return rv; +} + +/***************************************************************************/ + +nsresult +nsDiscriminatedUnion::ConvertToInt64(int64_t* aResult) const +{ + TRIVIAL_DATA_CONVERTER(VTYPE_INT64, mInt64Value, aResult) + TRIVIAL_DATA_CONVERTER(VTYPE_UINT64, mUint64Value, aResult) + + nsDiscriminatedUnion tempData; + nsresult rv = ToManageableNumber(&tempData); + if (NS_FAILED(rv)) { + return rv; + } + switch (tempData.mType) { + case nsIDataType::VTYPE_INT32: + *aResult = tempData.u.mInt32Value; + return rv; + case nsIDataType::VTYPE_UINT32: + *aResult = tempData.u.mUint32Value; + return rv; + case nsIDataType::VTYPE_DOUBLE: + // XXX should check for data loss here! + *aResult = tempData.u.mDoubleValue; + return rv; + default: + NS_ERROR("bad type returned from ToManageableNumber"); + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +nsresult +nsDiscriminatedUnion::ConvertToUint64(uint64_t* aResult) const +{ + return ConvertToInt64((int64_t*)aResult); +} + +/***************************************************************************/ + +bool +nsDiscriminatedUnion::String2ID(nsID* aPid) const +{ + nsAutoString tempString; + nsAString* pString; + + switch (mType) { + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + return aPid->Parse(u.str.mStringValue); + case nsIDataType::VTYPE_CSTRING: + return aPid->Parse(PromiseFlatCString(*u.mCStringValue).get()); + case nsIDataType::VTYPE_UTF8STRING: + return aPid->Parse(PromiseFlatUTF8String(*u.mUTF8StringValue).get()); + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + pString = u.mAStringValue; + break; + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + tempString.Assign(u.wstr.mWStringValue); + pString = &tempString; + break; + default: + NS_ERROR("bad type in call to String2ID"); + return false; + } + + char* pChars = ToNewCString(*pString); + if (!pChars) { + return false; + } + bool result = aPid->Parse(pChars); + free(pChars); + return result; +} + +nsresult +nsDiscriminatedUnion::ConvertToID(nsID* aResult) const +{ + nsID id; + + switch (mType) { + case nsIDataType::VTYPE_ID: + *aResult = u.mIDValue; + return NS_OK; + case nsIDataType::VTYPE_INTERFACE: + *aResult = NS_GET_IID(nsISupports); + return NS_OK; + case nsIDataType::VTYPE_INTERFACE_IS: + *aResult = u.iface.mInterfaceID; + return NS_OK; + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + if (!String2ID(&id)) { + return NS_ERROR_CANNOT_CONVERT_DATA; + } + *aResult = id; + return NS_OK; + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +/***************************************************************************/ + +nsresult +nsDiscriminatedUnion::ToString(nsACString& aOutString) const +{ + char* ptr; + + switch (mType) { + // all the stuff we don't handle... + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_WCHAR: + NS_ERROR("ToString being called for a string type - screwy logic!"); + MOZ_FALLTHROUGH; + + // XXX We might want stringified versions of these... ??? + + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_EMPTY: + aOutString.SetIsVoid(true); + return NS_OK; + + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + + // nsID has its own text formatter. + + case nsIDataType::VTYPE_ID: + ptr = u.mIDValue.ToString(); + if (!ptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + aOutString.Assign(ptr); + free(ptr); + return NS_OK; + + // Can't use PR_smprintf for floats, since it's locale-dependent +#define CASE__APPENDFLOAT_NUMBER(type_, member_) \ + case nsIDataType::type_ : \ + { \ + nsAutoCString str; \ + str.AppendFloat(u.member_); \ + aOutString.Assign(str); \ + return NS_OK; \ + } + + CASE__APPENDFLOAT_NUMBER(VTYPE_FLOAT, mFloatValue) + CASE__APPENDFLOAT_NUMBER(VTYPE_DOUBLE, mDoubleValue) + +#undef CASE__APPENDFLOAT_NUMBER + + // the rest can be PR_smprintf'd and use common code. + +#define CASE__SMPRINTF_NUMBER(type_, format_, cast_, member_) \ + case nsIDataType::type_: \ + ptr = PR_smprintf( format_ , (cast_) u.member_); \ + break; + + CASE__SMPRINTF_NUMBER(VTYPE_INT8, "%d", int, mInt8Value) + CASE__SMPRINTF_NUMBER(VTYPE_INT16, "%d", int, mInt16Value) + CASE__SMPRINTF_NUMBER(VTYPE_INT32, "%d", int, mInt32Value) + CASE__SMPRINTF_NUMBER(VTYPE_INT64, "%lld", int64_t, mInt64Value) + + CASE__SMPRINTF_NUMBER(VTYPE_UINT8, "%u", unsigned, mUint8Value) + CASE__SMPRINTF_NUMBER(VTYPE_UINT16, "%u", unsigned, mUint16Value) + CASE__SMPRINTF_NUMBER(VTYPE_UINT32, "%u", unsigned, mUint32Value) + CASE__SMPRINTF_NUMBER(VTYPE_UINT64, "%llu", int64_t, mUint64Value) + + // XXX Would we rather print "true" / "false" ? + CASE__SMPRINTF_NUMBER(VTYPE_BOOL, "%d", int, mBoolValue) + + CASE__SMPRINTF_NUMBER(VTYPE_CHAR, "%c", char, mCharValue) + +#undef CASE__SMPRINTF_NUMBER + } + + if (!ptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + aOutString.Assign(ptr); + PR_smprintf_free(ptr); + return NS_OK; +} + +nsresult +nsDiscriminatedUnion::ConvertToAString(nsAString& aResult) const +{ + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + aResult.Assign(*u.mAStringValue); + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + CopyASCIItoUTF16(*u.mCStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + CopyUTF8toUTF16(*u.mUTF8StringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + CopyASCIItoUTF16(u.str.mStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + aResult.Assign(u.wstr.mWStringValue); + return NS_OK; + case nsIDataType::VTYPE_STRING_SIZE_IS: + CopyASCIItoUTF16(nsDependentCString(u.str.mStringValue, + u.str.mStringLength), + aResult); + return NS_OK; + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + aResult.Assign(u.wstr.mWStringValue, u.wstr.mWStringLength); + return NS_OK; + case nsIDataType::VTYPE_WCHAR: + aResult.Assign(u.mWCharValue); + return NS_OK; + default: { + nsAutoCString tempCString; + nsresult rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + CopyASCIItoUTF16(tempCString, aResult); + return NS_OK; + } + } +} + +nsresult +nsDiscriminatedUnion::ConvertToACString(nsACString& aResult) const +{ + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + LossyCopyUTF16toASCII(*u.mAStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + aResult.Assign(*u.mCStringValue); + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + // XXX This is an extra copy that should be avoided + // once Jag lands support for UTF8String and associated + // conversion methods. + LossyCopyUTF16toASCII(NS_ConvertUTF8toUTF16(*u.mUTF8StringValue), + aResult); + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + aResult.Assign(*u.str.mStringValue); + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + LossyCopyUTF16toASCII(nsDependentString(u.wstr.mWStringValue), + aResult); + return NS_OK; + case nsIDataType::VTYPE_STRING_SIZE_IS: + aResult.Assign(u.str.mStringValue, u.str.mStringLength); + return NS_OK; + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + LossyCopyUTF16toASCII(nsDependentString(u.wstr.mWStringValue, + u.wstr.mWStringLength), + aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR: { + const char16_t* str = &u.mWCharValue; + LossyCopyUTF16toASCII(Substring(str, 1), aResult); + return NS_OK; + } + default: + return ToString(aResult); + } +} + +nsresult +nsDiscriminatedUnion::ConvertToAUTF8String(nsAUTF8String& aResult) const +{ + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + CopyUTF16toUTF8(*u.mAStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + // XXX Extra copy, can be removed if we're sure CSTRING can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(*u.mCStringValue), + aResult); + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + aResult.Assign(*u.mUTF8StringValue); + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + // XXX Extra copy, can be removed if we're sure CHAR_STR can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(u.str.mStringValue), + aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + CopyUTF16toUTF8(u.wstr.mWStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_STRING_SIZE_IS: + // XXX Extra copy, can be removed if we're sure CHAR_STR can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16( + nsDependentCString(u.str.mStringValue, + u.str.mStringLength)), aResult); + return NS_OK; + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + CopyUTF16toUTF8(nsDependentString(u.wstr.mWStringValue, + u.wstr.mWStringLength), + aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR: { + const char16_t* str = &u.mWCharValue; + CopyUTF16toUTF8(Substring(str, 1), aResult); + return NS_OK; + } + default: { + nsAutoCString tempCString; + nsresult rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + // XXX Extra copy, can be removed if we're sure tempCString can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(tempCString), aResult); + return NS_OK; + } + } +} + +nsresult +nsDiscriminatedUnion::ConvertToString(char** aResult) const +{ + uint32_t ignored; + return ConvertToStringWithSize(&ignored, aResult); +} + +nsresult +nsDiscriminatedUnion::ConvertToWString(char16_t** aResult) const +{ + uint32_t ignored; + return ConvertToWStringWithSize(&ignored, aResult); +} + +nsresult +nsDiscriminatedUnion::ConvertToStringWithSize(uint32_t* aSize, char** aStr) const +{ + nsAutoString tempString; + nsAutoCString tempCString; + nsresult rv; + + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + *aSize = u.mAStringValue->Length(); + *aStr = ToNewCString(*u.mAStringValue); + break; + case nsIDataType::VTYPE_CSTRING: + *aSize = u.mCStringValue->Length(); + *aStr = ToNewCString(*u.mCStringValue); + break; + case nsIDataType::VTYPE_UTF8STRING: { + // XXX This is doing 1 extra copy. Need to fix this + // when Jag lands UTF8String + // we want: + // *aSize = *mUTF8StringValue->Length(); + // *aStr = ToNewCString(*mUTF8StringValue); + // But this will have to do for now. + const NS_ConvertUTF8toUTF16 tempString16(*u.mUTF8StringValue); + *aSize = tempString16.Length(); + *aStr = ToNewCString(tempString16); + break; + } + case nsIDataType::VTYPE_CHAR_STR: { + nsDependentCString cString(u.str.mStringValue); + *aSize = cString.Length(); + *aStr = ToNewCString(cString); + break; + } + case nsIDataType::VTYPE_WCHAR_STR: { + nsDependentString string(u.wstr.mWStringValue); + *aSize = string.Length(); + *aStr = ToNewCString(string); + break; + } + case nsIDataType::VTYPE_STRING_SIZE_IS: { + nsDependentCString cString(u.str.mStringValue, + u.str.mStringLength); + *aSize = cString.Length(); + *aStr = ToNewCString(cString); + break; + } + case nsIDataType::VTYPE_WSTRING_SIZE_IS: { + nsDependentString string(u.wstr.mWStringValue, + u.wstr.mWStringLength); + *aSize = string.Length(); + *aStr = ToNewCString(string); + break; + } + case nsIDataType::VTYPE_WCHAR: + tempString.Assign(u.mWCharValue); + *aSize = tempString.Length(); + *aStr = ToNewCString(tempString); + break; + default: + rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + *aSize = tempCString.Length(); + *aStr = ToNewCString(tempCString); + break; + } + + return *aStr ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} +nsresult +nsDiscriminatedUnion::ConvertToWStringWithSize(uint32_t* aSize, char16_t** aStr) const +{ + nsAutoString tempString; + nsAutoCString tempCString; + nsresult rv; + + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + *aSize = u.mAStringValue->Length(); + *aStr = ToNewUnicode(*u.mAStringValue); + break; + case nsIDataType::VTYPE_CSTRING: + *aSize = u.mCStringValue->Length(); + *aStr = ToNewUnicode(*u.mCStringValue); + break; + case nsIDataType::VTYPE_UTF8STRING: { + *aStr = UTF8ToNewUnicode(*u.mUTF8StringValue, aSize); + break; + } + case nsIDataType::VTYPE_CHAR_STR: { + nsDependentCString cString(u.str.mStringValue); + *aSize = cString.Length(); + *aStr = ToNewUnicode(cString); + break; + } + case nsIDataType::VTYPE_WCHAR_STR: { + nsDependentString string(u.wstr.mWStringValue); + *aSize = string.Length(); + *aStr = ToNewUnicode(string); + break; + } + case nsIDataType::VTYPE_STRING_SIZE_IS: { + nsDependentCString cString(u.str.mStringValue, + u.str.mStringLength); + *aSize = cString.Length(); + *aStr = ToNewUnicode(cString); + break; + } + case nsIDataType::VTYPE_WSTRING_SIZE_IS: { + nsDependentString string(u.wstr.mWStringValue, + u.wstr.mWStringLength); + *aSize = string.Length(); + *aStr = ToNewUnicode(string); + break; + } + case nsIDataType::VTYPE_WCHAR: + tempString.Assign(u.mWCharValue); + *aSize = tempString.Length(); + *aStr = ToNewUnicode(tempString); + break; + default: + rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + *aSize = tempCString.Length(); + *aStr = ToNewUnicode(tempCString); + break; + } + + return *aStr ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult +nsDiscriminatedUnion::ConvertToISupports(nsISupports** aResult) const +{ + switch (mType) { + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + if (u.iface.mInterfaceValue) { + return u.iface.mInterfaceValue-> + QueryInterface(NS_GET_IID(nsISupports), (void**)aResult); + } else { + *aResult = nullptr; + return NS_OK; + } + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +nsresult +nsDiscriminatedUnion::ConvertToInterface(nsIID** aIID, + void** aInterface) const +{ + const nsIID* piid; + + switch (mType) { + case nsIDataType::VTYPE_INTERFACE: + piid = &NS_GET_IID(nsISupports); + break; + case nsIDataType::VTYPE_INTERFACE_IS: + piid = &u.iface.mInterfaceID; + break; + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + *aIID = (nsIID*)nsMemory::Clone(piid, sizeof(nsIID)); + if (!*aIID) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (u.iface.mInterfaceValue) { + return u.iface.mInterfaceValue->QueryInterface(*piid, aInterface); + } + + *aInterface = nullptr; + return NS_OK; +} + +nsresult +nsDiscriminatedUnion::ConvertToArray(uint16_t* aType, nsIID* aIID, + uint32_t* aCount, void** aPtr) const +{ + // XXX perhaps we'd like to add support for converting each of the various + // types into an array containing one element of that type. We can leverage + // CloneArray to do this if we want to support this. + + if (mType == nsIDataType::VTYPE_ARRAY) { + return CloneArray(u.array.mArrayType, &u.array.mArrayInterfaceID, + u.array.mArrayCount, u.array.mArrayValue, + aType, aIID, aCount, aPtr); + } + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +/***************************************************************************/ +// static setter functions... + +#define DATA_SETTER_PROLOGUE \ + Cleanup() + +#define DATA_SETTER_EPILOGUE(type_) \ + mType = nsIDataType::type_; + +#define DATA_SETTER(type_, member_, value_) \ + DATA_SETTER_PROLOGUE; \ + u.member_ = value_; \ + DATA_SETTER_EPILOGUE(type_) + +#define DATA_SETTER_WITH_CAST(type_, member_, cast_, value_) \ + DATA_SETTER_PROLOGUE; \ + u.member_ = cast_ value_; \ + DATA_SETTER_EPILOGUE(type_) + + +/********************************************/ + +#define CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) \ + { \ + +#define CASE__SET_FROM_VARIANT_VTYPE__GETTER(member_, name_) \ + rv = aValue->GetAs##name_ (&(u.member_ )); + +#define CASE__SET_FROM_VARIANT_VTYPE__GETTER_CAST(cast_, member_, name_) \ + rv = aValue->GetAs##name_ ( cast_ &(u.member_ )); + +#define CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_) \ + if (NS_SUCCEEDED(rv)) { \ + mType = nsIDataType::type_ ; \ + } \ + break; \ + } + +#define CASE__SET_FROM_VARIANT_TYPE(type_, member_, name_) \ + case nsIDataType::type_: \ + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) \ + CASE__SET_FROM_VARIANT_VTYPE__GETTER(member_, name_) \ + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_) + +#define CASE__SET_FROM_VARIANT_VTYPE_CAST(type_, cast_, member_, name_) \ + case nsIDataType::type_ : \ + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) \ + CASE__SET_FROM_VARIANT_VTYPE__GETTER_CAST(cast_, member_, name_) \ + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_) + + +nsresult +nsDiscriminatedUnion::SetFromVariant(nsIVariant* aValue) +{ + uint16_t type; + nsresult rv; + + Cleanup(); + + rv = aValue->GetDataType(&type); + if (NS_FAILED(rv)) { + return rv; + } + + switch (type) { + CASE__SET_FROM_VARIANT_VTYPE_CAST(VTYPE_INT8, (uint8_t*), mInt8Value, + Int8) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_INT16, mInt16Value, Int16) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_INT32, mInt32Value, Int32) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT8, mUint8Value, Uint8) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT16, mUint16Value, Uint16) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT32, mUint32Value, Uint32) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_FLOAT, mFloatValue, Float) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_DOUBLE, mDoubleValue, Double) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_BOOL , mBoolValue, Bool) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_CHAR, mCharValue, Char) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_WCHAR, mWCharValue, WChar) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_ID, mIDValue, ID) + + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_ASTRING); + u.mAStringValue = new nsString(); + if (!u.mAStringValue) { + return NS_ERROR_OUT_OF_MEMORY; + } + rv = aValue->GetAsAString(*u.mAStringValue); + if (NS_FAILED(rv)) { + delete u.mAStringValue; + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_ASTRING) + + case nsIDataType::VTYPE_CSTRING: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_CSTRING); + u.mCStringValue = new nsCString(); + if (!u.mCStringValue) { + return NS_ERROR_OUT_OF_MEMORY; + } + rv = aValue->GetAsACString(*u.mCStringValue); + if (NS_FAILED(rv)) { + delete u.mCStringValue; + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_CSTRING) + + case nsIDataType::VTYPE_UTF8STRING: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_UTF8STRING); + u.mUTF8StringValue = new nsUTF8String(); + if (!u.mUTF8StringValue) { + return NS_ERROR_OUT_OF_MEMORY; + } + rv = aValue->GetAsAUTF8String(*u.mUTF8StringValue); + if (NS_FAILED(rv)) { + delete u.mUTF8StringValue; + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_UTF8STRING) + + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_STRING_SIZE_IS); + rv = aValue->GetAsStringWithSize(&u.str.mStringLength, + &u.str.mStringValue); + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_STRING_SIZE_IS) + + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_INTERFACE_IS); + // XXX This iid handling is ugly! + nsIID* iid; + rv = aValue->GetAsInterface(&iid, (void**)&u.iface.mInterfaceValue); + if (NS_SUCCEEDED(rv)) { + u.iface.mInterfaceID = *iid; + free((char*)iid); + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_INTERFACE_IS) + + case nsIDataType::VTYPE_ARRAY: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_ARRAY); + rv = aValue->GetAsArray(&u.array.mArrayType, + &u.array.mArrayInterfaceID, + &u.array.mArrayCount, + &u.array.mArrayValue); + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_ARRAY) + + case nsIDataType::VTYPE_VOID: + SetToVoid(); + rv = NS_OK; + break; + case nsIDataType::VTYPE_EMPTY_ARRAY: + SetToEmptyArray(); + rv = NS_OK; + break; + case nsIDataType::VTYPE_EMPTY: + SetToEmpty(); + rv = NS_OK; + break; + default: + NS_ERROR("bad type in variant!"); + rv = NS_ERROR_FAILURE; + break; + } + return rv; +} + +void +nsDiscriminatedUnion::SetFromInt8(uint8_t aValue) +{ + DATA_SETTER_WITH_CAST(VTYPE_INT8, mInt8Value, (uint8_t), aValue); +} +void +nsDiscriminatedUnion::SetFromInt16(int16_t aValue) +{ + DATA_SETTER(VTYPE_INT16, mInt16Value, aValue); +} +void +nsDiscriminatedUnion::SetFromInt32(int32_t aValue) +{ + DATA_SETTER(VTYPE_INT32, mInt32Value, aValue); +} +void +nsDiscriminatedUnion::SetFromInt64(int64_t aValue) +{ + DATA_SETTER(VTYPE_INT64, mInt64Value, aValue); +} +void +nsDiscriminatedUnion::SetFromUint8(uint8_t aValue) +{ + DATA_SETTER(VTYPE_UINT8, mUint8Value, aValue); +} +void +nsDiscriminatedUnion::SetFromUint16(uint16_t aValue) +{ + DATA_SETTER(VTYPE_UINT16, mUint16Value, aValue); +} +void +nsDiscriminatedUnion::SetFromUint32(uint32_t aValue) +{ + DATA_SETTER(VTYPE_UINT32, mUint32Value, aValue); +} +void +nsDiscriminatedUnion::SetFromUint64(uint64_t aValue) +{ + DATA_SETTER(VTYPE_UINT64, mUint64Value, aValue); +} +void +nsDiscriminatedUnion::SetFromFloat(float aValue) +{ + DATA_SETTER(VTYPE_FLOAT, mFloatValue, aValue); +} +void +nsDiscriminatedUnion::SetFromDouble(double aValue) +{ + DATA_SETTER(VTYPE_DOUBLE, mDoubleValue, aValue); +} +void +nsDiscriminatedUnion::SetFromBool(bool aValue) +{ + DATA_SETTER(VTYPE_BOOL, mBoolValue, aValue); +} +void +nsDiscriminatedUnion::SetFromChar(char aValue) +{ + DATA_SETTER(VTYPE_CHAR, mCharValue, aValue); +} +void +nsDiscriminatedUnion::SetFromWChar(char16_t aValue) +{ + DATA_SETTER(VTYPE_WCHAR, mWCharValue, aValue); +} +void +nsDiscriminatedUnion::SetFromID(const nsID& aValue) +{ + DATA_SETTER(VTYPE_ID, mIDValue, aValue); +} +void +nsDiscriminatedUnion::SetFromAString(const nsAString& aValue) +{ + DATA_SETTER_PROLOGUE; + u.mAStringValue = new nsString(aValue); + DATA_SETTER_EPILOGUE(VTYPE_ASTRING); +} + +void +nsDiscriminatedUnion::SetFromDOMString(const nsAString& aValue) +{ + DATA_SETTER_PROLOGUE; + u.mAStringValue = new nsString(aValue); + DATA_SETTER_EPILOGUE(VTYPE_DOMSTRING); +} + +void +nsDiscriminatedUnion::SetFromACString(const nsACString& aValue) +{ + DATA_SETTER_PROLOGUE; + u.mCStringValue = new nsCString(aValue); + DATA_SETTER_EPILOGUE(VTYPE_CSTRING); +} + +void +nsDiscriminatedUnion::SetFromAUTF8String(const nsAUTF8String& aValue) +{ + DATA_SETTER_PROLOGUE; + u.mUTF8StringValue = new nsUTF8String(aValue); + DATA_SETTER_EPILOGUE(VTYPE_UTF8STRING); +} + +nsresult +nsDiscriminatedUnion::SetFromString(const char* aValue) +{ + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + return SetFromStringWithSize(strlen(aValue), aValue); +} +nsresult +nsDiscriminatedUnion::SetFromWString(const char16_t* aValue) +{ + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + return SetFromWStringWithSize(NS_strlen(aValue), aValue); +} +void +nsDiscriminatedUnion::SetFromISupports(nsISupports* aValue) +{ + return SetFromInterface(NS_GET_IID(nsISupports), aValue); +} +void +nsDiscriminatedUnion::SetFromInterface(const nsIID& aIID, nsISupports* aValue) +{ + DATA_SETTER_PROLOGUE; + NS_IF_ADDREF(aValue); + u.iface.mInterfaceValue = aValue; + u.iface.mInterfaceID = aIID; + DATA_SETTER_EPILOGUE(VTYPE_INTERFACE_IS); +} +nsresult +nsDiscriminatedUnion::SetFromArray(uint16_t aType, const nsIID* aIID, + uint32_t aCount, void* aValue) +{ + DATA_SETTER_PROLOGUE; + if (!aValue || !aCount) { + return NS_ERROR_NULL_POINTER; + } + + nsresult rv = CloneArray(aType, aIID, aCount, aValue, + &u.array.mArrayType, + &u.array.mArrayInterfaceID, + &u.array.mArrayCount, + &u.array.mArrayValue); + if (NS_FAILED(rv)) { + return rv; + } + DATA_SETTER_EPILOGUE(VTYPE_ARRAY); + return NS_OK; +} +nsresult +nsDiscriminatedUnion::SetFromStringWithSize(uint32_t aSize, + const char* aValue) +{ + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + if (!(u.str.mStringValue = + (char*)nsMemory::Clone(aValue, (aSize + 1) * sizeof(char)))) { + return NS_ERROR_OUT_OF_MEMORY; + } + u.str.mStringLength = aSize; + DATA_SETTER_EPILOGUE(VTYPE_STRING_SIZE_IS); + return NS_OK; +} +nsresult +nsDiscriminatedUnion::SetFromWStringWithSize(uint32_t aSize, + const char16_t* aValue) +{ + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + if (!(u.wstr.mWStringValue = + (char16_t*)nsMemory::Clone(aValue, (aSize + 1) * sizeof(char16_t)))) { + return NS_ERROR_OUT_OF_MEMORY; + } + u.wstr.mWStringLength = aSize; + DATA_SETTER_EPILOGUE(VTYPE_WSTRING_SIZE_IS); + return NS_OK; +} +void +nsDiscriminatedUnion::AllocateWStringWithSize(uint32_t aSize) +{ + DATA_SETTER_PROLOGUE; + u.wstr.mWStringValue = (char16_t*)moz_xmalloc((aSize + 1) * sizeof(char16_t)); + u.wstr.mWStringValue[aSize] = '\0'; + u.wstr.mWStringLength = aSize; + DATA_SETTER_EPILOGUE(VTYPE_WSTRING_SIZE_IS); +} +void +nsDiscriminatedUnion::SetToVoid() +{ + DATA_SETTER_PROLOGUE; + DATA_SETTER_EPILOGUE(VTYPE_VOID); +} +void +nsDiscriminatedUnion::SetToEmpty() +{ + DATA_SETTER_PROLOGUE; + DATA_SETTER_EPILOGUE(VTYPE_EMPTY); +} +void +nsDiscriminatedUnion::SetToEmptyArray() +{ + DATA_SETTER_PROLOGUE; + DATA_SETTER_EPILOGUE(VTYPE_EMPTY_ARRAY); +} + +/***************************************************************************/ + +void +nsDiscriminatedUnion::Cleanup() +{ + switch (mType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ID: + break; + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + delete u.mAStringValue; + break; + case nsIDataType::VTYPE_CSTRING: + delete u.mCStringValue; + break; + case nsIDataType::VTYPE_UTF8STRING: + delete u.mUTF8StringValue; + break; + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + free((char*)u.str.mStringValue); + break; + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + free((char*)u.wstr.mWStringValue); + break; + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + NS_IF_RELEASE(u.iface.mInterfaceValue); + break; + case nsIDataType::VTYPE_ARRAY: + FreeArray(); + break; + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + break; + default: + NS_ERROR("bad type in variant!"); + break; + } + + mType = nsIDataType::VTYPE_EMPTY; +} + +void +nsDiscriminatedUnion::Traverse(nsCycleCollectionTraversalCallback& aCb) const +{ + switch (mType) { + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mData"); + aCb.NoteXPCOMChild(u.iface.mInterfaceValue); + break; + case nsIDataType::VTYPE_ARRAY: + switch (u.array.mArrayType) { + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: { + nsISupports** p = (nsISupports**)u.array.mArrayValue; + for (uint32_t i = u.array.mArrayCount; i > 0; ++p, --i) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mData[i]"); + aCb.NoteXPCOMChild(*p); + } + break; + } + default: + break; + } + break; + default: + break; + } +} + +/***************************************************************************/ +/***************************************************************************/ +// members... + +nsVariantBase::nsVariantBase() + : mWritable(true) +{ +#ifdef DEBUG + { + // Assert that the nsIDataType consts match the values #defined in + // xpt_struct.h. Bad things happen somewhere if they don't. + struct THE_TYPES + { + uint16_t a; + uint16_t b; + }; + static const THE_TYPES array[] = { + {nsIDataType::VTYPE_INT8 , TD_INT8 }, + {nsIDataType::VTYPE_INT16 , TD_INT16 }, + {nsIDataType::VTYPE_INT32 , TD_INT32 }, + {nsIDataType::VTYPE_INT64 , TD_INT64 }, + {nsIDataType::VTYPE_UINT8 , TD_UINT8 }, + {nsIDataType::VTYPE_UINT16 , TD_UINT16 }, + {nsIDataType::VTYPE_UINT32 , TD_UINT32 }, + {nsIDataType::VTYPE_UINT64 , TD_UINT64 }, + {nsIDataType::VTYPE_FLOAT , TD_FLOAT }, + {nsIDataType::VTYPE_DOUBLE , TD_DOUBLE }, + {nsIDataType::VTYPE_BOOL , TD_BOOL }, + {nsIDataType::VTYPE_CHAR , TD_CHAR }, + {nsIDataType::VTYPE_WCHAR , TD_WCHAR }, + {nsIDataType::VTYPE_VOID , TD_VOID }, + {nsIDataType::VTYPE_ID , TD_PNSIID }, + {nsIDataType::VTYPE_DOMSTRING , TD_DOMSTRING }, + {nsIDataType::VTYPE_CHAR_STR , TD_PSTRING }, + {nsIDataType::VTYPE_WCHAR_STR , TD_PWSTRING }, + {nsIDataType::VTYPE_INTERFACE , TD_INTERFACE_TYPE }, + {nsIDataType::VTYPE_INTERFACE_IS , TD_INTERFACE_IS_TYPE}, + {nsIDataType::VTYPE_ARRAY , TD_ARRAY }, + {nsIDataType::VTYPE_STRING_SIZE_IS , TD_PSTRING_SIZE_IS }, + {nsIDataType::VTYPE_WSTRING_SIZE_IS , TD_PWSTRING_SIZE_IS }, + {nsIDataType::VTYPE_UTF8STRING , TD_UTF8STRING }, + {nsIDataType::VTYPE_CSTRING , TD_CSTRING }, + {nsIDataType::VTYPE_ASTRING , TD_ASTRING } + }; + static const int length = sizeof(array) / sizeof(array[0]); + static bool inited = false; + if (!inited) { + for (int i = 0; i < length; ++i) { + NS_ASSERTION(array[i].a == array[i].b, "bad const declaration"); + } + inited = true; + } + } +#endif +} + +// For all the data getters we just forward to the static (and sharable) +// 'ConvertTo' functions. + +NS_IMETHODIMP +nsVariantBase::GetDataType(uint16_t* aDataType) +{ + *aDataType = mData.GetType(); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::GetAsInt8(uint8_t* aResult) +{ + return mData.ConvertToInt8(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsInt16(int16_t* aResult) +{ + return mData.ConvertToInt16(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsInt32(int32_t* aResult) +{ + return mData.ConvertToInt32(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsInt64(int64_t* aResult) +{ + return mData.ConvertToInt64(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint8(uint8_t* aResult) +{ + return mData.ConvertToUint8(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint16(uint16_t* aResult) +{ + return mData.ConvertToUint16(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint32(uint32_t* aResult) +{ + return mData.ConvertToUint32(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint64(uint64_t* aResult) +{ + return mData.ConvertToUint64(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsFloat(float* aResult) +{ + return mData.ConvertToFloat(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsDouble(double* aResult) +{ + return mData.ConvertToDouble(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsBool(bool* aResult) +{ + return mData.ConvertToBool(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsChar(char* aResult) +{ + return mData.ConvertToChar(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsWChar(char16_t* aResult) +{ + return mData.ConvertToWChar(aResult); +} + +NS_IMETHODIMP_(nsresult) +nsVariantBase::GetAsID(nsID* aResult) +{ + return mData.ConvertToID(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsAString(nsAString& aResult) +{ + return mData.ConvertToAString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsDOMString(nsAString& aResult) +{ + // A DOMString maps to an AString internally, so we can re-use + // ConvertToAString here. + return mData.ConvertToAString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsACString(nsACString& aResult) +{ + return mData.ConvertToACString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsAUTF8String(nsAUTF8String& aResult) +{ + return mData.ConvertToAUTF8String(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsString(char** aResult) +{ + return mData.ConvertToString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsWString(char16_t** aResult) +{ + return mData.ConvertToWString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsISupports(nsISupports** aResult) +{ + return mData.ConvertToISupports(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsJSVal(JS::MutableHandleValue) +{ + // Can only get the jsval from an XPCVariant. + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +NS_IMETHODIMP +nsVariantBase::GetAsInterface(nsIID** aIID, void** aInterface) +{ + return mData.ConvertToInterface(aIID, aInterface); +} + +NS_IMETHODIMP_(nsresult) +nsVariantBase::GetAsArray(uint16_t* aType, nsIID* aIID, + uint32_t* aCount, void** aPtr) +{ + return mData.ConvertToArray(aType, aIID, aCount, aPtr); +} + +NS_IMETHODIMP +nsVariantBase::GetAsStringWithSize(uint32_t* aSize, char** aStr) +{ + return mData.ConvertToStringWithSize(aSize, aStr); +} + +NS_IMETHODIMP +nsVariantBase::GetAsWStringWithSize(uint32_t* aSize, char16_t** aStr) +{ + return mData.ConvertToWStringWithSize(aSize, aStr); +} + +/***************************************************************************/ + +NS_IMETHODIMP +nsVariantBase::GetWritable(bool* aWritable) +{ + *aWritable = mWritable; + return NS_OK; +} +NS_IMETHODIMP +nsVariantBase::SetWritable(bool aWritable) +{ + if (!mWritable && aWritable) { + return NS_ERROR_FAILURE; + } + mWritable = aWritable; + return NS_OK; +} + +/***************************************************************************/ + +// For all the data setters we just forward to the static (and sharable) +// 'SetFrom' functions. + +NS_IMETHODIMP +nsVariantBase::SetAsInt8(uint8_t aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt8(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInt16(int16_t aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt16(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInt32(int32_t aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt32(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInt64(int64_t aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt64(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint8(uint8_t aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint8(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint16(uint16_t aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint16(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint32(uint32_t aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint32(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint64(uint64_t aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint64(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsFloat(float aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromFloat(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsDouble(double aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromDouble(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsBool(bool aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromBool(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsChar(char aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromChar(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsWChar(char16_t aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromWChar(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsID(const nsID& aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromID(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsAString(const nsAString& aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromAString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsDOMString(const nsAString& aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + + mData.SetFromDOMString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsACString(const nsACString& aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromACString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsAUTF8String(const nsAUTF8String& aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromAUTF8String(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsString(const char* aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromString(aValue); +} + +NS_IMETHODIMP +nsVariantBase::SetAsWString(const char16_t* aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromWString(aValue); +} + +NS_IMETHODIMP +nsVariantBase::SetAsISupports(nsISupports* aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromISupports(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInterface(const nsIID& aIID, void* aInterface) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInterface(aIID, (nsISupports*)aInterface); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsArray(uint16_t aType, const nsIID* aIID, + uint32_t aCount, void* aPtr) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromArray(aType, aIID, aCount, aPtr); +} + +NS_IMETHODIMP +nsVariantBase::SetAsStringWithSize(uint32_t aSize, const char* aStr) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromStringWithSize(aSize, aStr); +} + +NS_IMETHODIMP +nsVariantBase::SetAsWStringWithSize(uint32_t aSize, const char16_t* aStr) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromWStringWithSize(aSize, aStr); +} + +NS_IMETHODIMP +nsVariantBase::SetAsVoid() +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetToVoid(); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsEmpty() +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetToEmpty(); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsEmptyArray() +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetToEmptyArray(); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetFromVariant(nsIVariant* aValue) +{ + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromVariant(aValue); +} + +/* nsVariant implementation */ + +NS_IMPL_ISUPPORTS(nsVariant, nsIVariant, nsIWritableVariant) + + +/* nsVariantCC implementation */ +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsVariantCC) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIVariant) + NS_INTERFACE_MAP_ENTRY(nsIWritableVariant) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsVariantCC) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsVariantCC) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsVariantCC) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsVariantCC) + tmp->mData.Traverse(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsVariantCC) + tmp->mData.Cleanup(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END diff --git a/xpcom/ds/nsVariant.h b/xpcom/ds/nsVariant.h new file mode 100644 index 000000000..5be2d18ee --- /dev/null +++ b/xpcom/ds/nsVariant.h @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsVariant_h +#define nsVariant_h + +#include "nsIVariant.h" +#include "nsStringFwd.h" +#include "mozilla/Attributes.h" +#include "nsCycleCollectionParticipant.h" + +/** + * Map the nsAUTF8String, nsUTF8String classes to the nsACString and + * nsCString classes respectively for now. These defines need to be removed + * once Jag lands his nsUTF8String implementation. + */ +#define nsAUTF8String nsACString +#define nsUTF8String nsCString +#define PromiseFlatUTF8String PromiseFlatCString + +/** + * nsDiscriminatedUnion is a class that nsIVariant implementors can use + * to hold the underlying data. + */ + +class nsDiscriminatedUnion +{ +public: + + nsDiscriminatedUnion() : mType(nsIDataType::VTYPE_EMPTY) {} + nsDiscriminatedUnion(const nsDiscriminatedUnion&) = delete; + nsDiscriminatedUnion(nsDiscriminatedUnion&&) = delete; + + ~nsDiscriminatedUnion() { Cleanup(); } + + nsDiscriminatedUnion& operator=(const nsDiscriminatedUnion&) = delete; + nsDiscriminatedUnion& operator=(nsDiscriminatedUnion&&) = delete; + + void Cleanup(); + + uint16_t GetType() const { return mType; } + + MOZ_MUST_USE nsresult ConvertToInt8(uint8_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToInt16(int16_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToInt32(int32_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToInt64(int64_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToUint8(uint8_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToUint16(uint16_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToUint32(uint32_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToUint64(uint64_t* aResult) const; + MOZ_MUST_USE nsresult ConvertToFloat(float* aResult) const; + MOZ_MUST_USE nsresult ConvertToDouble(double* aResult) const; + MOZ_MUST_USE nsresult ConvertToBool(bool* aResult) const; + MOZ_MUST_USE nsresult ConvertToChar(char* aResult) const; + MOZ_MUST_USE nsresult ConvertToWChar(char16_t* aResult) const; + + MOZ_MUST_USE nsresult ConvertToID(nsID* aResult) const; + + MOZ_MUST_USE nsresult ConvertToAString(nsAString& aResult) const; + MOZ_MUST_USE nsresult ConvertToAUTF8String(nsAUTF8String& aResult) const; + MOZ_MUST_USE nsresult ConvertToACString(nsACString& aResult) const; + MOZ_MUST_USE nsresult ConvertToString(char** aResult) const; + MOZ_MUST_USE nsresult ConvertToWString(char16_t** aResult) const; + MOZ_MUST_USE nsresult ConvertToStringWithSize(uint32_t* aSize, char** aStr) const; + MOZ_MUST_USE nsresult ConvertToWStringWithSize(uint32_t* aSize, char16_t** aStr) const; + + MOZ_MUST_USE nsresult ConvertToISupports(nsISupports** aResult) const; + MOZ_MUST_USE nsresult ConvertToInterface(nsIID** aIID, void** aInterface) const; + MOZ_MUST_USE nsresult ConvertToArray(uint16_t* aType, nsIID* aIID, + uint32_t* aCount, void** aPtr) const; + + MOZ_MUST_USE nsresult SetFromVariant(nsIVariant* aValue); + + void SetFromInt8(uint8_t aValue); + void SetFromInt16(int16_t aValue); + void SetFromInt32(int32_t aValue); + void SetFromInt64(int64_t aValue); + void SetFromUint8(uint8_t aValue); + void SetFromUint16(uint16_t aValue); + void SetFromUint32(uint32_t aValue); + void SetFromUint64(uint64_t aValue); + void SetFromFloat(float aValue); + void SetFromDouble(double aValue); + void SetFromBool(bool aValue); + void SetFromChar(char aValue); + void SetFromWChar(char16_t aValue); + void SetFromID(const nsID& aValue); + void SetFromAString(const nsAString& aValue); + void SetFromDOMString(const nsAString& aValue); + void SetFromAUTF8String(const nsAUTF8String& aValue); + void SetFromACString(const nsACString& aValue); + MOZ_MUST_USE nsresult SetFromString(const char* aValue); + MOZ_MUST_USE nsresult SetFromWString(const char16_t* aValue); + void SetFromISupports(nsISupports* aValue); + void SetFromInterface(const nsIID& aIID, nsISupports* aValue); + MOZ_MUST_USE nsresult SetFromArray(uint16_t aType, const nsIID* aIID, + uint32_t aCount, void* aValue); + MOZ_MUST_USE nsresult SetFromStringWithSize(uint32_t aSize, + const char* aValue); + MOZ_MUST_USE nsresult SetFromWStringWithSize(uint32_t aSize, + const char16_t* aValue); + + // Like SetFromWStringWithSize, but leaves the string uninitialized. It does + // does write the null-terminator. + void AllocateWStringWithSize(uint32_t aSize); + + void SetToVoid(); + void SetToEmpty(); + void SetToEmptyArray(); + + void Traverse(nsCycleCollectionTraversalCallback& aCb) const; + +private: + MOZ_MUST_USE nsresult + ToManageableNumber(nsDiscriminatedUnion* aOutData) const; + void FreeArray(); + MOZ_MUST_USE bool String2ID(nsID* aPid) const; + MOZ_MUST_USE nsresult ToString(nsACString& aOutString) const; + +public: + union + { + int8_t mInt8Value; + int16_t mInt16Value; + int32_t mInt32Value; + int64_t mInt64Value; + uint8_t mUint8Value; + uint16_t mUint16Value; + uint32_t mUint32Value; + uint64_t mUint64Value; + float mFloatValue; + double mDoubleValue; + bool mBoolValue; + char mCharValue; + char16_t mWCharValue; + nsIID mIDValue; + nsAString* mAStringValue; + nsAUTF8String* mUTF8StringValue; + nsACString* mCStringValue; + struct + { + // This is an owning reference that cannot be an nsCOMPtr because + // nsDiscriminatedUnion needs to be POD. AddRef/Release are manually + // called on this. + nsISupports* MOZ_OWNING_REF mInterfaceValue; + nsIID mInterfaceID; + } iface; + struct + { + nsIID mArrayInterfaceID; + void* mArrayValue; + uint32_t mArrayCount; + uint16_t mArrayType; + } array; + struct + { + char* mStringValue; + uint32_t mStringLength; + } str; + struct + { + char16_t* mWStringValue; + uint32_t mWStringLength; + } wstr; + } u; + uint16_t mType; +}; + +/** + * nsVariant implements the generic variant support. The xpcom module registers + * a factory (see NS_VARIANT_CONTRACTID in nsIVariant.idl) that will create + * these objects. They are created 'empty' and 'writable'. + * + * nsIVariant users won't usually need to see this class. + */ +class nsVariantBase : public nsIWritableVariant +{ +public: + NS_DECL_NSIVARIANT + NS_DECL_NSIWRITABLEVARIANT + + nsVariantBase(); + +protected: + ~nsVariantBase() {}; + + nsDiscriminatedUnion mData; + bool mWritable; +}; + +class nsVariant final : public nsVariantBase +{ +public: + NS_DECL_ISUPPORTS + + nsVariant() {}; + +private: + ~nsVariant() {}; +}; + +class nsVariantCC final : public nsVariantBase +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsVariantCC) + + nsVariantCC() {}; + +private: + ~nsVariantCC() {}; +}; + +/** + * Users of nsIVariant should be using the contractID and not this CID. + * - see NS_VARIANT_CONTRACTID in nsIVariant.idl. + */ + +#define NS_VARIANT_CID \ +{ /* 0D6EA1D0-879C-11d5-90EF-0010A4E73D9A */ \ + 0xd6ea1d0, \ + 0x879c, \ + 0x11d5, \ + {0x90, 0xef, 0x0, 0x10, 0xa4, 0xe7, 0x3d, 0x9a}} + +#endif // nsVariant_h diff --git a/xpcom/ds/nsWhitespaceTokenizer.h b/xpcom/ds/nsWhitespaceTokenizer.h new file mode 100644 index 000000000..bfe32f8dc --- /dev/null +++ b/xpcom/ds/nsWhitespaceTokenizer.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 __nsWhitespaceTokenizer_h +#define __nsWhitespaceTokenizer_h + +#include "mozilla/RangedPtr.h" +#include "nsDependentSubstring.h" +#include "nsCRT.h" + +template<typename DependentSubstringType, bool IsWhitespace(char16_t)> +class nsTWhitespaceTokenizer +{ + typedef typename DependentSubstringType::char_type CharType; + typedef typename DependentSubstringType::substring_type SubstringType; + +public: + explicit nsTWhitespaceTokenizer(const SubstringType& aSource) + : mIter(aSource.Data(), aSource.Length()) + , mEnd(aSource.Data() + aSource.Length(), aSource.Data(), + aSource.Length()) + , mWhitespaceBeforeFirstToken(false) + , mWhitespaceAfterCurrentToken(false) + { + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceBeforeFirstToken = true; + ++mIter; + } + } + + /** + * Checks if any more tokens are available. + */ + bool hasMoreTokens() const + { + return mIter < mEnd; + } + + /* + * Returns true if there is whitespace prior to the first token. + */ + bool whitespaceBeforeFirstToken() const + { + return mWhitespaceBeforeFirstToken; + } + + /* + * Returns true if there is any whitespace after the current token. + * This is always true unless we're reading the last token. + */ + bool whitespaceAfterCurrentToken() const + { + return mWhitespaceAfterCurrentToken; + } + + /** + * Returns the next token. + */ + const DependentSubstringType nextToken() + { + const mozilla::RangedPtr<const CharType> tokenStart = mIter; + while (mIter < mEnd && !IsWhitespace(*mIter)) { + ++mIter; + } + const mozilla::RangedPtr<const CharType> tokenEnd = mIter; + mWhitespaceAfterCurrentToken = false; + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceAfterCurrentToken = true; + ++mIter; + } + return Substring(tokenStart.get(), tokenEnd.get()); + } + +private: + mozilla::RangedPtr<const CharType> mIter; + const mozilla::RangedPtr<const CharType> mEnd; + bool mWhitespaceBeforeFirstToken; + bool mWhitespaceAfterCurrentToken; +}; + +template<bool IsWhitespace(char16_t) = NS_IsAsciiWhitespace> +class nsWhitespaceTokenizerTemplate + : public nsTWhitespaceTokenizer<nsDependentSubstring, IsWhitespace> +{ +public: + explicit nsWhitespaceTokenizerTemplate(const nsSubstring& aSource) + : nsTWhitespaceTokenizer<nsDependentSubstring, IsWhitespace>(aSource) + { + } +}; + +typedef nsWhitespaceTokenizerTemplate<> nsWhitespaceTokenizer; + +template<bool IsWhitespace(char16_t) = NS_IsAsciiWhitespace> +class nsCWhitespaceTokenizerTemplate + : public nsTWhitespaceTokenizer<nsDependentCSubstring, IsWhitespace> +{ +public: + explicit nsCWhitespaceTokenizerTemplate(const nsCSubstring& aSource) + : nsTWhitespaceTokenizer<nsDependentCSubstring, IsWhitespace>(aSource) + { + } +}; + +typedef nsCWhitespaceTokenizerTemplate<> nsCWhitespaceTokenizer; + +#endif /* __nsWhitespaceTokenizer_h */ diff --git a/xpcom/ds/nsWindowsRegKey.cpp b/xpcom/ds/nsWindowsRegKey.cpp new file mode 100644 index 000000000..55571d8f9 --- /dev/null +++ b/xpcom/ds/nsWindowsRegKey.cpp @@ -0,0 +1,579 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 <windows.h> +#include <shlwapi.h> +#include <stdlib.h> +#include "nsWindowsRegKey.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" +#include "nsAutoPtr.h" + +//----------------------------------------------------------------------------- + +// According to MSDN, the following limits apply (in characters excluding room +// for terminating null character): +#define MAX_KEY_NAME_LEN 255 +#define MAX_VALUE_NAME_LEN 16383 + +class nsWindowsRegKey final : public nsIWindowsRegKey +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWINDOWSREGKEY + + nsWindowsRegKey() + : mKey(nullptr) + , mWatchEvent(nullptr) + , mWatchRecursive(FALSE) + { + } + +private: + ~nsWindowsRegKey() + { + Close(); + } + + HKEY mKey; + HANDLE mWatchEvent; + BOOL mWatchRecursive; +}; + +NS_IMPL_ISUPPORTS(nsWindowsRegKey, nsIWindowsRegKey) + +NS_IMETHODIMP +nsWindowsRegKey::GetKey(HKEY* aKey) +{ + *aKey = mKey; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::SetKey(HKEY aKey) +{ + // We do not close the older aKey! + StopWatching(); + + mKey = aKey; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::Close() +{ + StopWatching(); + + if (mKey) { + RegCloseKey(mKey); + mKey = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::Open(uint32_t aRootKey, const nsAString& aPath, + uint32_t aMode) +{ + Close(); + + LONG rv = RegOpenKeyExW((HKEY)(intptr_t)aRootKey, + PromiseFlatString(aPath).get(), 0, (REGSAM)aMode, + &mKey); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::Create(uint32_t aRootKey, const nsAString& aPath, + uint32_t aMode) +{ + Close(); + + DWORD disposition; + LONG rv = RegCreateKeyExW((HKEY)(intptr_t)aRootKey, + PromiseFlatString(aPath).get(), 0, nullptr, + REG_OPTION_NON_VOLATILE, (REGSAM)aMode, nullptr, + &mKey, &disposition); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::OpenChild(const nsAString& aPath, uint32_t aMode, + nsIWindowsRegKey** aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr<nsIWindowsRegKey> child = new nsWindowsRegKey(); + + nsresult rv = child->Open((uintptr_t)mKey, aPath, aMode); + if (NS_FAILED(rv)) { + return rv; + } + + child.swap(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::CreateChild(const nsAString& aPath, uint32_t aMode, + nsIWindowsRegKey** aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr<nsIWindowsRegKey> child = new nsWindowsRegKey(); + + nsresult rv = child->Create((uintptr_t)mKey, aPath, aMode); + if (NS_FAILED(rv)) { + return rv; + } + + child.swap(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetChildCount(uint32_t* aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD numSubKeys; + LONG rv = RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, &numSubKeys, + nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + *aResult = numSubKeys; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetChildName(uint32_t aIndex, nsAString& aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + FILETIME lastWritten; + + wchar_t nameBuf[MAX_KEY_NAME_LEN + 1]; + DWORD nameLen = sizeof(nameBuf) / sizeof(nameBuf[0]); + + LONG rv = RegEnumKeyExW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr, + nullptr, &lastWritten); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_NOT_AVAILABLE; // XXX what's the best error code here? + } + + aResult.Assign(nameBuf, nameLen); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::HasChild(const nsAString& aName, bool* aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + // Check for the existence of a child key by opening the key with minimal + // rights. Perhaps there is a more efficient way to do this? + + HKEY key; + LONG rv = RegOpenKeyExW(mKey, PromiseFlatString(aName).get(), 0, + STANDARD_RIGHTS_READ, &key); + + if ((*aResult = (rv == ERROR_SUCCESS && key))) { + RegCloseKey(key); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetValueCount(uint32_t* aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD numValues; + LONG rv = RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, &numValues, nullptr, nullptr, + nullptr, nullptr); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + *aResult = numValues; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetValueName(uint32_t aIndex, nsAString& aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + wchar_t nameBuf[MAX_VALUE_NAME_LEN]; + DWORD nameLen = sizeof(nameBuf) / sizeof(nameBuf[0]); + + LONG rv = RegEnumValueW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr, + nullptr, nullptr); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_NOT_AVAILABLE; // XXX what's the best error code here? + } + + aResult.Assign(nameBuf, nameLen); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::HasValue(const nsAString& aName, bool* aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr, + nullptr, nullptr); + + *aResult = (rv == ERROR_SUCCESS); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::RemoveChild(const nsAString& aName) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegDeleteKeyW(mKey, PromiseFlatString(aName).get()); + + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::RemoveValue(const nsAString& aName) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegDeleteValueW(mKey, PromiseFlatString(aName).get()); + + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetValueType(const nsAString& aName, uint32_t* aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, + (LPDWORD)aResult, nullptr, nullptr); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadStringValue(const nsAString& aName, nsAString& aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD type, size; + + const nsString& flatName = PromiseFlatString(aName); + + LONG rv = RegQueryValueExW(mKey, flatName.get(), 0, &type, nullptr, &size); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + // This must be a string type in order to fetch the value as a string. + // We're being a bit forgiving here by allowing types other than REG_SZ. + if (type != REG_SZ && type != REG_EXPAND_SZ && type != REG_MULTI_SZ) { + return NS_ERROR_FAILURE; + } + + // The buffer size must be a multiple of 2. + if (size % 2 != 0) { + return NS_ERROR_UNEXPECTED; + } + + if (size == 0) { + aResult.Truncate(); + return NS_OK; + } + + // |size| may or may not include the terminating null character. + DWORD resultLen = size / 2; + + if (!aResult.SetLength(resultLen, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsAString::iterator begin; + aResult.BeginWriting(begin); + + rv = RegQueryValueExW(mKey, flatName.get(), 0, &type, (LPBYTE)begin.get(), + &size); + + if (!aResult.CharAt(resultLen - 1)) { + // The string passed to us had a null terminator in the final position. + aResult.Truncate(resultLen - 1); + } + + // Expand the environment variables if needed + if (type == REG_EXPAND_SZ) { + const nsString& flatSource = PromiseFlatString(aResult); + resultLen = ExpandEnvironmentStringsW(flatSource.get(), nullptr, 0); + if (resultLen > 1) { + nsAutoString expandedResult; + // |resultLen| includes the terminating null character + --resultLen; + if (!expandedResult.SetLength(resultLen, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsAString::iterator begin; + expandedResult.BeginWriting(begin); + + resultLen = ExpandEnvironmentStringsW(flatSource.get(), + wwc(begin.get()), + resultLen + 1); + if (resultLen <= 0) { + rv = ERROR_UNKNOWN_FEATURE; + aResult.Truncate(); + } else { + rv = ERROR_SUCCESS; + aResult = expandedResult; + } + } else if (resultLen == 1) { + // It apparently expands to nothing (just a null terminator). + aResult.Truncate(); + } + } + + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadIntValue(const nsAString& aName, uint32_t* aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD size = sizeof(*aResult); + LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr, + (LPBYTE)aResult, &size); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadInt64Value(const nsAString& aName, uint64_t* aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD size = sizeof(*aResult); + LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr, + (LPBYTE)aResult, &size); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadBinaryValue(const nsAString& aName, nsACString& aResult) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD size; + LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, + nullptr, nullptr, &size); + + if (rv != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + if (!size) { + aResult.Truncate(); + return NS_OK; + } + + if (!aResult.SetLength(size, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsACString::iterator begin; + aResult.BeginWriting(begin); + + rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr, + (LPBYTE)begin.get(), &size); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteStringValue(const nsAString& aName, + const nsAString& aValue) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + // Need to indicate complete size of buffer including null terminator. + const nsString& flatValue = PromiseFlatString(aValue); + + LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_SZ, + (const BYTE*)flatValue.get(), + (flatValue.Length() + 1) * sizeof(char16_t)); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteIntValue(const nsAString& aName, uint32_t aValue) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_DWORD, + (const BYTE*)&aValue, sizeof(aValue)); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteInt64Value(const nsAString& aName, uint64_t aValue) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_QWORD, + (const BYTE*)&aValue, sizeof(aValue)); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteBinaryValue(const nsAString& aName, + const nsACString& aValue) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + const nsCString& flatValue = PromiseFlatCString(aValue); + LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_BINARY, + (const BYTE*)flatValue.get(), flatValue.Length()); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::StartWatching(bool aRecurse) +{ + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (mWatchEvent) { + return NS_OK; + } + + mWatchEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!mWatchEvent) { + return NS_ERROR_OUT_OF_MEMORY; + } + + DWORD filter = REG_NOTIFY_CHANGE_NAME | + REG_NOTIFY_CHANGE_ATTRIBUTES | + REG_NOTIFY_CHANGE_LAST_SET | + REG_NOTIFY_CHANGE_SECURITY; + + LONG rv = RegNotifyChangeKeyValue(mKey, aRecurse, filter, mWatchEvent, TRUE); + if (rv != ERROR_SUCCESS) { + StopWatching(); + // On older versions of Windows, this call is not implemented, so simply + // return NS_OK in those cases and pretend that the watching is happening. + return (rv == ERROR_CALL_NOT_IMPLEMENTED) ? NS_OK : NS_ERROR_FAILURE; + } + + mWatchRecursive = aRecurse; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::StopWatching() +{ + if (mWatchEvent) { + CloseHandle(mWatchEvent); + mWatchEvent = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::HasChanged(bool* aResult) +{ + if (mWatchEvent && WaitForSingleObject(mWatchEvent, 0) == WAIT_OBJECT_0) { + // An event only gets signaled once, then it's done, so we have to set up + // another event to watch. + StopWatching(); + StartWatching(mWatchRecursive); + *aResult = true; + } else { + *aResult = false; + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::IsWatching(bool* aResult) +{ + *aResult = (mWatchEvent != nullptr); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +void +NS_NewWindowsRegKey(nsIWindowsRegKey** aResult) +{ + RefPtr<nsWindowsRegKey> key = new nsWindowsRegKey(); + key.forget(aResult); +} + +//----------------------------------------------------------------------------- + +nsresult +nsWindowsRegKeyConstructor(nsISupports* aDelegate, const nsIID& aIID, + void** aResult) +{ + if (aDelegate) { + return NS_ERROR_NO_AGGREGATION; + } + + nsCOMPtr<nsIWindowsRegKey> key; + NS_NewWindowsRegKey(getter_AddRefs(key)); + return key->QueryInterface(aIID, aResult); +} diff --git a/xpcom/ds/nsWindowsRegKey.h b/xpcom/ds/nsWindowsRegKey.h new file mode 100644 index 000000000..d7930579a --- /dev/null +++ b/xpcom/ds/nsWindowsRegKey.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 nsWindowsRegKey_h__ +#define nsWindowsRegKey_h__ + +//----------------------------------------------------------------------------- + +#include "nsIWindowsRegKey.h" + +/** + * This ContractID may be used to instantiate a windows registry key object + * via the XPCOM component manager. + */ +#define NS_WINDOWSREGKEY_CONTRACTID "@mozilla.org/windows-registry-key;1" + +/** + * This function may be used to instantiate a windows registry key object prior + * to XPCOM being initialized. + */ +extern "C" void NS_NewWindowsRegKey(nsIWindowsRegKey** aResult); + +//----------------------------------------------------------------------------- + +#ifdef IMPL_LIBXUL + +// a53bc624-d577-4839-b8ec-bb5040a52ff4 +#define NS_WINDOWSREGKEY_CID \ + { 0xa53bc624, 0xd577, 0x4839, \ + { 0xb8, 0xec, 0xbb, 0x50, 0x40, 0xa5, 0x2f, 0xf4 } } + +extern MOZ_MUST_USE nsresult nsWindowsRegKeyConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aResult); + +#endif // IMPL_LIBXUL + +//----------------------------------------------------------------------------- + +#endif // nsWindowsRegKey_h__ |