/* -*- 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/. */ /* rendering object for list-item bullets */ #include "nsBulletFrame.h" #include "mozilla/MathAlgorithms.h" #include "nsCOMPtr.h" #include "nsGkAtoms.h" #include "nsGenericHTMLElement.h" #include "nsAttrValueInlines.h" #include "nsPresContext.h" #include "nsIPresShell.h" #include "nsIDocument.h" #include "nsRenderingContext.h" #include "prprf.h" #include "nsDisplayList.h" #include "nsCounterManager.h" #include "imgIContainer.h" #include "imgRequestProxy.h" #include "nsIURI.h" #include #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" #endif using namespace mozilla; NS_DECLARE_FRAME_PROPERTY(FontSizeInflationProperty, nullptr) NS_IMPL_FRAMEARENA_HELPERS(nsBulletFrame) #ifdef DEBUG NS_QUERYFRAME_HEAD(nsBulletFrame) NS_QUERYFRAME_ENTRY(nsBulletFrame) NS_QUERYFRAME_TAIL_INHERITING(nsFrame) #endif nsBulletFrame::~nsBulletFrame() { } void nsBulletFrame::DestroyFrom(nsIFrame* aDestructRoot) { // Stop image loading first if (mImageRequest) { // Deregister our image request from the refresh driver nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest, &mRequestRegistered); mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE); mImageRequest = nullptr; } if (mListener) { mListener->SetFrame(nullptr); } // Let base class do the rest nsFrame::DestroyFrom(aDestructRoot); } #ifdef DEBUG_FRAME_DUMP NS_IMETHODIMP nsBulletFrame::GetFrameName(nsAString& aResult) const { return MakeFrameName(NS_LITERAL_STRING("Bullet"), aResult); } #endif nsIAtom* nsBulletFrame::GetType() const { return nsGkAtoms::bulletFrame; } bool nsBulletFrame::IsEmpty() { return IsSelfEmpty(); } bool nsBulletFrame::IsSelfEmpty() { return StyleList()->mListStyleType == NS_STYLE_LIST_STYLE_NONE; } /* virtual */ void nsBulletFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) { nsFrame::DidSetStyleContext(aOldStyleContext); imgRequestProxy *newRequest = StyleList()->GetListStyleImage(); if (newRequest) { if (!mListener) { mListener = new nsBulletListener(); mListener->SetFrame(this); } bool needNewRequest = true; if (mImageRequest) { // Reload the image, maybe... nsCOMPtr oldURI; mImageRequest->GetURI(getter_AddRefs(oldURI)); nsCOMPtr newURI; newRequest->GetURI(getter_AddRefs(newURI)); if (oldURI && newURI) { bool same; newURI->Equals(oldURI, &same); if (same) { needNewRequest = false; } } } if (needNewRequest) { nsRefPtr oldRequest = mImageRequest; newRequest->Clone(mListener, getter_AddRefs(mImageRequest)); // Deregister the old request. We wait until after Clone is done in case // the old request and the new request are the same underlying image // accessed via different URLs. if (oldRequest) { nsLayoutUtils::DeregisterImageRequest(PresContext(), oldRequest, &mRequestRegistered); oldRequest->CancelAndForgetObserver(NS_ERROR_FAILURE); oldRequest = nullptr; } // Register the new request. if (mImageRequest) { nsLayoutUtils::RegisterImageRequestIfAnimated(PresContext(), mImageRequest, &mRequestRegistered); } } } else { // No image request on the new style context if (mImageRequest) { nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest, &mRequestRegistered); mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE); mImageRequest = nullptr; } } #ifdef ACCESSIBILITY // Update the list bullet accessible. If old style list isn't available then // no need to update the accessible tree because it's not created yet. if (aOldStyleContext) { nsAccessibilityService* accService = nsIPresShell::AccService(); if (accService) { const nsStyleList* oldStyleList = aOldStyleContext->PeekStyleList(); if (oldStyleList) { bool hadBullet = oldStyleList->GetListStyleImage() || oldStyleList->mListStyleType != NS_STYLE_LIST_STYLE_NONE; const nsStyleList* newStyleList = StyleList(); bool hasBullet = newStyleList->GetListStyleImage() || newStyleList->mListStyleType != NS_STYLE_LIST_STYLE_NONE; if (hadBullet != hasBullet) { accService->UpdateListBullet(PresContext()->GetPresShell(), mContent, hasBullet); } } } } #endif } class nsDisplayBulletGeometry : public nsDisplayItemGenericGeometry { public: nsDisplayBulletGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) : nsDisplayItemGenericGeometry(aItem, aBuilder) { nsBulletFrame* f = static_cast(aItem->Frame()); mOrdinal = f->GetOrdinal(); } int32_t mOrdinal; }; class nsDisplayBullet : public nsDisplayItem { public: nsDisplayBullet(nsDisplayListBuilder* aBuilder, nsBulletFrame* aFrame) : nsDisplayItem(aBuilder, aFrame) { MOZ_COUNT_CTOR(nsDisplayBullet); } #ifdef NS_BUILD_REFCNT_LOGGING virtual ~nsDisplayBullet() { MOZ_COUNT_DTOR(nsDisplayBullet); } #endif virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) { *aSnap = false; return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); } virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, HitTestState* aState, nsTArray *aOutFrames) { aOutFrames->AppendElement(mFrame); } virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx); NS_DISPLAY_DECL_NAME("Bullet", TYPE_BULLET) virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) { bool snap; return GetBounds(aBuilder, &snap); } virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) { return new nsDisplayBulletGeometry(this, aBuilder); } virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, nsRegion *aInvalidRegion) { const nsDisplayBulletGeometry* geometry = static_cast(aGeometry); nsBulletFrame* f = static_cast(mFrame); if (f->GetOrdinal() != geometry->mOrdinal) { bool snap; aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &snap)); return; } nsCOMPtr image = f->GetImage(); if (aBuilder->ShouldSyncDecodeImages() && image && !image->IsDecoded()) { // If we are going to do a sync decode and we are not decoded then we are // going to be drawing something different from what is currently there, // so we add our bounds to the invalid region. bool snap; aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); } return nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); } }; void nsDisplayBullet::Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) { uint32_t flags = imgIContainer::FLAG_NONE; if (aBuilder->ShouldSyncDecodeImages()) { flags |= imgIContainer::FLAG_SYNC_DECODE; } static_cast(mFrame)-> PaintBullet(*aCtx, ToReferenceFrame(), mVisibleRect, flags); } void nsBulletFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) { if (!IsVisibleForPainting(aBuilder)) return; DO_GLOBAL_REFLOW_COUNT_DSP("nsBulletFrame"); aLists.Content()->AppendNewToTop( new (aBuilder) nsDisplayBullet(aBuilder, this)); } void nsBulletFrame::PaintBullet(nsRenderingContext& aRenderingContext, nsPoint aPt, const nsRect& aDirtyRect, uint32_t aFlags) { const nsStyleList* myList = StyleList(); uint8_t listStyleType = myList->mListStyleType; if (myList->GetListStyleImage() && mImageRequest) { uint32_t status; mImageRequest->GetImageStatus(&status); if (status & imgIRequest::STATUS_LOAD_COMPLETE && !(status & imgIRequest::STATUS_ERROR)) { nsCOMPtr imageCon; mImageRequest->GetImage(getter_AddRefs(imageCon)); if (imageCon) { nsRect dest(mPadding.left, mPadding.top, mRect.width - (mPadding.left + mPadding.right), mRect.height - (mPadding.top + mPadding.bottom)); nsLayoutUtils::DrawSingleImage(&aRenderingContext, imageCon, nsLayoutUtils::GetGraphicsFilterForFrame(this), dest + aPt, aDirtyRect, nullptr, aFlags); return; } } } nsRefPtr fm; aRenderingContext.SetColor(nsLayoutUtils::GetColor(this, eCSSProperty_color)); mTextIsRTL = false; nsAutoString text; switch (listStyleType) { case NS_STYLE_LIST_STYLE_NONE: break; default: case NS_STYLE_LIST_STYLE_DISC: aRenderingContext.FillEllipse(mPadding.left + aPt.x, mPadding.top + aPt.y, mRect.width - (mPadding.left + mPadding.right), mRect.height - (mPadding.top + mPadding.bottom)); break; case NS_STYLE_LIST_STYLE_CIRCLE: aRenderingContext.DrawEllipse(mPadding.left + aPt.x, mPadding.top + aPt.y, mRect.width - (mPadding.left + mPadding.right), mRect.height - (mPadding.top + mPadding.bottom)); break; case NS_STYLE_LIST_STYLE_SQUARE: { nsRect rect(aPt, mRect.Size()); rect.Deflate(mPadding); // Snap the height and the width of the rectangle to device pixels, // and then center the result within the original rectangle, so that // all square bullets at the same font size have the same visual // size (bug 376690). // FIXME: We should really only do this if we're not transformed // (like gfxContext::UserToDevicePixelSnapped does). nsPresContext *pc = PresContext(); nsRect snapRect(rect.x, rect.y, pc->RoundAppUnitsToNearestDevPixels(rect.width), pc->RoundAppUnitsToNearestDevPixels(rect.height)); snapRect.MoveBy((rect.width - snapRect.width) / 2, (rect.height - snapRect.height) / 2); aRenderingContext.FillRect(snapRect.x, snapRect.y, snapRect.width, snapRect.height); } break; case NS_STYLE_LIST_STYLE_DECIMAL: case NS_STYLE_LIST_STYLE_DECIMAL_LEADING_ZERO: case NS_STYLE_LIST_STYLE_CJK_DECIMAL: case NS_STYLE_LIST_STYLE_LOWER_ROMAN: case NS_STYLE_LIST_STYLE_UPPER_ROMAN: case NS_STYLE_LIST_STYLE_LOWER_ALPHA: case NS_STYLE_LIST_STYLE_UPPER_ALPHA: case NS_STYLE_LIST_STYLE_LOWER_GREEK: case NS_STYLE_LIST_STYLE_HEBREW: case NS_STYLE_LIST_STYLE_ARMENIAN: case NS_STYLE_LIST_STYLE_GEORGIAN: case NS_STYLE_LIST_STYLE_CJK_IDEOGRAPHIC: case NS_STYLE_LIST_STYLE_HIRAGANA: case NS_STYLE_LIST_STYLE_KATAKANA: case NS_STYLE_LIST_STYLE_HIRAGANA_IROHA: case NS_STYLE_LIST_STYLE_KATAKANA_IROHA: case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL: case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL: case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL: case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL: case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL: case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_CJK_HEAVENLY_STEM: case NS_STYLE_LIST_STYLE_MOZ_CJK_EARTHLY_BRANCH: case NS_STYLE_LIST_STYLE_MOZ_ARABIC_INDIC: case NS_STYLE_LIST_STYLE_MOZ_PERSIAN: case NS_STYLE_LIST_STYLE_MOZ_URDU: case NS_STYLE_LIST_STYLE_MOZ_DEVANAGARI: case NS_STYLE_LIST_STYLE_MOZ_GURMUKHI: case NS_STYLE_LIST_STYLE_MOZ_GUJARATI: case NS_STYLE_LIST_STYLE_MOZ_ORIYA: case NS_STYLE_LIST_STYLE_MOZ_KANNADA: case NS_STYLE_LIST_STYLE_MOZ_MALAYALAM: case NS_STYLE_LIST_STYLE_MOZ_BENGALI: case NS_STYLE_LIST_STYLE_MOZ_TAMIL: case NS_STYLE_LIST_STYLE_MOZ_TELUGU: case NS_STYLE_LIST_STYLE_MOZ_THAI: case NS_STYLE_LIST_STYLE_MOZ_LAO: case NS_STYLE_LIST_STYLE_MOZ_MYANMAR: case NS_STYLE_LIST_STYLE_MOZ_KHMER: case NS_STYLE_LIST_STYLE_MOZ_HANGUL: case NS_STYLE_LIST_STYLE_MOZ_HANGUL_CONSONANT: case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME: case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_NUMERIC: case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_AM: case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ER: case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ET: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm), GetFontSizeInflation()); GetListItemText(*myList, text); aRenderingContext.SetFont(fm); nscoord ascent = fm->MaxAscent(); aRenderingContext.SetTextRunRTL(mTextIsRTL); aRenderingContext.DrawString(text, mPadding.left + aPt.x, mPadding.top + aPt.y + ascent); break; } } int32_t nsBulletFrame::SetListItemOrdinal(int32_t aNextOrdinal, bool* aChanged, int32_t aIncrement) { MOZ_ASSERT(aIncrement == 1 || aIncrement == -1, "We shouldn't have weird increments here"); // Assume that the ordinal comes from the caller int32_t oldOrdinal = mOrdinal; mOrdinal = aNextOrdinal; // Try to get value directly from the list-item, if it specifies a // value attribute. Note: we do this with our parent's content // because our parent is the list-item. nsIContent* parentContent = mParent->GetContent(); if (parentContent) { nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(parentContent); if (hc) { const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::value); if (attr && attr->Type() == nsAttrValue::eInteger) { // Use ordinal specified by the value attribute mOrdinal = attr->GetIntegerValue(); } } } *aChanged = oldOrdinal != mOrdinal; return nsCounterManager::IncrementCounter(mOrdinal, aIncrement); } // XXX change roman/alpha to use unsigned math so that maxint and // maxnegint will work /** * For all functions below, a return value of true means that we * could represent mOrder in the desired numbering system. false * means we had to fall back to decimal */ static bool DecimalToText(int32_t ordinal, nsString& result) { char cbuf[40]; PR_snprintf(cbuf, sizeof(cbuf), "%ld", ordinal); result.AppendASCII(cbuf); return true; } static bool DecimalLeadingZeroToText(int32_t ordinal, nsString& result) { char cbuf[40]; PR_snprintf(cbuf, sizeof(cbuf), "%02ld", ordinal); result.AppendASCII(cbuf); return true; } static bool OtherDecimalToText(int32_t ordinal, char16_t zeroChar, nsString& result) { char16_t diff = zeroChar - char16_t('0'); // We're going to be appending to whatever is in "result" already, so make // sure to only munge the new bits. Note that we can't just grab the pointer // to the new stuff here, since appending to the string can realloc. size_t offset = result.Length(); DecimalToText(ordinal, result); char16_t* p = result.BeginWriting() + offset; if (ordinal < 0) { // skip the leading '-' ++p; } for(; '\0' != *p ; p++) *p += diff; return true; } static bool TamilToText(int32_t ordinal, nsString& result) { if (ordinal < 1 || ordinal > 9999) { // Can't do those in this system. return false; } char16_t diff = 0x0BE6 - char16_t('0'); // We're going to be appending to whatever is in "result" already, so make // sure to only munge the new bits. Note that we can't just grab the pointer // to the new stuff here, since appending to the string can realloc. size_t offset = result.Length(); DecimalToText(ordinal, result); char16_t* p = result.BeginWriting() + offset; for(; '\0' != *p ; p++) if(*p != char16_t('0')) *p += diff; return true; } static const char gLowerRomanCharsA[] = "ixcm"; static const char gUpperRomanCharsA[] = "IXCM"; static const char gLowerRomanCharsB[] = "vld"; static const char gUpperRomanCharsB[] = "VLD"; static bool RomanToText(int32_t ordinal, nsString& result, const char* achars, const char* bchars) { if (ordinal < 1 || ordinal > 3999) { return false; } nsAutoString addOn, decStr; decStr.AppendInt(ordinal, 10); int len = decStr.Length(); const char16_t* dp = decStr.get(); const char16_t* end = dp + len; int romanPos = len; int n; for (; dp < end; dp++) { romanPos--; addOn.SetLength(0); switch(*dp) { case '3': addOn.Append(char16_t(achars[romanPos])); // FALLTHROUGH case '2': addOn.Append(char16_t(achars[romanPos])); // FALLTHROUGH case '1': addOn.Append(char16_t(achars[romanPos])); break; case '4': addOn.Append(char16_t(achars[romanPos])); // FALLTHROUGH case '5': case '6': case '7': case '8': addOn.Append(char16_t(bchars[romanPos])); for(n=0;'5'+n<*dp;n++) { addOn.Append(char16_t(achars[romanPos])); } break; case '9': addOn.Append(char16_t(achars[romanPos])); addOn.Append(char16_t(achars[romanPos+1])); break; default: break; } result.Append(addOn); } return true; } #define ALPHA_SIZE 26 static const char16_t gLowerAlphaChars[ALPHA_SIZE] = { 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, // A B C D E 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, // F G H I J 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, // K L M N O 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, // P Q R S T 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, // U V W X Y 0x007A // Z }; static const char16_t gUpperAlphaChars[ALPHA_SIZE] = { 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, // A B C D E 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, // F G H I J 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, // K L M N O 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, // P Q R S T 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, // U V W X Y 0x005A // Z }; #define KATAKANA_CHARS_SIZE 48 // Page 94 Writing Systems of The World // after modification by momoi static const char16_t gKatakanaChars[KATAKANA_CHARS_SIZE] = { 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, // a i u e o 0x30AB, 0x30AD, 0x30AF, 0x30B1, 0x30B3, // ka ki ku ke ko 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD, // sa shi su se so 0x30BF, 0x30C1, 0x30C4, 0x30C6, 0x30C8, // ta chi tsu te to 0x30CA, 0x30CB, 0x30CC, 0x30CD, 0x30CE, // na ni nu ne no 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, // ha hi hu he ho 0x30DE, 0x30DF, 0x30E0, 0x30E1, 0x30E2, // ma mi mu me mo 0x30E4, 0x30E6, 0x30E8, // ya yu yo 0x30E9, 0x30EA, 0x30EB, 0x30EC, 0x30ED, // ra ri ru re ro 0x30EF, 0x30F0, 0x30F1, 0x30F2, // wa (w)i (w)e (w)o 0x30F3 // n }; #define HIRAGANA_CHARS_SIZE 48 static const char16_t gHiraganaChars[HIRAGANA_CHARS_SIZE] = { 0x3042, 0x3044, 0x3046, 0x3048, 0x304A, // a i u e o 0x304B, 0x304D, 0x304F, 0x3051, 0x3053, // ka ki ku ke ko 0x3055, 0x3057, 0x3059, 0x305B, 0x305D, // sa shi su se so 0x305F, 0x3061, 0x3064, 0x3066, 0x3068, // ta chi tsu te to 0x306A, 0x306B, 0x306C, 0x306D, 0x306E, // na ni nu ne no 0x306F, 0x3072, 0x3075, 0x3078, 0x307B, // ha hi hu he ho 0x307E, 0x307F, 0x3080, 0x3081, 0x3082, // ma mi mu me mo 0x3084, 0x3086, 0x3088, // ya yu yo 0x3089, 0x308A, 0x308B, 0x308C, 0x308D, // ra ri ru re ro 0x308F, 0x3090, 0x3091, 0x3092, // wa (w)i (w)e (w)o 0x3093 // n }; #define HIRAGANA_IROHA_CHARS_SIZE 47 // Page 94 Writing Systems of The World static const char16_t gHiraganaIrohaChars[HIRAGANA_IROHA_CHARS_SIZE] = { 0x3044, 0x308D, 0x306F, 0x306B, 0x307B, // i ro ha ni ho 0x3078, 0x3068, 0x3061, 0x308A, 0x306C, // he to chi ri nu 0x308B, 0x3092, 0x308F, 0x304B, 0x3088, // ru (w)o wa ka yo 0x305F, 0x308C, 0x305D, 0x3064, 0x306D, // ta re so tsu ne 0x306A, 0x3089, 0x3080, 0x3046, 0x3090, // na ra mu u (w)i 0x306E, 0x304A, 0x304F, 0x3084, 0x307E, // no o ku ya ma 0x3051, 0x3075, 0x3053, 0x3048, 0x3066, // ke hu ko e te 0x3042, 0x3055, 0x304D, 0x3086, 0x3081, // a sa ki yu me 0x307F, 0x3057, 0x3091, 0x3072, 0x3082, // mi shi (w)e hi mo 0x305B, 0x3059 // se su }; #define KATAKANA_IROHA_CHARS_SIZE 47 static const char16_t gKatakanaIrohaChars[KATAKANA_IROHA_CHARS_SIZE] = { 0x30A4, 0x30ED, 0x30CF, 0x30CB, 0x30DB, // i ro ha ni ho 0x30D8, 0x30C8, 0x30C1, 0x30EA, 0x30CC, // he to chi ri nu 0x30EB, 0x30F2, 0x30EF, 0x30AB, 0x30E8, // ru (w)o wa ka yo 0x30BF, 0x30EC, 0x30BD, 0x30C4, 0x30CD, // ta re so tsu ne 0x30CA, 0x30E9, 0x30E0, 0x30A6, 0x30F0, // na ra mu u (w)i 0x30CE, 0x30AA, 0x30AF, 0x30E4, 0x30DE, // no o ku ya ma 0x30B1, 0x30D5, 0x30B3, 0x30A8, 0x30C6, // ke hu ko e te 0x30A2, 0x30B5, 0x30AD, 0x30E6, 0x30E1, // a sa ki yu me 0x30DF, 0x30B7, 0x30F1, 0x30D2, 0x30E2, // mi shi (w)e hi mo 0x30BB, 0x30B9 // se su }; #define LOWER_GREEK_CHARS_SIZE 24 // Note: 0x03C2 GREEK FINAL SIGMA is not used in here.... static const char16_t gLowerGreekChars[LOWER_GREEK_CHARS_SIZE] = { 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, // alpha beta gamma delta epsilon 0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, // zeta eta theta iota kappa 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, // lamda mu nu xi omicron 0x03C0, 0x03C1, 0x03C3, 0x03C4, 0x03C5, // pi rho sigma tau upsilon 0x03C6, 0x03C7, 0x03C8, 0x03C9 // phi chi psi omega }; #define CJK_HEAVENLY_STEM_CHARS_SIZE 10 static const char16_t gCJKHeavenlyStemChars[CJK_HEAVENLY_STEM_CHARS_SIZE] = { 0x7532, 0x4e59, 0x4e19, 0x4e01, 0x620a, 0x5df1, 0x5e9a, 0x8f9b, 0x58ec, 0x7678 }; #define CJK_EARTHLY_BRANCH_CHARS_SIZE 12 static const char16_t gCJKEarthlyBranchChars[CJK_EARTHLY_BRANCH_CHARS_SIZE] = { 0x5b50, 0x4e11, 0x5bc5, 0x536f, 0x8fb0, 0x5df3, 0x5348, 0x672a, 0x7533, 0x9149, 0x620c, 0x4ea5 }; #define HANGUL_CHARS_SIZE 14 static const char16_t gHangulChars[HANGUL_CHARS_SIZE] = { 0xac00, 0xb098, 0xb2e4, 0xb77c, 0xb9c8, 0xbc14, 0xc0ac, 0xc544, 0xc790, 0xcc28, 0xce74, 0xd0c0, 0xd30c, 0xd558 }; #define HANGUL_CONSONANT_CHARS_SIZE 14 static const char16_t gHangulConsonantChars[HANGUL_CONSONANT_CHARS_SIZE] = { 0x3131, 0x3134, 0x3137, 0x3139, 0x3141, 0x3142, 0x3145, 0x3147, 0x3148, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e }; // Ge'ez set of Ethiopic ordered list. There are other locale-dependent sets. // For the time being, let's implement two Ge'ez sets only // per Momoi san's suggestion in bug 102252. // For details, refer to http://www.ethiopic.org/Collation/OrderedLists.html. #define ETHIOPIC_HALEHAME_CHARS_SIZE 26 static const char16_t gEthiopicHalehameChars[ETHIOPIC_HALEHAME_CHARS_SIZE] = { 0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230, 0x1240, 0x1260, 0x1270, 0x1280, 0x1290, 0x12a0, 0x12a8, 0x12c8, 0x12d0, 0x12d8, 0x12e8, 0x12f0, 0x1308, 0x1320, 0x1330, 0x1338, 0x1340, 0x1348, 0x1350 }; #define ETHIOPIC_HALEHAME_AM_CHARS_SIZE 33 static const char16_t gEthiopicHalehameAmChars[ETHIOPIC_HALEHAME_AM_CHARS_SIZE] = { 0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230, 0x1238, 0x1240, 0x1260, 0x1270, 0x1278, 0x1280, 0x1290, 0x1298, 0x12a0, 0x12a8, 0x12b8, 0x12c8, 0x12d0, 0x12d8, 0x12e0, 0x12e8, 0x12f0, 0x1300, 0x1308, 0x1320, 0x1328, 0x1330, 0x1338, 0x1340, 0x1348, 0x1350 }; #define ETHIOPIC_HALEHAME_TI_ER_CHARS_SIZE 31 static const char16_t gEthiopicHalehameTiErChars[ETHIOPIC_HALEHAME_TI_ER_CHARS_SIZE] = { 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1250, 0x1260, 0x1270, 0x1278, 0x1290, 0x1298, 0x12a0, 0x12a8, 0x12b8, 0x12c8, 0x12d0, 0x12d8, 0x12e0, 0x12e8, 0x12f0, 0x1300, 0x1308, 0x1320, 0x1328, 0x1330, 0x1338, 0x1348, 0x1350 }; #define ETHIOPIC_HALEHAME_TI_ET_CHARS_SIZE 34 static const char16_t gEthiopicHalehameTiEtChars[ETHIOPIC_HALEHAME_TI_ET_CHARS_SIZE] = { 0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230, 0x1238, 0x1240, 0x1250, 0x1260, 0x1270, 0x1278, 0x1280, 0x1290, 0x1298, 0x12a0, 0x12a8, 0x12b8, 0x12c8, 0x12d0, 0x12d8, 0x12e0, 0x12e8, 0x12f0, 0x1300, 0x1308, 0x1320, 0x1328, 0x1330, 0x1338, 0x1340, 0x1348, 0x1350 }; // We know cjk-ideographic need 31 characters to display 99,999,999,999,999,999 // georgian needs 6 at most // armenian needs 12 at most // hebrew may need more... #define NUM_BUF_SIZE 34 static bool CharListToText(int32_t ordinal, nsString& result, const char16_t* chars, int32_t aBase) { char16_t buf[NUM_BUF_SIZE]; int32_t idx = NUM_BUF_SIZE; if (ordinal < 1) { return false; } do { ordinal--; // a == 0 int32_t cur = ordinal % aBase; buf[--idx] = chars[cur]; ordinal /= aBase ; } while ( ordinal > 0); result.Append(buf+idx,NUM_BUF_SIZE-idx); return true; } static const char16_t gCJKDecimalChars[10] = { 0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d }; static bool CharListDecimalToText(int32_t ordinal, nsString& result, const char16_t* chars) { if (ordinal < 0) { return false; } char16_t buf[NUM_BUF_SIZE]; int32_t idx = NUM_BUF_SIZE; do { buf[--idx] = chars[ordinal % 10]; ordinal /= 10; } while (ordinal > 0); result.Append(buf + idx, NUM_BUF_SIZE - idx); return true; } enum CJKIdeographicLang { CHINESE, KOREAN, JAPANESE }; struct CJKIdeographicData { const char16_t *negative; char16_t digit[10]; char16_t unit[3]; char16_t unit10K[2]; uint8_t lang; bool informal; }; static const char16_t gJapaneseNegative[] = { 0x30de, 0x30a4, 0x30ca, 0x30b9, 0x0000 }; static const CJKIdeographicData gDataJapaneseInformal = { gJapaneseNegative, // negative { // digit 0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d }, { 0x5341, 0x767e, 0x5343 }, // unit { 0x4e07, 0x5104 }, // unit10K JAPANESE, // lang true // informal }; static const CJKIdeographicData gDataJapaneseFormal = { gJapaneseNegative, // negative { // digit 0x96f6, 0x58f1, 0x5f10, 0x53c2, 0x56db, 0x4f0d, 0x516d, 0x4e03, 0x516b, 0x4e5d }, { 0x62fe, 0x767e, 0x9621 }, // unit { 0x842c, 0x5104 }, // unit10K JAPANESE, // lang false // informal }; static const char16_t gKoreanNegative[] = { 0xb9c8, 0xc774, 0xb108, 0xc2a4, 0x0020, 0x0000 }; static const CJKIdeographicData gDataKoreanHangulFormal = { gKoreanNegative, // negative { // digit 0xc601, 0xc77c, 0xc774, 0xc0bc, 0xc0ac, 0xc624, 0xc721, 0xce60, 0xd314, 0xad6c }, { 0xc2ed, 0xbc31, 0xcc9c }, // unit { 0xb9cc, 0xc5b5 }, // unit10K KOREAN, // lang false // informal }; static const CJKIdeographicData gDataKoreanHanjaInformal = { gKoreanNegative, // negative { // digit 0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d }, { 0x5341, 0x767e, 0x5343 }, // unit { 0x842c, 0x5104 }, // unit10K KOREAN, // lang true // informal }; static const CJKIdeographicData gDataKoreanHanjaFormal = { gKoreanNegative, // negative { // digit 0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d }, { 0x62fe, 0x767e, 0x4edf }, // unit { 0x842c, 0x5104 }, // unit10K KOREAN, // lang false // informal }; static const char16_t gSimpChineseNegative[] = { 0x8d1f, 0x0000 }; static const CJKIdeographicData gDataSimpChineseInformal = { gSimpChineseNegative, // negative { // digit 0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d }, { 0x5341, 0x767e, 0x5343 }, // unit { 0x4e07, 0x4ebf }, // unit10K CHINESE, // lang true // informal }; static const CJKIdeographicData gDataSimpChineseFormal = { gSimpChineseNegative, // negative { // digit 0x96f6, 0x58f9, 0x8d30, 0x53c1, 0x8086, 0x4f0d, 0x9646, 0x67d2, 0x634c, 0x7396 }, { 0x62fe, 0x4f70, 0x4edf }, // unit { 0x4e07, 0x4ebf }, // unit10K CHINESE, // lang false // informal }; static const char16_t gTradChineseNegative[] = { 0x8ca0, 0x0000 }; static const CJKIdeographicData gDataTradChineseInformal = { gTradChineseNegative, // negative { // digit 0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d }, { 0x5341, 0x767e, 0x5343 }, // unit { 0x842c, 0x5104 }, // unit10K CHINESE, // lang true // informal }; static const CJKIdeographicData gDataTradChineseFormal = { gTradChineseNegative, // negative { // digit 0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x8086, 0x4f0d, 0x9678, 0x67d2, 0x634c, 0x7396 }, { 0x62fe, 0x4f70, 0x4edf }, // unit { 0x842c, 0x5104 }, // unit10K CHINESE, // lang false // informal }; static const bool CJKIdeographicToText(int32_t aOrdinal, nsString& result, const CJKIdeographicData& data) { char16_t buf[NUM_BUF_SIZE]; int32_t idx = NUM_BUF_SIZE; int32_t pos = 0; bool isNegative = (aOrdinal < 0); bool needZero = (aOrdinal == 0); int32_t unitidx = 0, unit10Kidx = 0; uint32_t ordinal = mozilla::Abs(aOrdinal); do { unitidx = pos % 4; if (unitidx == 0) { unit10Kidx = pos / 4; } int32_t cur = ordinal % 10; if (cur == 0) { if (needZero) { needZero = false; buf[--idx] = data.digit[0]; } } else { if (data.lang == CHINESE) { needZero = true; } if (unit10Kidx != 0) { if (data.lang == KOREAN) { buf[--idx] = ' '; } buf[--idx] = data.unit10K[unit10Kidx - 1]; } if (unitidx != 0) { buf[--idx] = data.unit[unitidx - 1]; } if (cur != 1) { buf[--idx] = data.digit[cur]; } else { bool needOne = true; if (data.informal) { switch (data.lang) { case CHINESE: if (unitidx == 1 && (ordinal == 1 || (pos > 4 && ordinal % 1000 == 1))) { needOne = false; } break; case JAPANESE: if (unitidx > 0 && (unitidx != 3 || (pos == 3 && ordinal == 1))) { needOne = false; } break; case KOREAN: if (unitidx > 0 || (pos == 4 && (ordinal % 1000) == 1)) { needOne = false; } break; } } if (needOne) { buf[--idx] = data.digit[1]; } } unit10Kidx = 0; } ordinal /= 10; pos++; } while (ordinal > 0); if (isNegative) { result.Append(data.negative); } result.Append(buf + idx, NUM_BUF_SIZE - idx); return true; } #define HEBREW_GERESH 0x05F3 static const char16_t gHebrewDigit[22] = { // 1 2 3 4 5 6 7 8 9 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, // 10 20 30 40 50 60 70 80 90 0x05D9, 0x05DB, 0x05DC, 0x05DE, 0x05E0, 0x05E1, 0x05E2, 0x05E4, 0x05E6, // 100 200 300 400 0x05E7, 0x05E8, 0x05E9, 0x05EA }; static bool HebrewToText(int32_t ordinal, nsString& result) { if (ordinal < 1 || ordinal > 999999) { return false; } bool outputSep = false; nsAutoString allText, thousandsGroup; do { thousandsGroup.Truncate(); int32_t n3 = ordinal % 1000; // Process digit for 100 - 900 for(int32_t n1 = 400; n1 > 0; ) { if( n3 >= n1) { n3 -= n1; thousandsGroup.Append(gHebrewDigit[(n1/100)-1+18]); } else { n1 -= 100; } // if } // for // Process digit for 10 - 90 int32_t n2; if( n3 >= 10 ) { // Special process for 15 and 16 if(( 15 == n3 ) || (16 == n3)) { // Special rule for religious reason... // 15 is represented by 9 and 6, not 10 and 5 // 16 is represented by 9 and 7, not 10 and 6 n2 = 9; thousandsGroup.Append(gHebrewDigit[ n2 - 1]); } else { n2 = n3 - (n3 % 10); thousandsGroup.Append(gHebrewDigit[(n2/10)-1+9]); } // if n3 -= n2; } // if // Process digit for 1 - 9 if ( n3 > 0) thousandsGroup.Append(gHebrewDigit[n3-1]); if (outputSep) thousandsGroup.Append((char16_t)HEBREW_GERESH); if (allText.IsEmpty()) allText = thousandsGroup; else allText = thousandsGroup + allText; ordinal /= 1000; outputSep = true; } while (ordinal >= 1); result.Append(allText); return true; } static bool ArmenianToText(int32_t ordinal, nsString& result) { if (ordinal < 1 || ordinal > 9999) { // zero or reach the limit of Armenian numbering system return false; } char16_t buf[NUM_BUF_SIZE]; int32_t idx = NUM_BUF_SIZE; int32_t d = 0; do { int32_t cur = ordinal % 10; if (cur > 0) { char16_t u = 0x0530 + (d * 9) + cur; buf[--idx] = u; } ++d; ordinal /= 10; } while (ordinal > 0); result.Append(buf + idx, NUM_BUF_SIZE - idx); return true; } static const char16_t gGeorgianValue [ 37 ] = { // 4 * 9 + 1 = 37 // 1 2 3 4 5 6 7 8 9 0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10F1, 0x10D7, // 10 20 30 40 50 60 70 80 90 0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10F2, 0x10DD, 0x10DE, 0x10DF, // 100 200 300 400 500 600 700 800 900 0x10E0, 0x10E1, 0x10E2, 0x10F3, 0x10E4, 0x10E5, 0x10E6, 0x10E7, 0x10E8, // 1000 2000 3000 4000 5000 6000 7000 8000 9000 0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10F4, 0x10EF, 0x10F0, // 10000 0x10F5 }; static bool GeorgianToText(int32_t ordinal, nsString& result) { if (ordinal < 1 || ordinal > 19999) { // zero or reach the limit of Georgian numbering system return false; } char16_t buf[NUM_BUF_SIZE]; int32_t idx = NUM_BUF_SIZE; int32_t d = 0; do { int32_t cur = ordinal % 10; if (cur > 0) { char16_t u = gGeorgianValue[(d * 9 ) + ( cur - 1)]; buf[--idx] = u; } ++d; ordinal /= 10; } while (ordinal > 0); result.Append(buf + idx, NUM_BUF_SIZE - idx); return true; } // Convert ordinal to Ethiopic numeric representation. // The detail is available at http://www.ethiopic.org/Numerals/ // The algorithm used here is based on the pseudo-code put up there by // Daniel Yacob . // Another reference is Unicode 3.0 standard section 11.1. #define ETHIOPIC_ONE 0x1369 #define ETHIOPIC_TEN 0x1372 #define ETHIOPIC_HUNDRED 0x137B #define ETHIOPIC_TEN_THOUSAND 0x137C static bool EthiopicToText(int32_t ordinal, nsString& result) { if (ordinal < 1) { return false; } nsAutoString asciiNumberString; // decimal string representation of ordinal DecimalToText(ordinal, asciiNumberString); uint8_t asciiStringLength = asciiNumberString.Length(); // If number length is odd, add a leading "0" // the leading "0" preconditions the string to always have the // leading tens place populated, this avoids a check within the loop. // If we didn't add the leading "0", decrement asciiStringLength so // it will be equivalent to a zero-based index in both cases. if (asciiStringLength & 1) { asciiNumberString.Insert(NS_LITERAL_STRING("0"), 0); } else { asciiStringLength--; } // Iterate from the highest digits to lowest // indexFromLeft indexes digits (0 = most significant) // groupIndexFromRight indexes pairs of digits (0 = least significant) for (uint8_t indexFromLeft = 0, groupIndexFromRight = asciiStringLength >> 1; indexFromLeft <= asciiStringLength; indexFromLeft += 2, groupIndexFromRight--) { uint8_t tensValue = asciiNumberString.CharAt(indexFromLeft) & 0x0F; uint8_t unitsValue = asciiNumberString.CharAt(indexFromLeft + 1) & 0x0F; uint8_t groupValue = tensValue * 10 + unitsValue; bool oddGroup = (groupIndexFromRight & 1); // we want to clear ETHIOPIC_ONE when it is superfluous if (ordinal > 1 && groupValue == 1 && // one without a leading ten (oddGroup || indexFromLeft == 0)) { // preceding (100) or leading the sequence unitsValue = 0; } // put it all together... if (tensValue) { // map onto Ethiopic "tens": result.Append((char16_t) (tensValue + ETHIOPIC_TEN - 1)); } if (unitsValue) { //map onto Ethiopic "units": result.Append((char16_t) (unitsValue + ETHIOPIC_ONE - 1)); } // Add a separator for all even groups except the last, // and for odd groups with non-zero value. if (oddGroup) { if (groupValue) { result.Append((char16_t) ETHIOPIC_HUNDRED); } } else { if (groupIndexFromRight) { result.Append((char16_t) ETHIOPIC_TEN_THOUSAND); } } } return true; } /* static */ void nsBulletFrame::AppendCounterText(int32_t aListStyleType, int32_t aOrdinal, nsString& result, bool& isRTL) { bool success = true; int32_t fallback = NS_STYLE_LIST_STYLE_DECIMAL; isRTL = false; switch (aListStyleType) { case NS_STYLE_LIST_STYLE_NONE: // used by counters code only break; case NS_STYLE_LIST_STYLE_DISC: // used by counters code only // XXX We really need to do this the same way we do list bullets. result.Append(char16_t(0x2022)); break; case NS_STYLE_LIST_STYLE_CIRCLE: // used by counters code only // XXX We really need to do this the same way we do list bullets. result.Append(char16_t(0x25E6)); break; case NS_STYLE_LIST_STYLE_SQUARE: // used by counters code only // XXX We really need to do this the same way we do list bullets. result.Append(char16_t(0x25FE)); break; case NS_STYLE_LIST_STYLE_DECIMAL: default: // CSS2 say "A users agent that does not recognize a numbering system // should use 'decimal' success = DecimalToText(aOrdinal, result); NS_ASSERTION(success, "DecimalToText must never fail"); break; case NS_STYLE_LIST_STYLE_DECIMAL_LEADING_ZERO: success = DecimalLeadingZeroToText(aOrdinal, result); break; case NS_STYLE_LIST_STYLE_CJK_DECIMAL: success = CharListDecimalToText(aOrdinal, result, gCJKDecimalChars); break; case NS_STYLE_LIST_STYLE_LOWER_ROMAN: success = RomanToText(aOrdinal, result, gLowerRomanCharsA, gLowerRomanCharsB); break; case NS_STYLE_LIST_STYLE_UPPER_ROMAN: success = RomanToText(aOrdinal, result, gUpperRomanCharsA, gUpperRomanCharsB); break; case NS_STYLE_LIST_STYLE_LOWER_ALPHA: success = CharListToText(aOrdinal, result, gLowerAlphaChars, ALPHA_SIZE); break; case NS_STYLE_LIST_STYLE_UPPER_ALPHA: success = CharListToText(aOrdinal, result, gUpperAlphaChars, ALPHA_SIZE); break; case NS_STYLE_LIST_STYLE_KATAKANA: success = CharListToText(aOrdinal, result, gKatakanaChars, KATAKANA_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_HIRAGANA: success = CharListToText(aOrdinal, result, gHiraganaChars, HIRAGANA_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_KATAKANA_IROHA: success = CharListToText(aOrdinal, result, gKatakanaIrohaChars, KATAKANA_IROHA_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_HIRAGANA_IROHA: success = CharListToText(aOrdinal, result, gHiraganaIrohaChars, HIRAGANA_IROHA_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_LOWER_GREEK: success = CharListToText(aOrdinal, result, gLowerGreekChars , LOWER_GREEK_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_CJK_IDEOGRAPHIC: case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_INFORMAL: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CJKIdeographicToText(aOrdinal, result, gDataTradChineseInformal); break; case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_FORMAL: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CJKIdeographicToText(aOrdinal, result, gDataTradChineseFormal); break; case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_INFORMAL: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CJKIdeographicToText(aOrdinal, result, gDataSimpChineseInformal); break; case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_FORMAL: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CJKIdeographicToText(aOrdinal, result, gDataSimpChineseFormal); break; case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_INFORMAL: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CJKIdeographicToText(aOrdinal, result, gDataJapaneseInformal); break; case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_FORMAL: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CJKIdeographicToText(aOrdinal, result, gDataJapaneseFormal); break; case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CJKIdeographicToText(aOrdinal, result, gDataKoreanHangulFormal); break; case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CJKIdeographicToText(aOrdinal, result, gDataKoreanHanjaInformal); break; case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CJKIdeographicToText(aOrdinal, result, gDataKoreanHanjaFormal); break; case NS_STYLE_LIST_STYLE_HEBREW: isRTL = true; success = HebrewToText(aOrdinal, result); break; case NS_STYLE_LIST_STYLE_ARMENIAN: success = ArmenianToText(aOrdinal, result); break; case NS_STYLE_LIST_STYLE_GEORGIAN: success = GeorgianToText(aOrdinal, result); break; case NS_STYLE_LIST_STYLE_MOZ_ARABIC_INDIC: success = OtherDecimalToText(aOrdinal, 0x0660, result); break; case NS_STYLE_LIST_STYLE_MOZ_PERSIAN: case NS_STYLE_LIST_STYLE_MOZ_URDU: success = OtherDecimalToText(aOrdinal, 0x06f0, result); break; case NS_STYLE_LIST_STYLE_MOZ_DEVANAGARI: success = OtherDecimalToText(aOrdinal, 0x0966, result); break; case NS_STYLE_LIST_STYLE_MOZ_GURMUKHI: success = OtherDecimalToText(aOrdinal, 0x0a66, result); break; case NS_STYLE_LIST_STYLE_MOZ_GUJARATI: success = OtherDecimalToText(aOrdinal, 0x0AE6, result); break; case NS_STYLE_LIST_STYLE_MOZ_ORIYA: success = OtherDecimalToText(aOrdinal, 0x0B66, result); break; case NS_STYLE_LIST_STYLE_MOZ_KANNADA: success = OtherDecimalToText(aOrdinal, 0x0CE6, result); break; case NS_STYLE_LIST_STYLE_MOZ_MALAYALAM: success = OtherDecimalToText(aOrdinal, 0x0D66, result); break; case NS_STYLE_LIST_STYLE_MOZ_THAI: success = OtherDecimalToText(aOrdinal, 0x0E50, result); break; case NS_STYLE_LIST_STYLE_MOZ_LAO: success = OtherDecimalToText(aOrdinal, 0x0ED0, result); break; case NS_STYLE_LIST_STYLE_MOZ_MYANMAR: success = OtherDecimalToText(aOrdinal, 0x1040, result); break; case NS_STYLE_LIST_STYLE_MOZ_KHMER: success = OtherDecimalToText(aOrdinal, 0x17E0, result); break; case NS_STYLE_LIST_STYLE_MOZ_BENGALI: success = OtherDecimalToText(aOrdinal, 0x09E6, result); break; case NS_STYLE_LIST_STYLE_MOZ_TELUGU: success = OtherDecimalToText(aOrdinal, 0x0C66, result); break; case NS_STYLE_LIST_STYLE_MOZ_TAMIL: success = TamilToText(aOrdinal, result); break; case NS_STYLE_LIST_STYLE_MOZ_CJK_HEAVENLY_STEM: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CharListToText(aOrdinal, result, gCJKHeavenlyStemChars, CJK_HEAVENLY_STEM_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_MOZ_CJK_EARTHLY_BRANCH: fallback = NS_STYLE_LIST_STYLE_CJK_DECIMAL; success = CharListToText(aOrdinal, result, gCJKEarthlyBranchChars, CJK_EARTHLY_BRANCH_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_MOZ_HANGUL: success = CharListToText(aOrdinal, result, gHangulChars, HANGUL_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_MOZ_HANGUL_CONSONANT: success = CharListToText(aOrdinal, result, gHangulConsonantChars, HANGUL_CONSONANT_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME: success = CharListToText(aOrdinal, result, gEthiopicHalehameChars, ETHIOPIC_HALEHAME_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_NUMERIC: success = EthiopicToText(aOrdinal, result); break; case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_AM: success = CharListToText(aOrdinal, result, gEthiopicHalehameAmChars, ETHIOPIC_HALEHAME_AM_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ER: success = CharListToText(aOrdinal, result, gEthiopicHalehameTiErChars, ETHIOPIC_HALEHAME_TI_ER_CHARS_SIZE); break; case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ET: success = CharListToText(aOrdinal, result, gEthiopicHalehameTiEtChars, ETHIOPIC_HALEHAME_TI_ET_CHARS_SIZE); break; } if (!success) { AppendCounterText(fallback, aOrdinal, result, isRTL); } } /* static */ void nsBulletFrame::GetListItemSuffix(int32_t aListStyleType, nsString& aResult, bool& aSuppressPadding) { aResult = '.'; aSuppressPadding = false; switch (aListStyleType) { case NS_STYLE_LIST_STYLE_NONE: // used by counters code only case NS_STYLE_LIST_STYLE_DISC: // used by counters code only case NS_STYLE_LIST_STYLE_CIRCLE: // used by counters code only case NS_STYLE_LIST_STYLE_SQUARE: // used by counters code only aResult.Truncate(); break; case NS_STYLE_LIST_STYLE_CJK_DECIMAL: case NS_STYLE_LIST_STYLE_CJK_IDEOGRAPHIC: case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL: case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_CJK_HEAVENLY_STEM: case NS_STYLE_LIST_STYLE_MOZ_CJK_EARTHLY_BRANCH: aResult = 0x3001; aSuppressPadding = true; break; case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL: case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL: case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_HANGUL: case NS_STYLE_LIST_STYLE_MOZ_HANGUL_CONSONANT: aResult = ','; break; } } void nsBulletFrame::GetListItemText(const nsStyleList& aListStyle, nsString& result) { const nsStyleVisibility* vis = StyleVisibility(); NS_ASSERTION(aListStyle.mListStyleType != NS_STYLE_LIST_STYLE_NONE && aListStyle.mListStyleType != NS_STYLE_LIST_STYLE_DISC && aListStyle.mListStyleType != NS_STYLE_LIST_STYLE_CIRCLE && aListStyle.mListStyleType != NS_STYLE_LIST_STYLE_SQUARE, "we should be using specialized code for these types"); result.Truncate(); AppendCounterText(aListStyle.mListStyleType, mOrdinal, result, mTextIsRTL); nsAutoString suffix; GetListItemSuffix(aListStyle.mListStyleType, suffix, mSuppressPadding); // We're not going to do proper Bidi reordering on the list item marker, but // just display the whole thing as RTL or LTR, so we fake reordering by // appending the suffix to the end of the list item marker if the // directionality of the characters is the same as the style direction or // prepending it to the beginning if they are different. result = (mTextIsRTL == (vis->mDirection == NS_STYLE_DIRECTION_RTL)) ? result + suffix : suffix + result; } #define MIN_BULLET_SIZE 1 void nsBulletFrame::GetDesiredSize(nsPresContext* aCX, nsRenderingContext *aRenderingContext, nsHTMLReflowMetrics& aMetrics, float aFontSizeInflation) { // Reset our padding. If we need it, we'll set it below. mPadding.SizeTo(0, 0, 0, 0); const nsStyleList* myList = StyleList(); nscoord ascent; RemoveStateBits(BULLET_FRAME_IMAGE_LOADING); if (myList->GetListStyleImage() && mImageRequest) { uint32_t status; mImageRequest->GetImageStatus(&status); if (status & imgIRequest::STATUS_SIZE_AVAILABLE && !(status & imgIRequest::STATUS_ERROR)) { // auto size the image aMetrics.Width() = mIntrinsicSize.width; aMetrics.SetTopAscent(aMetrics.Height() = mIntrinsicSize.height); AddStateBits(BULLET_FRAME_IMAGE_LOADING); return; } } // If we're getting our desired size and don't have an image, reset // mIntrinsicSize to (0,0). Otherwise, if we used to have an image, it // changed, and the new one is coming in, but we're reflowing before it's // fully there, we'll end up with mIntrinsicSize not matching our size, but // won't trigger a reflow in OnStartContainer (because mIntrinsicSize will // match the image size). mIntrinsicSize.SizeTo(0, 0); nsRefPtr fm; nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm), aFontSizeInflation); nscoord bulletSize; nsAutoString text; switch (myList->mListStyleType) { case NS_STYLE_LIST_STYLE_NONE: aMetrics.Width() = aMetrics.Height() = 0; aMetrics.SetTopAscent(0); break; case NS_STYLE_LIST_STYLE_DISC: case NS_STYLE_LIST_STYLE_CIRCLE: case NS_STYLE_LIST_STYLE_SQUARE: ascent = fm->MaxAscent(); bulletSize = std::max(nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE), NSToCoordRound(0.8f * (float(ascent) / 2.0f))); mPadding.bottom = NSToCoordRound(float(ascent) / 8.0f); aMetrics.Width() = aMetrics.Height() = bulletSize; aMetrics.SetTopAscent(bulletSize + mPadding.bottom); break; default: case NS_STYLE_LIST_STYLE_DECIMAL_LEADING_ZERO: case NS_STYLE_LIST_STYLE_DECIMAL: case NS_STYLE_LIST_STYLE_CJK_DECIMAL: case NS_STYLE_LIST_STYLE_LOWER_ROMAN: case NS_STYLE_LIST_STYLE_UPPER_ROMAN: case NS_STYLE_LIST_STYLE_LOWER_ALPHA: case NS_STYLE_LIST_STYLE_UPPER_ALPHA: case NS_STYLE_LIST_STYLE_KATAKANA: case NS_STYLE_LIST_STYLE_HIRAGANA: case NS_STYLE_LIST_STYLE_KATAKANA_IROHA: case NS_STYLE_LIST_STYLE_HIRAGANA_IROHA: case NS_STYLE_LIST_STYLE_LOWER_GREEK: case NS_STYLE_LIST_STYLE_HEBREW: case NS_STYLE_LIST_STYLE_ARMENIAN: case NS_STYLE_LIST_STYLE_GEORGIAN: case NS_STYLE_LIST_STYLE_CJK_IDEOGRAPHIC: case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL: case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL: case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL: case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL: case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL: case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_INFORMAL: case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_FORMAL: case NS_STYLE_LIST_STYLE_MOZ_CJK_HEAVENLY_STEM: case NS_STYLE_LIST_STYLE_MOZ_CJK_EARTHLY_BRANCH: case NS_STYLE_LIST_STYLE_MOZ_ARABIC_INDIC: case NS_STYLE_LIST_STYLE_MOZ_PERSIAN: case NS_STYLE_LIST_STYLE_MOZ_URDU: case NS_STYLE_LIST_STYLE_MOZ_DEVANAGARI: case NS_STYLE_LIST_STYLE_MOZ_GURMUKHI: case NS_STYLE_LIST_STYLE_MOZ_GUJARATI: case NS_STYLE_LIST_STYLE_MOZ_ORIYA: case NS_STYLE_LIST_STYLE_MOZ_KANNADA: case NS_STYLE_LIST_STYLE_MOZ_MALAYALAM: case NS_STYLE_LIST_STYLE_MOZ_BENGALI: case NS_STYLE_LIST_STYLE_MOZ_TAMIL: case NS_STYLE_LIST_STYLE_MOZ_TELUGU: case NS_STYLE_LIST_STYLE_MOZ_THAI: case NS_STYLE_LIST_STYLE_MOZ_LAO: case NS_STYLE_LIST_STYLE_MOZ_MYANMAR: case NS_STYLE_LIST_STYLE_MOZ_KHMER: case NS_STYLE_LIST_STYLE_MOZ_HANGUL: case NS_STYLE_LIST_STYLE_MOZ_HANGUL_CONSONANT: case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME: case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_NUMERIC: case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_AM: case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ER: case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ET: GetListItemText(*myList, text); aMetrics.Height() = fm->MaxHeight(); aRenderingContext->SetFont(fm); aMetrics.Width() = nsLayoutUtils::GetStringWidth(this, aRenderingContext, text.get(), text.Length()); aMetrics.SetTopAscent(fm->MaxAscent()); break; } } NS_IMETHODIMP nsBulletFrame::Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aMetrics, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { DO_GLOBAL_REFLOW_COUNT("nsBulletFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus); float inflation = nsLayoutUtils::FontSizeInflationFor(this); SetFontSizeInflation(inflation); // Get the base size // This will also set mSuppressPadding appropriately (via GetListItemText()) // for the builtin counter styles with ideographic comma as suffix where the // default padding from ua.css is not desired. GetDesiredSize(aPresContext, aReflowState.rendContext, aMetrics, inflation); // Add in the border and padding; split the top/bottom between the // ascent and descent to make things look nice const nsMargin& borderPadding = aReflowState.ComputedPhysicalBorderPadding(); if (!mSuppressPadding || aPresContext->HasAuthorSpecifiedRules(this, NS_AUTHOR_SPECIFIED_PADDING)) { mPadding.top += NSToCoordRound(borderPadding.top * inflation); mPadding.right += NSToCoordRound(borderPadding.right * inflation); mPadding.bottom += NSToCoordRound(borderPadding.bottom * inflation); mPadding.left += NSToCoordRound(borderPadding.left * inflation); } aMetrics.Width() += mPadding.left + mPadding.right; aMetrics.Height() += mPadding.top + mPadding.bottom; aMetrics.SetTopAscent(aMetrics.TopAscent() + mPadding.top); // XXX this is a bit of a hack, we're assuming that no glyphs used for bullets // overflow their font-boxes. It'll do for now; to fix it for real, we really // should rewrite all the text-handling code here to use gfxTextRun (bug // 397294). aMetrics.SetOverflowAreasToDesiredBounds(); aStatus = NS_FRAME_COMPLETE; NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics); return NS_OK; } /* virtual */ nscoord nsBulletFrame::GetMinWidth(nsRenderingContext *aRenderingContext) { nsHTMLReflowMetrics metrics(GetWritingMode()); DISPLAY_MIN_WIDTH(this, metrics.Width()); GetDesiredSize(PresContext(), aRenderingContext, metrics, 1.0f); return metrics.Width(); } /* virtual */ nscoord nsBulletFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) { nsHTMLReflowMetrics metrics(GetWritingMode()); DISPLAY_PREF_WIDTH(this, metrics.Width()); GetDesiredSize(PresContext(), aRenderingContext, metrics, 1.0f); return metrics.Width(); } NS_IMETHODIMP nsBulletFrame::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) { if (aType == imgINotificationObserver::SIZE_AVAILABLE) { nsCOMPtr image; aRequest->GetImage(getter_AddRefs(image)); return OnStartContainer(aRequest, image); } if (aType == imgINotificationObserver::FRAME_UPDATE) { // The image has changed. // Invalidate the entire content area. Maybe it's not optimal but it's simple and // always correct, and I'll be a stunned mullet if it ever matters for performance InvalidateFrame(); } if (aType == imgINotificationObserver::IS_ANIMATED) { // Register the image request with the refresh driver now that we know it's // animated. if (aRequest == mImageRequest) { nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest, &mRequestRegistered); } } return NS_OK; } nsresult nsBulletFrame::OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage) { if (!aImage) return NS_ERROR_INVALID_ARG; if (!aRequest) return NS_ERROR_INVALID_ARG; uint32_t status; aRequest->GetImageStatus(&status); if (status & imgIRequest::STATUS_ERROR) { return NS_OK; } nscoord w, h; aImage->GetWidth(&w); aImage->GetHeight(&h); nsPresContext* presContext = PresContext(); nsSize newsize(nsPresContext::CSSPixelsToAppUnits(w), nsPresContext::CSSPixelsToAppUnits(h)); if (mIntrinsicSize != newsize) { mIntrinsicSize = newsize; // Now that the size is available (or an error occurred), trigger // a reflow of the bullet frame. nsIPresShell *shell = presContext->GetPresShell(); if (shell) { shell->FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); } } // Handle animations aImage->SetAnimationMode(presContext->ImageAnimationMode()); // Ensure the animation (if any) is started. Note: There is no // corresponding call to Decrement for this. This Increment will be // 'cleaned up' by the Request when it is destroyed, but only then. aRequest->IncrementAnimationConsumers(); return NS_OK; } void nsBulletFrame::GetLoadGroup(nsPresContext *aPresContext, nsILoadGroup **aLoadGroup) { if (!aPresContext) return; NS_PRECONDITION(nullptr != aLoadGroup, "null OUT parameter pointer"); nsIPresShell *shell = aPresContext->GetPresShell(); if (!shell) return; nsIDocument *doc = shell->GetDocument(); if (!doc) return; *aLoadGroup = doc->GetDocumentLoadGroup().get(); // already_AddRefed } union VoidPtrOrFloat { VoidPtrOrFloat() : p(nullptr) {} void *p; float f; }; float nsBulletFrame::GetFontSizeInflation() const { if (!HasFontSizeInflation()) { return 1.0f; } VoidPtrOrFloat u; u.p = Properties().Get(FontSizeInflationProperty()); return u.f; } void nsBulletFrame::SetFontSizeInflation(float aInflation) { if (aInflation == 1.0f) { if (HasFontSizeInflation()) { RemoveStateBits(BULLET_FRAME_HAS_FONT_INFLATION); Properties().Delete(FontSizeInflationProperty()); } return; } AddStateBits(BULLET_FRAME_HAS_FONT_INFLATION); VoidPtrOrFloat u; u.f = aInflation; Properties().Set(FontSizeInflationProperty(), u.p); } already_AddRefed nsBulletFrame::GetImage() const { if (mImageRequest && StyleList()->GetListStyleImage()) { nsCOMPtr imageCon; mImageRequest->GetImage(getter_AddRefs(imageCon)); return imageCon.forget(); } return nullptr; } nscoord nsBulletFrame::GetBaseline() const { nscoord ascent = 0, bottomPadding; if (GetStateBits() & BULLET_FRAME_IMAGE_LOADING) { ascent = GetRect().height; } else { nsRefPtr fm; nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm), GetFontSizeInflation()); const nsStyleList* myList = StyleList(); switch (myList->mListStyleType) { case NS_STYLE_LIST_STYLE_NONE: break; case NS_STYLE_LIST_STYLE_DISC: case NS_STYLE_LIST_STYLE_CIRCLE: case NS_STYLE_LIST_STYLE_SQUARE: ascent = fm->MaxAscent(); bottomPadding = NSToCoordRound(float(ascent) / 8.0f); ascent = std::max(nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE), NSToCoordRound(0.8f * (float(ascent) / 2.0f))); ascent += bottomPadding; break; default: ascent = fm->MaxAscent(); break; } } return ascent + GetUsedBorderAndPadding().top; } NS_IMPL_ISUPPORTS1(nsBulletListener, imgINotificationObserver) nsBulletListener::nsBulletListener() : mFrame(nullptr) { } nsBulletListener::~nsBulletListener() { } NS_IMETHODIMP nsBulletListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) { if (!mFrame) return NS_ERROR_FAILURE; return mFrame->Notify(aRequest, aType, aData); }