Moved leaf classes of MDefinition class tree into MIRIns.h
MozReview-Commit-ID: d0ohui9P2C
new file mode 100644
--- /dev/null
+++ b/gfx/layers/client/TextureClientPool.cpp.orig
@@ -0,0 +1,339 @@
+/* -*- Mode: C++; tab-width: 20; 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 "TextureClientPool.h"
+#include "CompositableClient.h"
+#include "mozilla/layers/CompositableForwarder.h"
+#include "mozilla/layers/TextureForwarder.h"
+#include "mozilla/layers/TiledContentClient.h"
+
+#include "gfxPrefs.h"
+
+#include "nsComponentManagerUtils.h"
+
+#define TCP_LOG(...)
+//#define TCP_LOG(...) printf_stderr(__VA_ARGS__);
+
+namespace mozilla {
+namespace layers {
+
+// We want to shrink to our maximum size of N unused tiles
+// after a timeout to allow for short-term budget requirements
+static void
+ShrinkCallback(nsITimer *aTimer, void *aClosure)
+{
+ static_cast<TextureClientPool*>(aClosure)->ShrinkToMaximumSize();
+}
+
+// After a certain amount of inactivity, let's clear the pool so that
+// we don't hold onto tiles needlessly. In general, allocations are
+// cheap enough that re-allocating isn't an issue unless we're allocating
+// at an inopportune time (e.g. mid-animation).
+static void
+ClearCallback(nsITimer *aTimer, void *aClosure)
+{
+ static_cast<TextureClientPool*>(aClosure)->Clear();
+}
+
+TextureClientPool::TextureClientPool(LayersBackend aLayersBackend,
+ int32_t aMaxTextureSize,
+ gfx::SurfaceFormat aFormat,
+ gfx::IntSize aSize,
+ TextureFlags aFlags,
+ uint32_t aShrinkTimeoutMsec,
+ uint32_t aClearTimeoutMsec,
+ uint32_t aInitialPoolSize,
+ uint32_t aPoolUnusedSize,
+ TextureForwarder* aAllocator)
+ : mBackend(aLayersBackend)
+ , mMaxTextureSize(aMaxTextureSize)
+ , mFormat(aFormat)
+ , mSize(aSize)
+ , mFlags(aFlags)
+ , mShrinkTimeoutMsec(aShrinkTimeoutMsec)
+ , mClearTimeoutMsec(aClearTimeoutMsec)
+ , mInitialPoolSize(aInitialPoolSize)
+ , mPoolUnusedSize(aPoolUnusedSize)
+ , mOutstandingClients(0)
+ , mSurfaceAllocator(aAllocator)
+ , mDestroyed(false)
+{
+ TCP_LOG("TexturePool %p created with maximum unused texture clients %u\n",
+ this, mInitialPoolSize);
+ mShrinkTimer = new mozilla::timer;
+ mClearTimer = new mozilla::timer;
+ if (aFormat == gfx::SurfaceFormat::UNKNOWN) {
+ gfxWarning() << "Creating texture pool for SurfaceFormat::UNKNOWN format";
+ }
+}
+
+TextureClientPool::~TextureClientPool()
+{
+ mShrinkTimer->Cancel();
+ mClearTimer->Cancel();
+}
+
+#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL
+static bool TestClientPool(const char* what,
+ TextureClient* aClient,
+ TextureClientPool* aPool)
+{
+ if (!aClient || !aPool) {
+ return false;
+ }
+
+ TextureClientPool* actual = aClient->mPoolTracker;
+ bool ok = (actual == aPool);
+ if (ok) {
+ ok = (aClient->GetFormat() == aPool->GetFormat());
+ }
+
+ if (!ok) {
+ if (actual) {
+ gfxCriticalError() << "Pool error(" << what << "): "
+ << aPool << "-" << aPool->GetFormat() << ", "
+ << actual << "-" << actual->GetFormat() << ", "
+ << aClient->GetFormat();
+ MOZ_CRASH("GFX: Crashing with actual");
+ } else {
+ gfxCriticalError() << "Pool error(" << what << "): "
+ << aPool << "-" << aPool->GetFormat() << ", nullptr, "
+ << aClient->GetFormat();
+ MOZ_CRASH("GFX: Crashing without actual");
+ }
+ }
+ return ok;
+}
+#endif
+
+already_AddRefed<TextureClient>
+TextureClientPool::GetTextureClient()
+{
+ // Try to fetch a client from the pool
+ RefPtr<TextureClient> textureClient;
+
+ // We initially allocate mInitialPoolSize for our pool. If we run
+ // out of TextureClients, we allocate additional TextureClients to try and keep around
+ // mPoolUnusedSize
+ if (!mTextureClients.size()) {
+ AllocateTextureClient();
+ }
+
+ if (!mTextureClients.size()) {
+ // All our allocations failed, return nullptr
+ return nullptr;
+ }
+
+ mOutstandingClients++;
+ textureClient = mTextureClients.top();
+ mTextureClients.pop();
+#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL
+ if (textureClient) {
+ textureClient->mPoolTracker = this;
+ }
+ DebugOnly<bool> ok = TestClientPool("fetch", textureClient, this);
+ MOZ_ASSERT(ok);
+#endif
+ TCP_LOG("TexturePool %p giving %p from pool; size %u outstanding %u\n",
+ this, textureClient.get(), mTextureClients.size(), mOutstandingClients);
+
+ return textureClient.forget();
+}
+
+void
+TextureClientPool::AllocateTextureClient()
+{
+ TCP_LOG("TexturePool %p allocating TextureClient, outstanding %u\n",
+ this, mOutstandingClients);
+
+ RefPtr<TextureClient> newClient;
+ if (gfxPrefs::ForceShmemTiles()) {
+ // gfx::BackendType::NONE means use the content backend
+ newClient =
+ TextureClient::CreateForRawBufferAccess(mSurfaceAllocator,
+ mFormat, mSize,
+ gfx::BackendType::NONE,
+ mBackend,
+ mFlags, ALLOC_DEFAULT);
+ } else {
+ newClient =
+ TextureClient::CreateForDrawing(mSurfaceAllocator,
+ mFormat, mSize,
+ mBackend,
+ mMaxTextureSize,
+ BackendSelector::Content,
+ mFlags);
+ }
+
+ if (newClient) {
+ mTextureClients.push(newClient);
+ }
+}
+
+void
+TextureClientPool::ResetTimers()
+{
+ // Shrink down if we're beyond our maximum size
+ if (mShrinkTimeoutMsec &&
+ mTextureClients.size() + mTextureClientsDeferred.size() > mPoolUnusedSize) {
+ TCP_LOG("TexturePool %p scheduling a shrink-to-max-size\n", this);
+ mShrinkTimer->InitWithFuncCallback(ShrinkCallback, this, mShrinkTimeoutMsec,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+
+ // Clear pool after a period of inactivity to reduce memory consumption
+ if (mClearTimeoutMsec) {
+ TCP_LOG("TexturePool %p scheduling a clear\n", this);
+ mClearTimer->InitWithFuncCallback(ClearCallback, this, mClearTimeoutMsec,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+void
+TextureClientPool::ReturnTextureClient(TextureClient *aClient)
+{
+ if (!aClient || mDestroyed) {
+ return;
+ }
+#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL
+ DebugOnly<bool> ok = TestClientPool("return", aClient, this);
+ MOZ_ASSERT(ok);
+#endif
+ // Add the client to the pool:
+ MOZ_ASSERT(mOutstandingClients > mTextureClientsDeferred.size());
+ mOutstandingClients--;
+ mTextureClients.push(aClient);
+ TCP_LOG("TexturePool %p had client %p returned; size %u outstanding %u\n",
+ this, aClient, mTextureClients.size(), mOutstandingClients);
+
+ ResetTimers();
+}
+
+void
+TextureClientPool::ReturnTextureClientDeferred(TextureClient* aClient)
+{
+ if (!aClient || mDestroyed) {
+ return;
+ }
+ MOZ_ASSERT(aClient->GetReadLock());
+#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL
+ DebugOnly<bool> ok = TestClientPool("defer", aClient, this);
+ MOZ_ASSERT(ok);
+#endif
+ mTextureClientsDeferred.push_back(aClient);
+ TCP_LOG("TexturePool %p had client %p defer-returned, size %u outstanding %u\n",
+ this, aClient, mTextureClientsDeferred.size(), mOutstandingClients);
+
+ ResetTimers();
+}
+
+void
+TextureClientPool::ShrinkToMaximumSize()
+{
+ // We're over our desired maximum size, immediately shrink down to the
+ // maximum.
+ //
+ // We cull from the deferred TextureClients first, as we can't reuse those
+ // until they get returned.
+ uint32_t totalUnusedTextureClients = mTextureClients.size() + mTextureClientsDeferred.size();
+
+ // If we have > mInitialPoolSize outstanding, then we want to keep around
+ // mPoolUnusedSize at a maximum. If we have fewer than mInitialPoolSize
+ // outstanding, then keep around the entire initial pool size.
+ uint32_t targetUnusedClients;
+ if (mOutstandingClients > mInitialPoolSize) {
+ targetUnusedClients = mPoolUnusedSize;
+ } else {
+ targetUnusedClients = mInitialPoolSize;
+ }
+
+ TCP_LOG("TexturePool %p shrinking to maximum unused size %u; current pool size %u; total outstanding %u\n",
+ this, targetUnusedClients, totalUnusedTextureClients, mOutstandingClients);
+
+ while (totalUnusedTextureClients > targetUnusedClients) {
+ if (mTextureClientsDeferred.size()) {
+ mOutstandingClients--;
+ TCP_LOG("TexturePool %p dropped deferred client %p; %u remaining\n",
+ this, mTextureClientsDeferred.front().get(),
+ mTextureClientsDeferred.size() - 1);
+ mTextureClientsDeferred.pop_front();
+ } else {
+ TCP_LOG("TexturePool %p dropped non-deferred client %p; %u remaining\n",
+ this, mTextureClients.top().get(), mTextureClients.size() - 1);
+ mTextureClients.pop();
+ }
+ totalUnusedTextureClients--;
+ }
+}
+
+void
+TextureClientPool::ReturnDeferredClients()
+{
+ if (mTextureClientsDeferred.empty()) {
+ return;
+ }
+
+ TCP_LOG("TexturePool %p returning %u deferred clients to pool\n",
+ this, mTextureClientsDeferred.size());
+
+ ReturnUnlockedClients();
+ ShrinkToMaximumSize();
+}
+
+void
+TextureClientPool::ReturnUnlockedClients()
+{
+ for (auto it = mTextureClientsDeferred.begin(); it != mTextureClientsDeferred.end();) {
+ MOZ_ASSERT((*it)->GetReadLock()->GetReadCount() >= 1);
+ // Last count is held by the lock itself.
+ if (!(*it)->IsReadLocked()) {
+ mTextureClients.push(*it);
+ it = mTextureClientsDeferred.erase(it);
+
+ MOZ_ASSERT(mOutstandingClients > 0);
+ mOutstandingClients--;
+ } else {
+ it++;
+ }
+ }
+}
+
+void
+TextureClientPool::ReportClientLost()
+{
+ MOZ_ASSERT(mOutstandingClients > mTextureClientsDeferred.size());
+ mOutstandingClients--;
+ TCP_LOG("TexturePool %p getting report client lost; down to %u outstanding\n",
+ this, mOutstandingClients);
+}
+
+void
+TextureClientPool::Clear()
+{
+ TCP_LOG("TexturePool %p getting cleared\n", this);
+ while (!mTextureClients.empty()) {
+ TCP_LOG("TexturePool %p releasing client %p\n",
+ this, mTextureClients.top().get());
+ mTextureClients.pop();
+ }
+ while (!mTextureClientsDeferred.empty()) {
+ MOZ_ASSERT(mOutstandingClients > 0);
+ mOutstandingClients--;
+ TCP_LOG("TexturePool %p releasing deferred client %p\n",
+ this, mTextureClientsDeferred.front().get());
+ mTextureClientsDeferred.pop_front();
+ }
+}
+
+void TextureClientPool::Destroy()
+{
+ Clear();
+ mDestroyed = true;
+ mInitialPoolSize = 0;
+ mPoolUnusedSize = 0;
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp.orig
@@ -0,0 +1,1756 @@
+/* -*- Mode: C++; tab-width: 20; 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 "mozilla/Logging.h"
+
+#include "gfxFcPlatformFontList.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "gfxFontFamilyList.h"
+#include "gfxFT2Utils.h"
+#include "gfxPlatform.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/TimeStamp.h"
+#include "nsGkAtoms.h"
+#include "nsILanguageAtomService.h"
+#include "nsUnicodeProperties.h"
+#include "nsUnicodeRange.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsCharSeparatedTokenizer.h"
+
+#include "mozilla/gfx/HelpersCairo.h"
+
+#include <fontconfig/fcfreetype.h>
+
+#ifdef MOZ_WIDGET_GTK
+#include <gdk/gdk.h>
+#include "gfxPlatformGtk.h"
+#endif
+
+#ifdef MOZ_X11
+#include "mozilla/X11Util.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::unicode;
+
+#ifndef FC_POSTSCRIPT_NAME
+#define FC_POSTSCRIPT_NAME "postscriptname" /* String */
+#endif
+
+#define PRINTING_FC_PROPERTY "gfx.printing"
+
+#define LOG_FONTLIST(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), \
+ LogLevel::Debug, args)
+#define LOG_FONTLIST_ENABLED() MOZ_LOG_TEST( \
+ gfxPlatform::GetLog(eGfxLog_fontlist), \
+ LogLevel::Debug)
+#define LOG_CMAPDATA_ENABLED() MOZ_LOG_TEST( \
+ gfxPlatform::GetLog(eGfxLog_cmapdata), \
+ LogLevel::Debug)
+
+static const FcChar8*
+ToFcChar8Ptr(const char* aStr)
+{
+ return reinterpret_cast<const FcChar8*>(aStr);
+}
+
+static const char*
+ToCharPtr(const FcChar8 *aStr)
+{
+ return reinterpret_cast<const char*>(aStr);
+}
+
+FT_Library gfxFcPlatformFontList::sCairoFTLibrary = nullptr;
+
+static cairo_user_data_key_t sFcFontlistUserFontDataKey;
+
+// canonical name ==> first en name or first name if no en name
+// This is the required logic for fullname lookups as per CSS3 Fonts spec.
+static uint32_t
+FindCanonicalNameIndex(FcPattern* aFont, const char* aLangField)
+{
+ uint32_t n = 0, en = 0;
+ FcChar8* lang;
+ while (FcPatternGetString(aFont, aLangField, n, &lang) == FcResultMatch) {
+ // look for 'en' or variants, en-US, en-JP etc.
+ uint32_t len = strlen(ToCharPtr(lang));
+ bool enPrefix = (strncmp(ToCharPtr(lang), "en", 2) == 0);
+ if (enPrefix && (len == 2 || (len > 2 && aLangField[2] == '-'))) {
+ en = n;
+ break;
+ }
+ n++;
+ }
+ return en;
+}
+
+static void
+GetFaceNames(FcPattern* aFont, const nsAString& aFamilyName,
+ nsAString& aPostscriptName, nsAString& aFullname)
+{
+ // get the Postscript name
+ FcChar8* psname;
+ if (FcPatternGetString(aFont, FC_POSTSCRIPT_NAME, 0, &psname) == FcResultMatch) {
+ AppendUTF8toUTF16(ToCharPtr(psname), aPostscriptName);
+ }
+
+ // get the canonical fullname (i.e. en name or first name)
+ uint32_t en = FindCanonicalNameIndex(aFont, FC_FULLNAMELANG);
+ FcChar8* fullname;
+ if (FcPatternGetString(aFont, FC_FULLNAME, en, &fullname) == FcResultMatch) {
+ AppendUTF8toUTF16(ToCharPtr(fullname), aFullname);
+ }
+
+ // if have fullname, done
+ if (!aFullname.IsEmpty()) {
+ return;
+ }
+
+ // otherwise, set the fullname to family + style name [en] and use that
+ aFullname.Append(aFamilyName);
+
+ // figure out the en style name
+ en = FindCanonicalNameIndex(aFont, FC_STYLELANG);
+ nsAutoString style;
+ FcChar8* stylename = nullptr;
+ FcPatternGetString(aFont, FC_STYLE, en, &stylename);
+ if (stylename) {
+ AppendUTF8toUTF16(ToCharPtr(stylename), style);
+ }
+
+ if (!style.IsEmpty() && !style.EqualsLiteral("Regular")) {
+ aFullname.Append(' ');
+ aFullname.Append(style);
+ }
+}
+
+static uint16_t
+MapFcWeight(int aFcWeight)
+{
+ if (aFcWeight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2) {
+ return 100;
+ } else if (aFcWeight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2) {
+ return 200;
+ } else if (aFcWeight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2) {
+ return 300;
+ } else if (aFcWeight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2) {
+ // This includes FC_WEIGHT_BOOK
+ return 400;
+ } else if (aFcWeight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) {
+ return 500;
+ } else if (aFcWeight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) {
+ return 600;
+ } else if (aFcWeight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2) {
+ return 700;
+ } else if (aFcWeight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2) {
+ return 800;
+ } else if (aFcWeight <= FC_WEIGHT_BLACK) {
+ return 900;
+ }
+
+ // including FC_WEIGHT_EXTRABLACK
+ return 901;
+}
+
+static int16_t
+MapFcWidth(int aFcWidth)
+{
+ if (aFcWidth <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) {
+ return NS_FONT_STRETCH_ULTRA_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) {
+ return NS_FONT_STRETCH_EXTRA_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) {
+ return NS_FONT_STRETCH_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) {
+ return NS_FONT_STRETCH_SEMI_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) {
+ return NS_FONT_STRETCH_NORMAL;
+ }
+ if (aFcWidth <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) {
+ return NS_FONT_STRETCH_SEMI_EXPANDED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) {
+ return NS_FONT_STRETCH_EXPANDED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) {
+ return NS_FONT_STRETCH_EXTRA_EXPANDED;
+ }
+ return NS_FONT_STRETCH_ULTRA_EXPANDED;
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName,
+ FcPattern* aFontPattern,
+ bool aIgnoreFcCharmap)
+ : gfxFontEntry(aFaceName), mFontPattern(aFontPattern),
+ mFTFace(nullptr), mFTFaceInitialized(false),
+ mIgnoreFcCharmap(aIgnoreFcCharmap),
+ mAspect(0.0), mFontData(nullptr)
+{
+ // italic
+ int slant;
+ if (FcPatternGetInteger(aFontPattern, FC_SLANT, 0, &slant) != FcResultMatch) {
+ slant = FC_SLANT_ROMAN;
+ }
+ if (slant == FC_SLANT_OBLIQUE) {
+ mStyle = NS_FONT_STYLE_OBLIQUE;
+ } else if (slant > 0) {
+ mStyle = NS_FONT_STYLE_ITALIC;
+ }
+
+ // weight
+ int weight;
+ if (FcPatternGetInteger(aFontPattern, FC_WEIGHT, 0, &weight) != FcResultMatch) {
+ weight = FC_WEIGHT_REGULAR;
+ }
+ mWeight = MapFcWeight(weight);
+
+ // width
+ int width;
+ if (FcPatternGetInteger(aFontPattern, FC_WIDTH, 0, &width) != FcResultMatch) {
+ width = FC_WIDTH_NORMAL;
+ }
+ mStretch = MapFcWidth(width);
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t *aData,
+ FT_Face aFace)
+ : gfxFontEntry(aFaceName),
+ mFTFace(aFace), mFTFaceInitialized(true),
+ mIgnoreFcCharmap(true),
+ mAspect(0.0), mFontData(aData)
+{
+ mWeight = aWeight;
+ mStyle = aStyle;
+ mStretch = aStretch;
+ mIsDataUserFont = true;
+
+ // Use fontconfig to fill out the pattern from the FTFace.
+ // The "file" argument cannot be nullptr (in fontconfig-2.6.0 at
+ // least). The dummy file passed here is removed below.
+ //
+ // When fontconfig scans the system fonts, FcConfigGetBlanks(nullptr)
+ // is passed as the "blanks" argument, which provides that unexpectedly
+ // blank glyphs are elided. Here, however, we pass nullptr for
+ // "blanks", effectively assuming that, if the font has a blank glyph,
+ // then the author intends any associated character to be rendered
+ // blank.
+ mFontPattern = FcFreeTypeQueryFace(mFTFace, ToFcChar8Ptr(""), 0, nullptr);
+ // given that we have a FT_Face, not really sure this is possible...
+ if (!mFontPattern) {
+ mFontPattern = FcPatternCreate();
+ }
+ FcPatternDel(mFontPattern, FC_FILE);
+ FcPatternDel(mFontPattern, FC_INDEX);
+
+ // Make a new pattern and store the face in it so that cairo uses
+ // that when creating a cairo font face.
+ FcPatternAddFTFace(mFontPattern, FC_FT_FACE, mFTFace);
+
+ mUserFontData = new FTUserFontData(mFTFace, mFontData);
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName,
+ FcPattern* aFontPattern,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+ : gfxFontEntry(aFaceName), mFontPattern(aFontPattern),
+ mFTFace(nullptr), mFTFaceInitialized(false),
+ mAspect(0.0), mFontData(nullptr)
+{
+ mWeight = aWeight;
+ mStyle = aStyle;
+ mStretch = aStretch;
+ mIsLocalUserFont = true;
+
+ // The proper setting of mIgnoreFcCharmap is tricky for fonts loaded
+ // via src:local()...
+ // If the local font happens to come from the application fontset,
+ // we want to set it to true so that color/svg fonts will work even
+ // if the default glyphs are blank; but if the local font is a non-
+ // sfnt face (e.g. legacy type 1) then we need to set it to false
+ // because our cmap-reading code will fail and we depend on FT+Fc to
+ // determine the coverage.
+ // We set the flag here, but may flip it the first time TestCharacterMap
+ // is called, at which point we'll look to see whether a 'cmap' is
+ // actually present in the font.
+ mIgnoreFcCharmap = true;
+}
+
+gfxFontconfigFontEntry::~gfxFontconfigFontEntry()
+{
+}
+
+static bool
+PatternHasLang(const FcPattern *aPattern, const FcChar8 *aLang)
+{
+ FcLangSet *langset;
+
+ if (FcPatternGetLangSet(aPattern, FC_LANG, 0, &langset) != FcResultMatch) {
+ return false;
+ }
+
+ if (FcLangSetHasLang(langset, aLang) != FcLangDifferentLang) {
+ return true;
+ }
+ return false;
+}
+
+bool
+gfxFontconfigFontEntry::SupportsLangGroup(nsIAtom *aLangGroup) const
+{
+ if (!aLangGroup || aLangGroup == nsGkAtoms::Unicode) {
+ return true;
+ }
+
+ nsAutoCString fcLang;
+ gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
+ pfl->GetSampleLangForGroup(aLangGroup, fcLang);
+ if (fcLang.IsEmpty()) {
+ return true;
+ }
+
+ // is lang included in the underlying pattern?
+ return PatternHasLang(mFontPattern, ToFcChar8Ptr(fcLang.get()));
+}
+
+nsresult
+gfxFontconfigFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
+{
+ // attempt this once, if errors occur leave a blank cmap
+ if (mCharacterMap) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxCharacterMap> charmap;
+ nsresult rv;
+ bool symbolFont = false; // currently ignored
+
+ if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
+ mUVSOffset,
+ symbolFont))) {
+ rv = NS_OK;
+ } else {
+ uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
+ charmap = new gfxCharacterMap();
+ AutoTable cmapTable(this, kCMAP);
+
+ if (cmapTable) {
+ bool unicodeFont = false; // currently ignored
+ uint32_t cmapLen;
+ const uint8_t* cmapData =
+ reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable,
+ &cmapLen));
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen,
+ *charmap, mUVSOffset,
+ unicodeFont, symbolFont);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ mHasCmapTable = NS_SUCCEEDED(rv);
+ if (mHasCmapTable) {
+ gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
+ mCharacterMap = pfl->FindCharMap(charmap);
+ } else {
+ // if error occurred, initialize to null cmap
+ mCharacterMap = new gfxCharacterMap();
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d hash: %8.8x%s\n",
+ NS_ConvertUTF16toUTF8(mName).get(),
+ charmap->SizeOfIncludingThis(moz_malloc_size_of),
+ charmap->mHash, mCharacterMap == charmap ? " new" : ""));
+ if (LOG_CMAPDATA_ENABLED()) {
+ char prefix[256];
+ SprintfLiteral(prefix, "(cmapdata) name: %.220s",
+ NS_ConvertUTF16toUTF8(mName).get());
+ charmap->Dump(prefix, eGfxLog_cmapdata);
+ }
+
+ return rv;
+}
+
+static bool
+HasChar(FcPattern *aFont, FcChar32 aCh)
+{
+ FcCharSet *charset = nullptr;
+ FcPatternGetCharSet(aFont, FC_CHARSET, 0, &charset);
+ return charset && FcCharSetHasChar(charset, aCh);
+}
+
+bool
+gfxFontconfigFontEntry::TestCharacterMap(uint32_t aCh)
+{
+ // For user fonts, or for fonts bundled with the app (which might include
+ // color/svg glyphs where the default glyphs may be blank, and thus confuse
+ // fontconfig/freetype's char map checking), we instead check the cmap
+ // directly for character coverage.
+ if (mIgnoreFcCharmap) {
+ // If it does not actually have a cmap, switch our strategy to use
+ // fontconfig's charmap after all (except for data fonts, which must
+ // always have a cmap to have passed OTS validation).
+ if (!mIsDataUserFont && !HasFontTable(TRUETYPE_TAG('c','m','a','p'))) {
+ mIgnoreFcCharmap = false;
+ // ...and continue with HasChar() below.
+ } else {
+ return gfxFontEntry::TestCharacterMap(aCh);
+ }
+ }
+ // otherwise (for system fonts), use the charmap in the pattern
+ return HasChar(mFontPattern, aCh);
+}
+
+hb_blob_t*
+gfxFontconfigFontEntry::GetFontTable(uint32_t aTableTag)
+{
+ // for data fonts, read directly from the font data
+ if (mFontData) {
+ return gfxFontUtils::GetTableFromFontData(mFontData, aTableTag);
+ }
+
+ return gfxFontEntry::GetFontTable(aTableTag);
+}
+
+void
+gfxFontconfigFontEntry::MaybeReleaseFTFace()
+{
+ // don't release if either HB or Gr face still exists
+ if (mHBFace || mGrFace) {
+ return;
+ }
+ // only close out FT_Face for system fonts, not for data fonts
+ if (!mIsDataUserFont) {
+ if (mFTFace) {
+ FT_Done_Face(mFTFace);
+ mFTFace = nullptr;
+ }
+ mFTFaceInitialized = false;
+ }
+}
+
+void
+gfxFontconfigFontEntry::ForgetHBFace()
+{
+ gfxFontEntry::ForgetHBFace();
+ MaybeReleaseFTFace();
+}
+
+void
+gfxFontconfigFontEntry::ReleaseGrFace(gr_face* aFace)
+{
+ gfxFontEntry::ReleaseGrFace(aFace);
+ MaybeReleaseFTFace();
+}
+
+double
+gfxFontconfigFontEntry::GetAspect()
+{
+ if (mAspect == 0.0) {
+ // default to aspect = 0.5
+ mAspect = 0.5;
+
+ // create a font to calculate x-height / em-height
+ gfxFontStyle s;
+ s.size = 100.0; // pick large size to avoid possible hinting artifacts
+ RefPtr<gfxFont> font = FindOrMakeFont(&s, false);
+ if (font) {
+ const gfxFont::Metrics& metrics =
+ font->GetMetrics(gfxFont::eHorizontal);
+
+ // The factor of 0.1 ensures that xHeight is sane so fonts don't
+ // become huge. Strictly ">" ensures that xHeight and emHeight are
+ // not both zero.
+ if (metrics.xHeight > 0.1 * metrics.emHeight) {
+ mAspect = metrics.xHeight / metrics.emHeight;
+ }
+ }
+ }
+ return mAspect;
+}
+
+static void
+PrepareFontOptions(FcPattern* aPattern,
+ cairo_font_options_t* aFontOptions)
+{
+ NS_ASSERTION(aFontOptions, "null font options passed to PrepareFontOptions");
+
+ // xxx - taken from the gfxFontconfigFonts code, needs to be reviewed
+
+ FcBool printing;
+ if (FcPatternGetBool(aPattern, PRINTING_FC_PROPERTY, 0, &printing) !=
+ FcResultMatch) {
+ printing = FcFalse;
+ }
+
+ // Font options are set explicitly here to improve cairo's caching
+ // behavior and to record the relevant parts of the pattern for
+ // SetupCairoFont (so that the pattern can be released).
+ //
+ // Most font_options have already been set as defaults on the FcPattern
+ // with cairo_ft_font_options_substitute(), then user and system
+ // fontconfig configurations were applied. The resulting font_options
+ // have been recorded on the face during
+ // cairo_ft_font_face_create_for_pattern().
+ //
+ // None of the settings here cause this scaled_font to behave any
+ // differently from how it would behave if it were created from the same
+ // face with default font_options.
+ //
+ // We set options explicitly so that the same scaled_font will be found in
+ // the cairo_scaled_font_map when cairo loads glyphs from a context with
+ // the same font_face, font_matrix, ctm, and surface font_options.
+ //
+ // Unfortunately, _cairo_scaled_font_keys_equal doesn't know about the
+ // font_options on the cairo_ft_font_face, and doesn't consider default
+ // option values to not match any explicit values.
+ //
+ // Even after cairo_set_scaled_font is used to set font_options for the
+ // cairo context, when cairo looks for a scaled_font for the context, it
+ // will look for a font with some option values from the target surface if
+ // any values are left default on the context font_options. If this
+ // scaled_font is created with default font_options, cairo will not find
+ // it.
+ //
+ // The one option not recorded in the pattern is hint_metrics, which will
+ // affect glyph metrics. The default behaves as CAIRO_HINT_METRICS_ON.
+ // We should be considering the font_options of the surface on which this
+ // font will be used, but currently we don't have different gfxFonts for
+ // different surface font_options, so we'll create a font suitable for the
+ // Screen. Image and xlib surfaces default to CAIRO_HINT_METRICS_ON.
+ if (printing) {
+ cairo_font_options_set_hint_metrics(aFontOptions, CAIRO_HINT_METRICS_OFF);
+ } else {
+ cairo_font_options_set_hint_metrics(aFontOptions, CAIRO_HINT_METRICS_ON);
+ }
+
+ // The remaining options have been recorded on the pattern and the face.
+ // _cairo_ft_options_merge has some logic to decide which options from the
+ // scaled_font or from the cairo_ft_font_face take priority in the way the
+ // font behaves.
+ //
+ // In the majority of cases, _cairo_ft_options_merge uses the options from
+ // the cairo_ft_font_face, so sometimes it is not so important which
+ // values are set here so long as they are not defaults, but we'll set
+ // them to the exact values that we expect from the font, to be consistent
+ // and to protect against changes in cairo.
+ //
+ // In some cases, _cairo_ft_options_merge uses some options from the
+ // scaled_font's font_options rather than options on the
+ // cairo_ft_font_face (from fontconfig).
+ // https://bugs.freedesktop.org/show_bug.cgi?id=11838
+ //
+ // Surface font options were set on the pattern in
+ // cairo_ft_font_options_substitute. If fontconfig has changed the
+ // hint_style then that is what the user (or distribution) wants, so we
+ // use the setting from the FcPattern.
+ //
+ // Fallback values here mirror treatment of defaults in cairo-ft-font.c.
+ FcBool hinting = FcFalse;
+ if (FcPatternGetBool(aPattern, FC_HINTING, 0, &hinting) != FcResultMatch) {
+ hinting = FcTrue;
+ }
+
+ cairo_hint_style_t hint_style;
+ if (printing || !hinting) {
+ hint_style = CAIRO_HINT_STYLE_NONE;
+ } else {
+ int fc_hintstyle;
+ if (FcPatternGetInteger(aPattern, FC_HINT_STYLE,
+ 0, &fc_hintstyle) != FcResultMatch) {
+ fc_hintstyle = FC_HINT_FULL;
+ }
+ switch (fc_hintstyle) {
+ case FC_HINT_NONE:
+ hint_style = CAIRO_HINT_STYLE_NONE;
+ break;
+ case FC_HINT_SLIGHT:
+ hint_style = CAIRO_HINT_STYLE_SLIGHT;
+ break;
+ case FC_HINT_MEDIUM:
+ default: // This fallback mirrors _get_pattern_ft_options in cairo.
+ hint_style = CAIRO_HINT_STYLE_MEDIUM;
+ break;
+ case FC_HINT_FULL:
+ hint_style = CAIRO_HINT_STYLE_FULL;
+ break;
+ }
+ }
+ cairo_font_options_set_hint_style(aFontOptions, hint_style);
+
+ int rgba;
+ if (FcPatternGetInteger(aPattern,
+ FC_RGBA, 0, &rgba) != FcResultMatch) {
+ rgba = FC_RGBA_UNKNOWN;
+ }
+ cairo_subpixel_order_t subpixel_order = CAIRO_SUBPIXEL_ORDER_DEFAULT;
+ switch (rgba) {
+ case FC_RGBA_UNKNOWN:
+ case FC_RGBA_NONE:
+ default:
+ // There is no CAIRO_SUBPIXEL_ORDER_NONE. Subpixel antialiasing
+ // is disabled through cairo_antialias_t.
+ rgba = FC_RGBA_NONE;
+ // subpixel_order won't be used by the font as we won't use
+ // CAIRO_ANTIALIAS_SUBPIXEL, but don't leave it at default for
+ // caching reasons described above. Fall through:
+ MOZ_FALLTHROUGH;
+ case FC_RGBA_RGB:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_RGB;
+ break;
+ case FC_RGBA_BGR:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_BGR;
+ break;
+ case FC_RGBA_VRGB:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_VRGB;
+ break;
+ case FC_RGBA_VBGR:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_VBGR;
+ break;
+ }
+ cairo_font_options_set_subpixel_order(aFontOptions, subpixel_order);
+
+ FcBool fc_antialias;
+ if (FcPatternGetBool(aPattern,
+ FC_ANTIALIAS, 0, &fc_antialias) != FcResultMatch) {
+ fc_antialias = FcTrue;
+ }
+ cairo_antialias_t antialias;
+ if (!fc_antialias) {
+ antialias = CAIRO_ANTIALIAS_NONE;
+ } else if (rgba == FC_RGBA_NONE) {
+ antialias = CAIRO_ANTIALIAS_GRAY;
+ } else {
+ antialias = CAIRO_ANTIALIAS_SUBPIXEL;
+ }
+ cairo_font_options_set_antialias(aFontOptions, antialias);
+}
+
+cairo_scaled_font_t*
+gfxFontconfigFontEntry::CreateScaledFont(FcPattern* aRenderPattern,
+ gfxFloat aAdjustedSize,
+ const gfxFontStyle *aStyle,
+ bool aNeedsBold)
+{
+ if (aNeedsBold) {
+ FcPatternAddBool(aRenderPattern, FC_EMBOLDEN, FcTrue);
+ }
+
+ // synthetic oblique by skewing via the font matrix
+ bool needsOblique = IsUpright() &&
+ aStyle->style != NS_FONT_STYLE_NORMAL &&
+ aStyle->allowSyntheticStyle;
+
+ if (needsOblique) {
+ // disable embedded bitmaps (mimics behavior in 90-synthetic.conf)
+ FcPatternDel(aRenderPattern, FC_EMBEDDED_BITMAP);
+ FcPatternAddBool(aRenderPattern, FC_EMBEDDED_BITMAP, FcFalse);
+ }
+
+ cairo_font_face_t *face =
+ cairo_ft_font_face_create_for_pattern(aRenderPattern);
+
+ if (mFontData) {
+ // for data fonts, add the face/data pointer to the cairo font face
+ // so that it gets deleted whenever cairo decides
+ NS_ASSERTION(mFTFace, "FT_Face is null when setting user data");
+ NS_ASSERTION(mUserFontData, "user font data is null when setting user data");
+ cairo_font_face_set_user_data(face,
+ &sFcFontlistUserFontDataKey,
+ new FTUserFontDataRef(mUserFontData),
+ FTUserFontDataRef::Destroy);
+ }
+
+ cairo_scaled_font_t *scaledFont = nullptr;
+
+ cairo_matrix_t sizeMatrix;
+ cairo_matrix_t identityMatrix;
+
+ cairo_matrix_init_scale(&sizeMatrix, aAdjustedSize, aAdjustedSize);
+ cairo_matrix_init_identity(&identityMatrix);
+
+ if (needsOblique) {
+ const double kSkewFactor = OBLIQUE_SKEW_FACTOR;
+
+ cairo_matrix_t style;
+ cairo_matrix_init(&style,
+ 1, //xx
+ 0, //yx
+ -1 * kSkewFactor, //xy
+ 1, //yy
+ 0, //x0
+ 0); //y0
+ cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
+ }
+
+ cairo_font_options_t *fontOptions = cairo_font_options_create();
+ PrepareFontOptions(aRenderPattern, fontOptions);
+
+ scaledFont = cairo_scaled_font_create(face, &sizeMatrix,
+ &identityMatrix, fontOptions);
+ cairo_font_options_destroy(fontOptions);
+
+ NS_ASSERTION(cairo_scaled_font_status(scaledFont) == CAIRO_STATUS_SUCCESS,
+ "Failed to make scaled font");
+
+ cairo_font_face_destroy(face);
+
+ return scaledFont;
+}
+
+#ifdef MOZ_WIDGET_GTK
+// defintion included below
+static void ApplyGdkScreenFontOptions(FcPattern *aPattern);
+#endif
+
+#ifdef MOZ_X11
+static bool
+GetXftInt(Display* aDisplay, const char* aName, int* aResult)
+{
+ if (!aDisplay) {
+ return false;
+ }
+ char* value = XGetDefault(aDisplay, "Xft", aName);
+ if (!value) {
+ return false;
+ }
+ if (FcNameConstant(const_cast<FcChar8*>(ToFcChar8Ptr(value)), aResult)) {
+ return true;
+ }
+ char* end;
+ *aResult = strtol(value, &end, 0);
+ if (end != value) {
+ return true;
+ }
+ return false;
+}
+#endif
+
+static void
+PreparePattern(FcPattern* aPattern, bool aIsPrinterFont)
+{
+ FcConfigSubstitute(nullptr, aPattern, FcMatchPattern);
+
+ // This gets cairo_font_options_t for the Screen. We should have
+ // different font options for printing (no hinting) but we are not told
+ // what we are measuring for.
+ //
+ // If cairo adds support for lcd_filter, gdk will not provide the default
+ // setting for that option. We could get the default setting by creating
+ // an xlib surface once, recording its font_options, and then merging the
+ // gdk options.
+ //
+ // Using an xlib surface would also be an option to get Screen font
+ // options for non-GTK X11 toolkits, but less efficient than using GDK to
+ // pick up dynamic changes.
+ if(aIsPrinterFont) {
+ cairo_font_options_t *options = cairo_font_options_create();
+ cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_NONE);
+ cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_GRAY);
+ cairo_ft_font_options_substitute(options, aPattern);
+ cairo_font_options_destroy(options);
+ FcPatternAddBool(aPattern, PRINTING_FC_PROPERTY, FcTrue);
+ } else {
+#ifdef MOZ_WIDGET_GTK
+ ApplyGdkScreenFontOptions(aPattern);
+
+#ifdef MOZ_X11
+ FcValue value;
+ int lcdfilter;
+ if (FcPatternGet(aPattern, FC_LCD_FILTER, 0, &value) == FcResultNoMatch) {
+ GdkDisplay* dpy = gdk_display_get_default();
+ if (GDK_IS_X11_DISPLAY(dpy) &&
+ GetXftInt(GDK_DISPLAY_XDISPLAY(dpy), "lcdfilter", &lcdfilter)) {
+ FcPatternAddInteger(aPattern, FC_LCD_FILTER, lcdfilter);
+ }
+ }
+#endif // MOZ_X11
+#endif // MOZ_WIDGET_GTK
+ }
+
+ FcDefaultSubstitute(aPattern);
+}
+
+gfxFont*
+gfxFontconfigFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle,
+ bool aNeedsBold)
+{
+ nsAutoRef<FcPattern> pattern(FcPatternCreate());
+ if (!pattern) {
+ NS_WARNING("Failed to create Fontconfig pattern for font instance");
+ return nullptr;
+ }
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, aFontStyle->size);
+
+ PreparePattern(pattern, aFontStyle->printerFont);
+ nsAutoRef<FcPattern> renderPattern
+ (FcFontRenderPrepare(nullptr, pattern, mFontPattern));
+ if (!renderPattern) {
+ NS_WARNING("Failed to prepare Fontconfig pattern for font instance");
+ return nullptr;
+ }
+
+ double adjustedSize = aFontStyle->size;
+ if (aFontStyle->sizeAdjust >= 0.0) {
+ adjustedSize = aFontStyle->GetAdjustedSize(GetAspect());
+ }
+
+ cairo_scaled_font_t* scaledFont =
+ CreateScaledFont(renderPattern, adjustedSize, aFontStyle, aNeedsBold);
+ gfxFont* newFont =
+ new gfxFontconfigFont(scaledFont, renderPattern, adjustedSize,
+ this, aFontStyle, aNeedsBold);
+ cairo_scaled_font_destroy(scaledFont);
+
+ return newFont;
+}
+
+nsresult
+gfxFontconfigFontEntry::CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer)
+{
+ NS_ASSERTION(!mIsDataUserFont,
+ "data fonts should be reading tables directly from memory");
+
+ if (!mFTFaceInitialized) {
+ mFTFaceInitialized = true;
+ FcChar8 *filename;
+ if (FcPatternGetString(mFontPattern, FC_FILE, 0, &filename) != FcResultMatch) {
+ return NS_ERROR_FAILURE;
+ }
+ int index;
+ if (FcPatternGetInteger(mFontPattern, FC_INDEX, 0, &index) != FcResultMatch) {
+ index = 0; // default to 0 if not found in pattern
+ }
+ if (FT_New_Face(gfxFcPlatformFontList::GetFTLibrary(),
+ (const char*)filename, index, &mFTFace) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (!mFTFace) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ FT_ULong length = 0;
+ if (FT_Load_Sfnt_Table(mFTFace, aTableTag, 0, nullptr, &length) != 0) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (!aBuffer.SetLength(length, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (FT_Load_Sfnt_Table(mFTFace, aTableTag, 0, aBuffer.Elements(), &length) != 0) {
+ aBuffer.Clear();
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+gfxFontconfigFontFamily::FindStyleVariations(FontInfoData *aFontInfoData)
+{
+ if (mHasStyles) {
+ return;
+ }
+
+ // add font entries for each of the faces
+ uint32_t numFonts = mFontPatterns.Length();
+ NS_ASSERTION(numFonts, "font family containing no faces!!");
+ uint32_t numRegularFaces = 0;
+ for (uint32_t i = 0; i < numFonts; i++) {
+ FcPattern* face = mFontPatterns[i];
+
+ // figure out the psname/fullname and choose which to use as the facename
+ nsAutoString psname, fullname;
+ GetFaceNames(face, mName, psname, fullname);
+ const nsAutoString& faceName = !psname.IsEmpty() ? psname : fullname;
+
+ gfxFontconfigFontEntry *fontEntry =
+ new gfxFontconfigFontEntry(faceName, face, mContainsAppFonts);
+ AddFontEntry(fontEntry);
+
+ if (fontEntry->IsUpright() &&
+ fontEntry->Weight() == NS_FONT_WEIGHT_NORMAL &&
+ fontEntry->Stretch() == NS_FONT_STRETCH_NORMAL) {
+ numRegularFaces++;
+ }
+
+ if (LOG_FONTLIST_ENABLED()) {
+ LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %d stretch: %d"
+ " psname: %s fullname: %s",
+ NS_ConvertUTF16toUTF8(fontEntry->Name()).get(),
+ NS_ConvertUTF16toUTF8(Name()).get(),
+ (fontEntry->IsItalic()) ?
+ "italic" : (fontEntry->IsOblique() ? "oblique" : "normal"),
+ fontEntry->Weight(), fontEntry->Stretch(),
+ NS_ConvertUTF16toUTF8(psname).get(),
+ NS_ConvertUTF16toUTF8(fullname).get()));
+ }
+ }
+
+ // somewhat arbitrary, but define a family with two or more regular
+ // faces as a family for which intra-family fallback should be used
+ if (numRegularFaces > 1) {
+ mCheckForFallbackFaces = true;
+ }
+ mFaceNamesInitialized = true;
+ mFontPatterns.Clear();
+ SetHasStyles(true);
+}
+
+void
+gfxFontconfigFontFamily::AddFontPattern(FcPattern* aFontPattern)
+{
+ NS_ASSERTION(!mHasStyles,
+ "font patterns must not be added to already enumerated families");
+
+ nsCountedRef<FcPattern> pattern(aFontPattern);
+ mFontPatterns.AppendElement(pattern);
+}
+
+gfxFontconfigFont::gfxFontconfigFont(cairo_scaled_font_t *aScaledFont,
+ FcPattern *aPattern,
+ gfxFloat aAdjustedSize,
+ gfxFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle,
+ bool aNeedsBold) :
+ gfxFontconfigFontBase(aScaledFont, aPattern, aFontEntry, aFontStyle)
+{
+ mAdjustedSize = aAdjustedSize;
+}
+
+gfxFontconfigFont::~gfxFontconfigFont()
+{
+}
+
+gfxFcPlatformFontList::gfxFcPlatformFontList()
+ : mLocalNames(64)
+ , mGenericMappings(32)
+ , mFcSubstituteCache(64)
+ , mLastConfig(nullptr)
+ , mAlwaysUseFontconfigGenerics(true)
+{
+ // if the rescan interval is set, start the timer
+ int rescanInterval = FcConfigGetRescanInterval(nullptr);
+ if (rescanInterval) {
+ mLastConfig = FcConfigGetCurrent();
+ mCheckFontUpdatesTimer = new mozilla::timer;
+ if (mCheckFontUpdatesTimer) {
+ mCheckFontUpdatesTimer->
+ InitWithFuncCallback(CheckFontUpdates, this,
+ (rescanInterval + 1) * 1000,
+ nsITimer::TYPE_REPEATING_SLACK);
+ } else {
+ NS_WARNING("Failure to create font updates timer");
+ }
+ }
+
+#ifdef MOZ_BUNDLED_FONTS
+ mBundledFontsInitialized = false;
+#endif
+}
+
+gfxFcPlatformFontList::~gfxFcPlatformFontList()
+{
+ if (mCheckFontUpdatesTimer) {
+ mCheckFontUpdatesTimer->Cancel();
+ mCheckFontUpdatesTimer = nullptr;
+ }
+}
+
+void
+gfxFcPlatformFontList::AddFontSetFamilies(FcFontSet* aFontSet, bool aAppFonts)
+{
+ // This iterates over the fonts in a font set and adds in gfxFontFamily
+ // objects for each family. The patterns for individual fonts are not
+ // copied here. When a family is actually used, the fonts in the family
+ // are enumerated and the patterns copied. Note that we're explicitly
+ // excluding non-scalable fonts such as X11 bitmap fonts, which
+ // Chrome Skia/Webkit code does also.
+
+ if (!aFontSet) {
+ NS_WARNING("AddFontSetFamilies called with a null font set.");
+ return;
+ }
+
+ FcChar8* lastFamilyName = (FcChar8*)"";
+ RefPtr<gfxFontconfigFontFamily> fontFamily;
+ nsAutoString familyName;
+ for (int f = 0; f < aFontSet->nfont; f++) {
+ FcPattern* font = aFontSet->fonts[f];
+
+ // not scalable? skip...
+ FcBool scalable;
+ if (FcPatternGetBool(font, FC_SCALABLE, 0, &scalable) != FcResultMatch ||
+ !scalable) {
+ continue;
+ }
+
+ // get canonical name
+ uint32_t cIndex = FindCanonicalNameIndex(font, FC_FAMILYLANG);
+ FcChar8* canonical = nullptr;
+ FcPatternGetString(font, FC_FAMILY, cIndex, &canonical);
+ if (!canonical) {
+ continue;
+ }
+
+ // same as the last one? no need to add a new family, skip
+ if (FcStrCmp(canonical, lastFamilyName) != 0) {
+ lastFamilyName = canonical;
+
+ // add new family if one doesn't already exist
+ familyName.Truncate();
+ AppendUTF8toUTF16(ToCharPtr(canonical), familyName);
+ nsAutoString keyName(familyName);
+ ToLowerCase(keyName);
+
+ fontFamily = static_cast<gfxFontconfigFontFamily*>
+ (mFontFamilies.GetWeak(keyName));
+ if (!fontFamily) {
+ fontFamily = new gfxFontconfigFontFamily(familyName);
+ mFontFamilies.Put(keyName, fontFamily);
+ }
+ // Record if the family contains fonts from the app font set
+ // (in which case we won't rely on fontconfig's charmap, due to
+ // bug 1276594).
+ if (aAppFonts) {
+ fontFamily->SetFamilyContainsAppFonts(true);
+ }
+
+ // Add pointers to other localized family names. Most fonts
+ // only have a single name, so the first call to GetString
+ // will usually not match
+ FcChar8* otherName;
+ int n = (cIndex == 0 ? 1 : 0);
+ while (FcPatternGetString(font, FC_FAMILY, n, &otherName) == FcResultMatch) {
+ NS_ConvertUTF8toUTF16 otherFamilyName(ToCharPtr(otherName));
+ AddOtherFamilyName(fontFamily, otherFamilyName);
+ n++;
+ if (n == int(cIndex)) {
+ n++; // skip over canonical name
+ }
+ }
+ }
+
+ NS_ASSERTION(fontFamily, "font must belong to a font family");
+ fontFamily->AddFontPattern(font);
+
+ // map the psname, fullname ==> font family for local font lookups
+ nsAutoString psname, fullname;
+ GetFaceNames(font, familyName, psname, fullname);
+ if (!psname.IsEmpty()) {
+ ToLowerCase(psname);
+ mLocalNames.Put(psname, font);
+ }
+ if (!fullname.IsEmpty()) {
+ ToLowerCase(fullname);
+ mLocalNames.Put(fullname, font);
+ }
+ }
+}
+
+nsresult
+gfxFcPlatformFontList::InitFontListForPlatform()
+{
+ mLastConfig = FcConfigGetCurrent();
+
+ mLocalNames.Clear();
+ mFcSubstituteCache.Clear();
+
+ // iterate over available fonts
+ FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem);
+ AddFontSetFamilies(systemFonts, /* aAppFonts = */ false);
+ mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+
+#ifdef MOZ_BUNDLED_FONTS
+ ActivateBundledFonts();
+ FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication);
+ AddFontSetFamilies(appFonts, /* aAppFonts = */ true);
+#endif
+
+ mOtherFamilyNamesInitialized = true;
+
+ return NS_OK;
+}
+
+// For displaying the fontlist in UI, use explicit call to FcFontList. Using
+// FcFontList results in the list containing the localized names as dictated
+// by system defaults.
+static void
+GetSystemFontList(nsTArray<nsString>& aListOfFonts, nsIAtom *aLangGroup)
+{
+ aListOfFonts.Clear();
+
+ nsAutoRef<FcPattern> pat(FcPatternCreate());
+ if (!pat) {
+ return;
+ }
+
+ nsAutoRef<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr));
+ if (!os) {
+ return;
+ }
+
+ // add the lang to the pattern
+ nsAutoCString fcLang;
+ gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
+ pfl->GetSampleLangForGroup(aLangGroup, fcLang);
+ if (!fcLang.IsEmpty()) {
+ FcPatternAddString(pat, FC_LANG, ToFcChar8Ptr(fcLang.get()));
+ }
+
+ // ignore size-specific fonts
+ FcPatternAddBool(pat, FC_SCALABLE, FcTrue);
+
+ nsAutoRef<FcFontSet> fs(FcFontList(nullptr, pat, os));
+ if (!fs) {
+ return;
+ }
+
+ for (int i = 0; i < fs->nfont; i++) {
+ char *family;
+
+ if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0,
+ (FcChar8 **) &family) != FcResultMatch)
+ {
+ continue;
+ }
+
+ // Remove duplicates...
+ nsAutoString strFamily;
+ AppendUTF8toUTF16(family, strFamily);
+ if (aListOfFonts.Contains(strFamily)) {
+ continue;
+ }
+
+ aListOfFonts.AppendElement(strFamily);
+ }
+
+ aListOfFonts.Sort();
+}
+
+void
+gfxFcPlatformFontList::GetFontList(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts)
+{
+ // Get the list of font family names using fontconfig
+ GetSystemFontList(aListOfFonts, aLangGroup);
+
+ // Under Linux, the generics "serif", "sans-serif" and "monospace"
+ // are included in the pref fontlist. These map to whatever fontconfig
+ // decides they should be for a given language, rather than one of the
+ // fonts listed in the prefs font lists (e.g. font.name.*, font.name-list.*)
+ bool serif = false, sansSerif = false, monospace = false;
+ if (aGenericFamily.IsEmpty())
+ serif = sansSerif = monospace = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("serif"))
+ serif = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif"))
+ sansSerif = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("monospace"))
+ monospace = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") ||
+ aGenericFamily.LowerCaseEqualsLiteral("fantasy"))
+ serif = sansSerif = true;
+ else
+ NS_NOTREACHED("unexpected CSS generic font family");
+
+ // The first in the list becomes the default in
+ // FontBuilder.readFontSelection() if the preference-selected font is not
+ // available, so put system configured defaults first.
+ if (monospace)
+ aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("monospace"));
+ if (sansSerif)
+ aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("sans-serif"));
+ if (serif)
+ aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("serif"));
+}
+
+gfxFontFamily*
+gfxFcPlatformFontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle)
+{
+ // Get the default font by using a fake name to retrieve the first
+ // scalable font that fontconfig suggests for the given language.
+ PrefFontList* prefFonts =
+ FindGenericFamilies(NS_LITERAL_STRING("-moz-default"), aStyle->language);
+ NS_ASSERTION(prefFonts, "null list of generic fonts");
+ if (prefFonts && !prefFonts->IsEmpty()) {
+ return (*prefFonts)[0];
+ }
+ return nullptr;
+}
+
+gfxFontEntry*
+gfxFcPlatformFontList::LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ nsAutoString keyName(aFontName);
+ ToLowerCase(keyName);
+
+ // if name is not in the global list, done
+ FcPattern* fontPattern = mLocalNames.Get(keyName);
+ if (!fontPattern) {
+ return nullptr;
+ }
+
+ return new gfxFontconfigFontEntry(aFontName, fontPattern,
+ aWeight, aStretch, aStyle);
+}
+
+gfxFontEntry*
+gfxFcPlatformFontList::MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength)
+{
+ FT_Face face;
+ FT_Error error =
+ FT_New_Memory_Face(gfxFcPlatformFontList::GetFTLibrary(),
+ aFontData, aLength, 0, &face);
+ if (error != FT_Err_Ok) {
+ NS_Free((void*)aFontData);
+ return nullptr;
+ }
+ if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE)) {
+ FT_Done_Face(face);
+ NS_Free((void*)aFontData);
+ return nullptr;
+ }
+
+ return new gfxFontconfigFontEntry(aFontName, aWeight, aStretch,
+ aStyle, aFontData, face);
+}
+
+bool
+gfxFcPlatformFontList::FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle,
+ gfxFloat aDevToCssSize)
+{
+ nsAutoString familyName(aFamily);
+ ToLowerCase(familyName);
+ nsIAtom* language = (aStyle ? aStyle->language.get() : nullptr);
+
+ // deprecated generic names are explicitly converted to standard generics
+ bool isDeprecatedGeneric = false;
+ if (familyName.EqualsLiteral("sans") ||
+ familyName.EqualsLiteral("sans serif")) {
+ familyName.AssignLiteral("sans-serif");
+ isDeprecatedGeneric = true;
+ } else if (familyName.EqualsLiteral("mono")) {
+ familyName.AssignLiteral("monospace");
+ isDeprecatedGeneric = true;
+ }
+
+ // fontconfig generics? use fontconfig to determine the family for lang
+ if (isDeprecatedGeneric ||
+ mozilla::FontFamilyName::Convert(familyName).IsGeneric()) {
+ PrefFontList* prefFonts = FindGenericFamilies(familyName, language);
+ if (prefFonts && !prefFonts->IsEmpty()) {
+ aOutput->AppendElements(*prefFonts);
+ return true;
+ }
+ return false;
+ }
+
+ // fontconfig allows conditional substitutions in such a way that it's
+ // difficult to distinguish an explicit substitution from other suggested
+ // choices. To sniff out explicit substitutions, compare the substitutions
+ // for "font, -moz-sentinel" to "-moz-sentinel" to sniff out the
+ // substitutions
+ //
+ // Example:
+ //
+ // serif ==> DejaVu Serif, ...
+ // Helvetica, serif ==> Helvetica, TeX Gyre Heros, Nimbus Sans L, DejaVu Serif
+ //
+ // In this case fontconfig is including Tex Gyre Heros and
+ // Nimbus Sans L as alternatives for Helvetica.
+
+ // Because the FcConfigSubstitute call is quite expensive, we cache the
+ // actual font families found via this process. So check the cache first:
+ NS_ConvertUTF16toUTF8 familyToFind(familyName);
+ AutoTArray<gfxFontFamily*,10> cachedFamilies;
+ if (mFcSubstituteCache.Get(familyToFind, &cachedFamilies)) {
+ if (cachedFamilies.IsEmpty()) {
+ return false;
+ }
+ aOutput->AppendElements(cachedFamilies);
+ return true;
+ }
+
+ // It wasn't in the cache, so we need to ask fontconfig...
+ const FcChar8* kSentinelName = ToFcChar8Ptr("-moz-sentinel");
+ FcChar8* sentinelFirstFamily = nullptr;
+ nsAutoRef<FcPattern> sentinelSubst(FcPatternCreate());
+ FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName);
+ FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern);
+ FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sentinelFirstFamily);
+
+ // substitutions for font, -moz-sentinel pattern
+ nsAutoRef<FcPattern> fontWithSentinel(FcPatternCreate());
+ FcPatternAddString(fontWithSentinel, FC_FAMILY,
+ ToFcChar8Ptr(familyToFind.get()));
+ FcPatternAddString(fontWithSentinel, FC_FAMILY, kSentinelName);
+ FcConfigSubstitute(nullptr, fontWithSentinel, FcMatchPattern);
+
+ // Add all font family matches until reaching the sentinel.
+ FcChar8* substName = nullptr;
+ for (int i = 0;
+ FcPatternGetString(fontWithSentinel, FC_FAMILY,
+ i, &substName) == FcResultMatch;
+ i++)
+ {
+ NS_ConvertUTF8toUTF16 subst(ToCharPtr(substName));
+ if (sentinelFirstFamily &&
+ FcStrCmp(substName, sentinelFirstFamily) == 0) {
+ break;
+ }
+ gfxPlatformFontList::FindAndAddFamilies(subst, &cachedFamilies);
+ }
+
+ // Cache the resulting list, so we don't have to do this again.
+ mFcSubstituteCache.Put(familyToFind, cachedFamilies);
+
+ if (cachedFamilies.IsEmpty()) {
+ return false;
+ }
+ aOutput->AppendElements(cachedFamilies);
+ return true;
+}
+
+bool
+gfxFcPlatformFontList::GetStandardFamilyName(const nsAString& aFontName,
+ nsAString& aFamilyName)
+{
+ aFamilyName.Truncate();
+
+ // The fontconfig list of fonts includes generic family names in the
+ // font list. For these, just use the generic name.
+ if (aFontName.EqualsLiteral("serif") ||
+ aFontName.EqualsLiteral("sans-serif") ||
+ aFontName.EqualsLiteral("monospace")) {
+ aFamilyName.Assign(aFontName);
+ return true;
+ }
+
+ nsAutoRef<FcPattern> pat(FcPatternCreate());
+ if (!pat) {
+ return true;
+ }
+
+ nsAutoRef<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr));
+ if (!os) {
+ return true;
+ }
+
+ // ignore size-specific fonts
+ FcPatternAddBool(pat, FC_SCALABLE, FcTrue);
+
+ // add the family name to the pattern
+ NS_ConvertUTF16toUTF8 familyName(aFontName);
+ FcPatternAddString(pat, FC_FAMILY, ToFcChar8Ptr(familyName.get()));
+
+ nsAutoRef<FcFontSet> givenFS(FcFontList(nullptr, pat, os));
+ if (!givenFS) {
+ return true;
+ }
+
+ // See if there is a font face with first family equal to the given family
+ // (needs to be in sync with names coming from GetFontList())
+ nsTArray<nsCString> candidates;
+ for (int i = 0; i < givenFS->nfont; i++) {
+ char* firstFamily;
+
+ if (FcPatternGetString(givenFS->fonts[i], FC_FAMILY, 0,
+ (FcChar8 **) &firstFamily) != FcResultMatch)
+ {
+ continue;
+ }
+
+ nsDependentCString first(firstFamily);
+ if (!candidates.Contains(first)) {
+ candidates.AppendElement(first);
+
+ if (familyName.Equals(first)) {
+ aFamilyName.Assign(aFontName);
+ return true;
+ }
+ }
+ }
+
+ // Because fontconfig conflates different family name types, need to
+ // double check that the candidate name is not simply a different
+ // name type. For example, if a font with nameID=16 "Minion Pro" and
+ // nameID=21 "Minion Pro Caption" exists, calling FcFontList with
+ // family="Minion Pro" will return a set of patterns some of which
+ // will have a first family of "Minion Pro Caption". Ignore these
+ // patterns and use the first candidate that maps to a font set with
+ // the same number of faces and an identical set of patterns.
+ for (uint32_t j = 0; j < candidates.Length(); ++j) {
+ FcPatternDel(pat, FC_FAMILY);
+ FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)candidates[j].get());
+
+ nsAutoRef<FcFontSet> candidateFS(FcFontList(nullptr, pat, os));
+ if (!candidateFS) {
+ return true;
+ }
+
+ if (candidateFS->nfont != givenFS->nfont) {
+ continue;
+ }
+
+ bool equal = true;
+ for (int i = 0; i < givenFS->nfont; ++i) {
+ if (!FcPatternEqual(candidateFS->fonts[i], givenFS->fonts[i])) {
+ equal = false;
+ break;
+ }
+ }
+ if (equal) {
+ AppendUTF8toUTF16(candidates[j], aFamilyName);
+ return true;
+ }
+ }
+
+ // didn't find localized name, leave family name blank
+ return true;
+}
+
+static const char kFontNamePrefix[] = "font.name.";
+
+void
+gfxFcPlatformFontList::AddGenericFonts(mozilla::FontFamilyType aGenericType,
+ nsIAtom* aLanguage,
+ nsTArray<gfxFontFamily*>& aFamilyList)
+{
+ bool usePrefFontList = false;
+
+ // treat -moz-fixed as monospace
+ if (aGenericType == eFamily_moz_fixed) {
+ aGenericType = eFamily_monospace;
+ }
+
+ const char* generic = GetGenericName(aGenericType);
+ NS_ASSERTION(generic, "weird generic font type");
+ if (!generic) {
+ return;
+ }
+
+ // By default, most font prefs on Linux map to "use fontconfig"
+ // keywords. So only need to explicitly lookup font pref if
+ // non-default settings exist
+ NS_ConvertASCIItoUTF16 genericToLookup(generic);
+ if ((!mAlwaysUseFontconfigGenerics && aLanguage) ||
+ aLanguage == nsGkAtoms::x_math) {
+ nsIAtom* langGroup = GetLangGroup(aLanguage);
+ nsAutoCString langGroupStr;
+ if (langGroup) {
+ langGroup->ToUTF8String(langGroupStr);
+ }
+ nsAutoCString prefFontName(kFontNamePrefix);
+ prefFontName.Append(generic);
+ prefFontName.Append('.');
+ prefFontName.Append(langGroupStr);
+ nsAdoptingString fontlistValue = Preferences::GetString(prefFontName.get());
+ if (fontlistValue) {
+ if (!fontlistValue.EqualsLiteral("serif") &&
+ !fontlistValue.EqualsLiteral("sans-serif") &&
+ !fontlistValue.EqualsLiteral("monospace")) {
+ usePrefFontList = true;
+ } else {
+ // serif, sans-serif or monospace was specified
+ genericToLookup.Assign(fontlistValue);
+ }
+ }
+ }
+
+ // when pref fonts exist, use standard pref font lookup
+ if (usePrefFontList) {
+ return gfxPlatformFontList::AddGenericFonts(aGenericType,
+ aLanguage,
+ aFamilyList);
+ }
+
+ PrefFontList* prefFonts = FindGenericFamilies(genericToLookup, aLanguage);
+ NS_ASSERTION(prefFonts, "null generic font list");
+ aFamilyList.AppendElements(*prefFonts);
+}
+
+void
+gfxFcPlatformFontList::ClearLangGroupPrefFonts()
+{
+ ClearGenericMappings();
+ gfxPlatformFontList::ClearLangGroupPrefFonts();
+ mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+}
+
+/* static */ FT_Library
+gfxFcPlatformFontList::GetFTLibrary()
+{
+ if (!sCairoFTLibrary) {
+ // Use cairo's FT_Library so that cairo takes care of shutdown of the
+ // FT_Library after it has destroyed its font_faces, and FT_Done_Face
+ // has been called on each FT_Face, at least until this bug is fixed:
+ // https://bugs.freedesktop.org/show_bug.cgi?id=18857
+ //
+ // Cairo keeps it's own FT_Library object for creating FT_Face
+ // instances, so use that. There's no simple API for accessing this
+ // so use the hacky method below of making a font and extracting
+ // the library pointer from that.
+
+ bool needsBold;
+ gfxFontStyle style;
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ gfxFontFamily* family = pfl->GetDefaultFont(&style);
+ NS_ASSERTION(family, "couldn't find a default font family");
+ gfxFontEntry* fe = family->FindFontForStyle(style, needsBold);
+ if (!fe) {
+ return nullptr;
+ }
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&style, false);
+ if (!font) {
+ return nullptr;
+ }
+
+ gfxFT2FontBase* ft2Font = reinterpret_cast<gfxFT2FontBase*>(font.get());
+ gfxFT2LockedFace face(ft2Font);
+ if (!face.get()) {
+ return nullptr;
+ }
+
+ sCairoFTLibrary = face.get()->glyph->library;
+ }
+
+ return sCairoFTLibrary;
+}
+
+gfxPlatformFontList::PrefFontList*
+gfxFcPlatformFontList::FindGenericFamilies(const nsAString& aGeneric,
+ nsIAtom* aLanguage)
+{
+ // set up name
+ NS_ConvertUTF16toUTF8 generic(aGeneric);
+
+ nsAutoCString fcLang;
+ GetSampleLangForGroup(aLanguage, fcLang);
+ ToLowerCase(fcLang);
+
+ nsAutoCString genericLang(generic);
+ if (fcLang.Length() > 0) {
+ genericLang.Append('-');
+ }
+ genericLang.Append(fcLang);
+
+ // try to get the family from the cache
+ PrefFontList* prefFonts = mGenericMappings.Get(genericLang);
+ if (prefFonts) {
+ return prefFonts;
+ }
+
+ // if not found, ask fontconfig to pick the appropriate font
+ nsAutoRef<FcPattern> genericPattern(FcPatternCreate());
+ FcPatternAddString(genericPattern, FC_FAMILY,
+ ToFcChar8Ptr(generic.get()));
+
+ // -- prefer scalable fonts
+ FcPatternAddBool(genericPattern, FC_SCALABLE, FcTrue);
+
+ // -- add the lang to the pattern
+ if (!fcLang.IsEmpty()) {
+ FcPatternAddString(genericPattern, FC_LANG,
+ ToFcChar8Ptr(fcLang.get()));
+ }
+
+ // -- perform substitutions
+ FcConfigSubstitute(nullptr, genericPattern, FcMatchPattern);
+ FcDefaultSubstitute(genericPattern);
+
+ // -- sort to get the closest matches
+ FcResult result;
+ nsAutoRef<FcFontSet> faces(FcFontSort(nullptr, genericPattern, FcFalse,
+ nullptr, &result));
+
+ if (!faces) {
+ return nullptr;
+ }
+
+ // -- select the fonts to be used for the generic
+ prefFonts = new PrefFontList; // can be empty but in practice won't happen
+ uint32_t limit = gfxPlatformGtk::GetPlatform()->MaxGenericSubstitions();
+ bool foundFontWithLang = false;
+ for (int i = 0; i < faces->nfont; i++) {
+ FcPattern* font = faces->fonts[i];
+ FcChar8* mappedGeneric = nullptr;
+
+ // not scalable? skip...
+ FcBool scalable;
+ if (FcPatternGetBool(font, FC_SCALABLE, 0, &scalable) != FcResultMatch ||
+ !scalable) {
+ continue;
+ }
+
+ FcPatternGetString(font, FC_FAMILY, 0, &mappedGeneric);
+ if (mappedGeneric) {
+ NS_ConvertUTF8toUTF16 mappedGenericName(ToCharPtr(mappedGeneric));
+ AutoTArray<gfxFontFamily*,1> genericFamilies;
+ if (gfxPlatformFontList::FindAndAddFamilies(mappedGenericName,
+ &genericFamilies)) {
+ MOZ_ASSERT(genericFamilies.Length() == 1,
+ "expected a single family");
+ if (!prefFonts->Contains(genericFamilies[0])) {
+ prefFonts->AppendElement(genericFamilies[0]);
+ bool foundLang =
+ !fcLang.IsEmpty() &&
+ PatternHasLang(font, ToFcChar8Ptr(fcLang.get()));
+ foundFontWithLang = foundFontWithLang || foundLang;
+ // check to see if the list is full
+ if (prefFonts->Length() >= limit) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // if no font in the list matches the lang, trim all but the first one
+ if (!prefFonts->IsEmpty() && !foundFontWithLang) {
+ prefFonts->TruncateLength(1);
+ }
+
+ mGenericMappings.Put(genericLang, prefFonts);
+ return prefFonts;
+}
+
+bool
+gfxFcPlatformFontList::PrefFontListsUseOnlyGenerics()
+{
+ bool prefFontsUseOnlyGenerics = true;
+ uint32_t count;
+ char** names;
+ nsresult rv = Preferences::GetRootBranch()->
+ GetChildList(kFontNamePrefix, &count, &names);
+ if (NS_SUCCEEDED(rv) && count) {
+ for (size_t i = 0; i < count; i++) {
+ // Check whether all font.name prefs map to generic keywords
+ // and that the pref name and keyword match.
+ // Ex: font.name.serif.ar ==> "serif" (ok)
+ // Ex: font.name.serif.ar ==> "monospace" (return false)
+ // Ex: font.name.serif.ar ==> "DejaVu Serif" (return false)
+
+ nsDependentCString prefName(names[i] +
+ ArrayLength(kFontNamePrefix) - 1);
+ nsCCharSeparatedTokenizer tokenizer(prefName, '.');
+ const nsDependentCSubstring& generic = tokenizer.nextToken();
+ const nsDependentCSubstring& langGroup = tokenizer.nextToken();
+ nsAdoptingCString fontPrefValue = Preferences::GetCString(names[i]);
+
+ if (!langGroup.EqualsLiteral("x-math") &&
+ !generic.Equals(fontPrefValue)) {
+ prefFontsUseOnlyGenerics = false;
+ break;
+ }
+ }
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, names);
+ }
+ return prefFontsUseOnlyGenerics;
+}
+
+/* static */ void
+gfxFcPlatformFontList::CheckFontUpdates(nsITimer *aTimer, void *aThis)
+{
+ // check for font updates
+ FcInitBringUptoDate();
+
+ // update fontlist if current config changed
+ gfxFcPlatformFontList *pfl = static_cast<gfxFcPlatformFontList*>(aThis);
+ FcConfig* current = FcConfigGetCurrent();
+ if (current != pfl->GetLastConfig()) {
+ pfl->UpdateFontList();
+ pfl->ForceGlobalReflow();
+ }
+}
+
+#ifdef MOZ_BUNDLED_FONTS
+void
+gfxFcPlatformFontList::ActivateBundledFonts()
+{
+ if (!mBundledFontsInitialized) {
+ mBundledFontsInitialized = true;
+ nsCOMPtr<nsIFile> localDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ if (NS_FAILED(localDir->Append(NS_LITERAL_STRING("fonts")))) {
+ return;
+ }
+ bool isDir;
+ if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
+ return;
+ }
+ if (NS_FAILED(localDir->GetNativePath(mBundledFontsPath))) {
+ return;
+ }
+ }
+ if (!mBundledFontsPath.IsEmpty()) {
+ FcConfigAppFontAddDir(nullptr, ToFcChar8Ptr(mBundledFontsPath.get()));
+ }
+}
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+/***************************************************************************
+ *
+ * This function must be last in the file because it uses the system cairo
+ * library. Above this point the cairo library used is the tree cairo if
+ * MOZ_TREE_CAIRO.
+ */
+
+#if MOZ_TREE_CAIRO
+// Tree cairo symbols have different names. Disable their activation through
+// preprocessor macros.
+#undef cairo_ft_font_options_substitute
+
+// The system cairo functions are not declared because the include paths cause
+// the gdk headers to pick up the tree cairo.h.
+extern "C" {
+NS_VISIBILITY_DEFAULT void
+cairo_ft_font_options_substitute (const cairo_font_options_t *options,
+ FcPattern *pattern);
+}
+#endif
+
+static void
+ApplyGdkScreenFontOptions(FcPattern *aPattern)
+{
+ const cairo_font_options_t *options =
+ gdk_screen_get_font_options(gdk_screen_get_default());
+
+ cairo_ft_font_options_substitute(options, aPattern);
+}
+
+#endif // MOZ_WIDGET_GTK
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxFont.cpp.orig
@@ -0,0 +1,4008 @@
+/* -*- Mode: C++; tab-width: 20; 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 "gfxFont.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/SVGContextPaint.h"
+
+#include "mozilla/Logging.h"
+
+#include "nsITimer.h"
+
+#include "gfxGlyphExtents.h"
+#include "gfxPlatform.h"
+#include "gfxTextRun.h"
+#include "nsGkAtoms.h"
+
+#include "gfxTypes.h"
+#include "gfxContext.h"
+#include "gfxFontMissingGlyphs.h"
+#include "gfxGraphiteShaper.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxUserFontSet.h"
+#include "nsIUGenCategory.h"
+#include "nsSpecialCasingData.h"
+#include "nsTextRunTransformations.h"
+#include "nsUnicodeProperties.h"
+#include "nsStyleConsts.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "gfxMathTable.h"
+#include "gfxSVGGlyphs.h"
+#include "gfx2DGlue.h"
+
+#include "GreekCasing.h"
+
+#include "cairo.h"
+
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+
+#include <algorithm>
+#include <limits>
+#include <cmath>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+using mozilla::services::GetObserverService;
+
+gfxFontCache *gfxFontCache::gGlobalCache = nullptr;
+
+#ifdef DEBUG_roc
+#define DEBUG_TEXT_RUN_STORAGE_METRICS
+#endif
+
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+uint32_t gTextRunStorageHighWaterMark = 0;
+uint32_t gTextRunStorage = 0;
+uint32_t gFontCount = 0;
+uint32_t gGlyphExtentsCount = 0;
+uint32_t gGlyphExtentsWidthsTotalSize = 0;
+uint32_t gGlyphExtentsSetupEagerSimple = 0;
+uint32_t gGlyphExtentsSetupEagerTight = 0;
+uint32_t gGlyphExtentsSetupLazyTight = 0;
+uint32_t gGlyphExtentsSetupFallBackToTight = 0;
+#endif
+
+#define LOG_FONTINIT(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), \
+ LogLevel::Debug, args)
+#define LOG_FONTINIT_ENABLED() MOZ_LOG_TEST( \
+ gfxPlatform::GetLog(eGfxLog_fontinit), \
+ LogLevel::Debug)
+
+
+/*
+ * gfxFontCache - global cache of gfxFont instances.
+ * Expires unused fonts after a short interval;
+ * notifies fonts to age their cached shaped-word records;
+ * observes memory-pressure notification and tells fonts to clear their
+ * shaped-word caches to free up memory.
+ */
+
+MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf)
+
+NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter)
+
+NS_IMETHODIMP
+gfxFontCache::MemoryReporter::CollectReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize)
+{
+ FontCacheSizes sizes;
+
+ gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf,
+ &sizes);
+
+ MOZ_COLLECT_REPORT(
+ "explicit/gfx/font-cache", KIND_HEAP, UNITS_BYTES,
+ sizes.mFontInstances,
+ "Memory used for active font instances.");
+
+ MOZ_COLLECT_REPORT(
+ "explicit/gfx/font-shaped-words", KIND_HEAP, UNITS_BYTES,
+ sizes.mShapedWords,
+ "Memory used to cache shaped glyph data.");
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver)
+
+NS_IMETHODIMP
+gfxFontCache::Observer::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *someData)
+{
+ if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
+ gfxFontCache *fontCache = gfxFontCache::GetCache();
+ if (fontCache) {
+ fontCache->FlushShapedWordCaches();
+ }
+ } else {
+ NS_NOTREACHED("unexpected notification topic");
+ }
+ return NS_OK;
+}
+
+nsresult
+gfxFontCache::Init()
+{
+ NS_ASSERTION(!gGlobalCache, "Where did this come from?");
+ gGlobalCache = new gfxFontCache();
+ if (!gGlobalCache) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ RegisterStrongMemoryReporter(new MemoryReporter());
+ return NS_OK;
+}
+
+void
+gfxFontCache::Shutdown()
+{
+ delete gGlobalCache;
+ gGlobalCache = nullptr;
+
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark);
+ printf("Total number of fonts=%d\n", gFontCount);
+ printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount,
+ int(gGlyphExtentsCount*sizeof(gfxGlyphExtents)));
+ printf("Total glyph extents width-storage size allocated=%d\n", gGlyphExtentsWidthsTotalSize);
+ printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple);
+ printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight);
+ printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight);
+ printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight);
+#endif
+}
+
+gfxFontCache::gfxFontCache()
+ : nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000,
+ "gfxFontCache")
+{
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ obs->AddObserver(new Observer, "memory-pressure", false);
+ }
+
+#ifndef RELEASE_OR_BETA
+ // Currently disabled for release builds, due to unexplained crashes
+ // during expiration; see bug 717175 & 894798.
+ mWordCacheExpirationTimer = new mozilla::Timer;
+ if (mWordCacheExpirationTimer) {
+ mWordCacheExpirationTimer->
+ InitWithFuncCallback(WordCacheExpirationTimerCallback, this,
+ SHAPED_WORD_TIMEOUT_SECONDS * 1000,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+#endif
+}
+
+gfxFontCache::~gfxFontCache()
+{
+ // Ensure the user font cache releases its references to font entries,
+ // so they aren't kept alive after the font instances and font-list
+ // have been shut down.
+ gfxUserFontSet::UserFontCache::Shutdown();
+
+ if (mWordCacheExpirationTimer) {
+ mWordCacheExpirationTimer->Cancel();
+ mWordCacheExpirationTimer = nullptr;
+ }
+
+ // Expire everything that has a zero refcount, so we don't leak them.
+ AgeAllGenerations();
+ // All fonts should be gone.
+ NS_WARNING_ASSERTION(mFonts.Count() == 0,
+ "Fonts still alive while shutting down gfxFontCache");
+ // Note that we have to delete everything through the expiration
+ // tracker, since there might be fonts not in the hashtable but in
+ // the tracker.
+}
+
+bool
+gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const
+{
+ const gfxCharacterMap* fontUnicodeRangeMap = mFont->GetUnicodeRangeMap();
+ return aKey->mFontEntry == mFont->GetFontEntry() &&
+ aKey->mStyle->Equals(*mFont->GetStyle()) &&
+ ((!aKey->mUnicodeRangeMap && !fontUnicodeRangeMap) ||
+ (aKey->mUnicodeRangeMap && fontUnicodeRangeMap &&
+ aKey->mUnicodeRangeMap->Equals(fontUnicodeRangeMap)));
+}
+
+already_AddRefed<gfxFont>
+gfxFontCache::Lookup(const gfxFontEntry* aFontEntry,
+ const gfxFontStyle* aStyle,
+ const gfxCharacterMap* aUnicodeRangeMap)
+{
+ Key key(aFontEntry, aStyle, aUnicodeRangeMap);
+ HashEntry *entry = mFonts.GetEntry(key);
+
+ Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT, entry != nullptr);
+ if (!entry)
+ return nullptr;
+
+ RefPtr<gfxFont> font = entry->mFont;
+ return font.forget();
+}
+
+void
+gfxFontCache::AddNew(gfxFont *aFont)
+{
+ Key key(aFont->GetFontEntry(), aFont->GetStyle(),
+ aFont->GetUnicodeRangeMap());
+ HashEntry *entry = mFonts.PutEntry(key);
+ if (!entry)
+ return;
+ gfxFont *oldFont = entry->mFont;
+ entry->mFont = aFont;
+ // Assert that we can find the entry we just put in (this fails if the key
+ // has a NaN float value in it, e.g. 'sizeAdjust').
+ MOZ_ASSERT(entry == mFonts.GetEntry(key));
+ // If someone's asked us to replace an existing font entry, then that's a
+ // bit weird, but let it happen, and expire the old font if it's not used.
+ if (oldFont && oldFont->GetExpirationState()->IsTracked()) {
+ // if oldFont == aFont, recount should be > 0,
+ // so we shouldn't be here.
+ NS_ASSERTION(aFont != oldFont, "new font is tracked for expiry!");
+ NotifyExpired(oldFont);
+ }
+}
+
+void
+gfxFontCache::NotifyReleased(gfxFont *aFont)
+{
+ nsresult rv = AddObject(aFont);
+ if (NS_FAILED(rv)) {
+ // We couldn't track it for some reason. Kill it now.
+ DestroyFont(aFont);
+ }
+ // Note that we might have fonts that aren't in the hashtable, perhaps because
+ // of OOM adding to the hashtable or because someone did an AddNew where
+ // we already had a font. These fonts are added to the expiration tracker
+ // anyway, even though Lookup can't resurrect them. Eventually they will
+ // expire and be deleted.
+}
+
+void
+gfxFontCache::NotifyExpired(gfxFont *aFont)
+{
+ aFont->ClearCachedWords();
+ RemoveObject(aFont);
+ DestroyFont(aFont);
+}
+
+void
+gfxFontCache::DestroyFont(gfxFont *aFont)
+{
+ Key key(aFont->GetFontEntry(), aFont->GetStyle(),
+ aFont->GetUnicodeRangeMap());
+ HashEntry *entry = mFonts.GetEntry(key);
+ if (entry && entry->mFont == aFont) {
+ mFonts.RemoveEntry(entry);
+ }
+ NS_ASSERTION(aFont->GetRefCount() == 0,
+ "Destroying with non-zero ref count!");
+ delete aFont;
+}
+
+/*static*/
+void
+gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache)
+{
+ gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
+ for (auto it = cache->mFonts.Iter(); !it.Done(); it.Next()) {
+ it.Get()->mFont->AgeCachedWords();
+ }
+}
+
+void
+gfxFontCache::FlushShapedWordCaches()
+{
+ for (auto it = mFonts.Iter(); !it.Done(); it.Next()) {
+ it.Get()->mFont->ClearCachedWords();
+ }
+}
+
+void
+gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ // TODO: add the overhead of the expiration tracker (generation arrays)
+
+ aSizes->mFontInstances += mFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mFonts.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->mFont->AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ }
+}
+
+void
+gfxFontCache::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ aSizes->mFontInstances += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+#define MAX_SSXX_VALUE 99
+#define MAX_CVXX_VALUE 99
+
+static void
+LookupAlternateValues(gfxFontFeatureValueSet *featureLookup,
+ const nsAString& aFamily,
+ const nsTArray<gfxAlternateValue>& altValue,
+ nsTArray<gfxFontFeature>& aFontFeatures)
+{
+ uint32_t numAlternates = altValue.Length();
+ for (uint32_t i = 0; i < numAlternates; i++) {
+ const gfxAlternateValue& av = altValue.ElementAt(i);
+ AutoTArray<uint32_t,4> values;
+
+ // map <family, name, feature> ==> <values>
+ bool found =
+ featureLookup->GetFontFeatureValuesFor(aFamily, av.alternate,
+ av.value, values);
+ uint32_t numValues = values.Length();
+
+ // nothing defined, skip
+ if (!found || numValues == 0) {
+ continue;
+ }
+
+ gfxFontFeature feature;
+ if (av.alternate == NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT) {
+ NS_ASSERTION(numValues <= 2,
+ "too many values allowed for character-variant");
+ // character-variant(12 3) ==> 'cv12' = 3
+ uint32_t nn = values.ElementAt(0);
+ // ignore values greater than 99
+ if (nn == 0 || nn > MAX_CVXX_VALUE) {
+ continue;
+ }
+ feature.mValue = 1;
+ if (numValues > 1) {
+ feature.mValue = values.ElementAt(1);
+ }
+ feature.mTag = HB_TAG('c','v',('0' + nn / 10), ('0' + nn % 10));
+ aFontFeatures.AppendElement(feature);
+
+ } else if (av.alternate == NS_FONT_VARIANT_ALTERNATES_STYLESET) {
+ // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1
+ feature.mValue = 1;
+ for (uint32_t v = 0; v < numValues; v++) {
+ uint32_t nn = values.ElementAt(v);
+ if (nn == 0 || nn > MAX_SSXX_VALUE) {
+ continue;
+ }
+ feature.mTag = HB_TAG('s','s',('0' + nn / 10), ('0' + nn % 10));
+ aFontFeatures.AppendElement(feature);
+ }
+
+ } else {
+ NS_ASSERTION(numValues == 1,
+ "too many values for font-specific font-variant-alternates");
+ feature.mValue = values.ElementAt(0);
+
+ switch (av.alternate) {
+ case NS_FONT_VARIANT_ALTERNATES_STYLISTIC: // salt
+ feature.mTag = HB_TAG('s','a','l','t');
+ break;
+ case NS_FONT_VARIANT_ALTERNATES_SWASH: // swsh, cswh
+ feature.mTag = HB_TAG('s','w','s','h');
+ aFontFeatures.AppendElement(feature);
+ feature.mTag = HB_TAG('c','s','w','h');
+ break;
+ case NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: // ornm
+ feature.mTag = HB_TAG('o','r','n','m');
+ break;
+ case NS_FONT_VARIANT_ALTERNATES_ANNOTATION: // nalt
+ feature.mTag = HB_TAG('n','a','l','t');
+ break;
+ default:
+ feature.mTag = 0;
+ break;
+ }
+
+ NS_ASSERTION(feature.mTag, "unsupported alternate type");
+ if (!feature.mTag) {
+ continue;
+ }
+ aFontFeatures.AppendElement(feature);
+ }
+ }
+}
+
+/* static */ void
+gfxFontShaper::MergeFontFeatures(
+ const gfxFontStyle *aStyle,
+ const nsTArray<gfxFontFeature>& aFontFeatures,
+ bool aDisableLigatures,
+ const nsAString& aFamilyName,
+ bool aAddSmallCaps,
+ void (*aHandleFeature)(const uint32_t&, uint32_t&, void*),
+ void* aHandleFeatureData)
+{
+ uint32_t numAlts = aStyle->alternateValues.Length();
+ const nsTArray<gfxFontFeature>& styleRuleFeatures =
+ aStyle->featureSettings;
+
+ // Bail immediately if nothing to do, which is the common case.
+ if (styleRuleFeatures.IsEmpty() &&
+ aFontFeatures.IsEmpty() &&
+ !aDisableLigatures &&
+ aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL &&
+ aStyle->variantSubSuper == NS_FONT_VARIANT_POSITION_NORMAL &&
+ numAlts == 0) {
+ return;
+ }
+
+ nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
+
+ // Ligature features are enabled by default in the generic shaper,
+ // so we explicitly turn them off if necessary (for letter-spacing)
+ if (aDisableLigatures) {
+ mergedFeatures.Put(HB_TAG('l','i','g','a'), 0);
+ mergedFeatures.Put(HB_TAG('c','l','i','g'), 0);
+ }
+
+ // add feature values from font
+ uint32_t i, count;
+
+ count = aFontFeatures.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = aFontFeatures.ElementAt(i);
+ mergedFeatures.Put(feature.mTag, feature.mValue);
+ }
+
+ // font-variant-caps - handled here due to the need for fallback handling
+ // petite caps cases can fallback to appropriate smallcaps
+ uint32_t variantCaps = aStyle->variantCaps;
+ switch (variantCaps) {
+ case NS_FONT_VARIANT_CAPS_NORMAL:
+ break;
+
+ case NS_FONT_VARIANT_CAPS_ALLSMALL:
+ mergedFeatures.Put(HB_TAG('c','2','s','c'), 1);
+ // fall through to the small-caps case
+ MOZ_FALLTHROUGH;
+
+ case NS_FONT_VARIANT_CAPS_SMALLCAPS:
+ mergedFeatures.Put(HB_TAG('s','m','c','p'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_ALLPETITE:
+ mergedFeatures.Put(aAddSmallCaps ? HB_TAG('c','2','s','c') :
+ HB_TAG('c','2','p','c'), 1);
+ // fall through to the petite-caps case
+ MOZ_FALLTHROUGH;
+
+ case NS_FONT_VARIANT_CAPS_PETITECAPS:
+ mergedFeatures.Put(aAddSmallCaps ? HB_TAG('s','m','c','p') :
+ HB_TAG('p','c','a','p'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_TITLING:
+ mergedFeatures.Put(HB_TAG('t','i','t','l'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_UNICASE:
+ mergedFeatures.Put(HB_TAG('u','n','i','c'), 1);
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected variantCaps");
+ break;
+ }
+
+ // font-variant-position - handled here due to the need for fallback
+ switch (aStyle->variantSubSuper) {
+ case NS_FONT_VARIANT_POSITION_NORMAL:
+ break;
+ case NS_FONT_VARIANT_POSITION_SUPER:
+ mergedFeatures.Put(HB_TAG('s','u','p','s'), 1);
+ break;
+ case NS_FONT_VARIANT_POSITION_SUB:
+ mergedFeatures.Put(HB_TAG('s','u','b','s'), 1);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected variantSubSuper");
+ break;
+ }
+
+ // add font-specific feature values from style rules
+ if (aStyle->featureValueLookup && numAlts > 0) {
+ AutoTArray<gfxFontFeature,4> featureList;
+
+ // insert list of alternate feature settings
+ LookupAlternateValues(aStyle->featureValueLookup, aFamilyName,
+ aStyle->alternateValues, featureList);
+
+ count = featureList.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = featureList.ElementAt(i);
+ mergedFeatures.Put(feature.mTag, feature.mValue);
+ }
+ }
+
+ // add feature values from style rules
+ count = styleRuleFeatures.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = styleRuleFeatures.ElementAt(i);
+ mergedFeatures.Put(feature.mTag, feature.mValue);
+ }
+
+ if (mergedFeatures.Count() != 0) {
+ for (auto iter = mergedFeatures.Iter(); !iter.Done(); iter.Next()) {
+ aHandleFeature(iter.Key(), iter.Data(), aHandleFeatureData);
+ }
+ }
+}
+
+// Work out whether cairo will snap inter-glyph spacing to pixels.
+//
+// Layout does not align text to pixel boundaries, so, with font drawing
+// backends that snap glyph positions to pixels, it is important that
+// inter-glyph spacing within words is always an integer number of pixels.
+// This ensures that the drawing backend snaps all of the word's glyphs in the
+// same direction and so inter-glyph spacing remains the same.
+//
+/* static */ void
+gfxFontShaper::GetRoundOffsetsToPixels(DrawTarget* aDrawTarget,
+ bool* aRoundX, bool* aRoundY)
+{
+ *aRoundX = false;
+ // Could do something fancy here for ScaleFactors of
+ // AxisAlignedTransforms, but we leave things simple.
+ // Not much point rounding if a matrix will mess things up anyway.
+ // Also return false for non-cairo contexts.
+ if (aDrawTarget->GetTransform().HasNonTranslation()) {
+ *aRoundY = false;
+ return;
+ }
+
+ // All raster backends snap glyphs to pixels vertically.
+ // Print backends set CAIRO_HINT_METRICS_OFF.
+ *aRoundY = true;
+
+ cairo_t* cr = gfxFont::RefCairo(aDrawTarget);
+ cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(cr);
+
+ // bug 1198921 - this sometimes fails under Windows for whatver reason
+ NS_ASSERTION(scaled_font, "null cairo scaled font should never be returned "
+ "by cairo_get_scaled_font");
+ if (!scaled_font) {
+ *aRoundX = true; // default to the same as the fallback path below
+ return;
+ }
+
+ // Sometimes hint metrics gets set for us, most notably for printing.
+ cairo_font_options_t *font_options = cairo_font_options_create();
+ cairo_scaled_font_get_font_options(scaled_font, font_options);
+ cairo_hint_metrics_t hint_metrics =
+ cairo_font_options_get_hint_metrics(font_options);
+ cairo_font_options_destroy(font_options);
+
+ switch (hint_metrics) {
+ case CAIRO_HINT_METRICS_OFF:
+ *aRoundY = false;
+ return;
+ case CAIRO_HINT_METRICS_DEFAULT:
+ // Here we mimic what cairo surface/font backends do. Printing
+ // surfaces have already been handled by hint_metrics. The
+ // fallback show_glyphs implementation composites pixel-aligned
+ // glyph surfaces, so we just pick surface/font combinations that
+ // override this.
+ switch (cairo_scaled_font_get_type(scaled_font)) {
+#if CAIRO_HAS_DWRITE_FONT // dwrite backend is not in std cairo releases yet
+ case CAIRO_FONT_TYPE_DWRITE:
+ // show_glyphs is implemented on the font and so is used for
+ // all surface types; however, it may pixel-snap depending on
+ // the dwrite rendering mode
+ if (!cairo_dwrite_scaled_font_get_force_GDI_classic(scaled_font) &&
+ gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode() ==
+ DWRITE_MEASURING_MODE_NATURAL) {
+ return;
+ }
+ MOZ_FALLTHROUGH;
+#endif
+ case CAIRO_FONT_TYPE_QUARTZ:
+ // Quartz surfaces implement show_glyphs for Quartz fonts
+ if (cairo_surface_get_type(cairo_get_target(cr)) ==
+ CAIRO_SURFACE_TYPE_QUARTZ) {
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case CAIRO_HINT_METRICS_ON:
+ break;
+ }
+ *aRoundX = true;
+}
+
+void
+gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
+ const char16_t *aString,
+ uint32_t aLength)
+{
+ CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset;
+
+ gfxTextRun::CompressedGlyph extendCluster;
+ extendCluster.SetComplex(false, true, 0);
+
+ ClusterIterator iter(aString, aLength);
+
+ // the ClusterIterator won't be able to tell us if the string
+ // _begins_ with a cluster-extender, so we handle that here
+ if (aLength) {
+ uint32_t ch = *aString;
+ if (aLength > 1 && NS_IS_HIGH_SURROGATE(ch) &&
+ NS_IS_LOW_SURROGATE(aString[1])) {
+ ch = SURROGATE_TO_UCS4(ch, aString[1]);
+ }
+ if (IsClusterExtender(ch)) {
+ *glyphs = extendCluster;
+ }
+ }
+
+ while (!iter.AtEnd()) {
+ if (*iter == char16_t(' ')) {
+ glyphs->SetIsSpace();
+ }
+ // advance iter to the next cluster-start (or end of text)
+ iter.Next();
+ // step past the first char of the cluster
+ aString++;
+ glyphs++;
+ // mark all the rest as cluster-continuations
+ while (aString < iter) {
+ *glyphs = extendCluster;
+ glyphs++;
+ aString++;
+ }
+ }
+}
+
+void
+gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
+ const uint8_t *aString,
+ uint32_t aLength)
+{
+ CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset;
+ const uint8_t *limit = aString + aLength;
+
+ while (aString < limit) {
+ if (*aString == uint8_t(' ')) {
+ glyphs->SetIsSpace();
+ }
+ aString++;
+ glyphs++;
+ }
+}
+
+gfxShapedText::DetailedGlyph *
+gfxShapedText::AllocateDetailedGlyphs(uint32_t aIndex, uint32_t aCount)
+{
+ NS_ASSERTION(aIndex < GetLength(), "Index out of range");
+
+ if (!mDetailedGlyphs) {
+ mDetailedGlyphs = MakeUnique<DetailedGlyphStore>();
+ }
+
+ return mDetailedGlyphs->Allocate(aIndex, aCount);
+}
+
+void
+gfxShapedText::SetGlyphs(uint32_t aIndex, CompressedGlyph aGlyph,
+ const DetailedGlyph *aGlyphs)
+{
+ NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here");
+ NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(),
+ "First character can't be a ligature continuation!");
+
+ uint32_t glyphCount = aGlyph.GetGlyphCount();
+ if (glyphCount > 0) {
+ DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount);
+ memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount);
+ }
+ GetCharacterGlyphs()[aIndex] = aGlyph;
+}
+
+#define ZWNJ 0x200C
+#define ZWJ 0x200D
+static inline bool
+IsIgnorable(uint32_t aChar)
+{
+ return (IsDefaultIgnorable(aChar)) || aChar == ZWNJ || aChar == ZWJ;
+}
+
+void
+gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont)
+{
+ uint8_t category = GetGeneralCategory(aChar);
+ if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK &&
+ category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
+ {
+ GetCharacterGlyphs()[aIndex].SetComplex(false, true, 0);
+ }
+
+ DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+
+ details->mGlyphID = aChar;
+ if (IsIgnorable(aChar)) {
+ // Setting advance width to zero will prevent drawing the hexbox
+ details->mAdvance = 0;
+ } else {
+ gfxFloat width =
+ std::max(aFont->GetMetrics(gfxFont::eHorizontal).aveCharWidth,
+ gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth(aChar,
+ mAppUnitsPerDevUnit)));
+ details->mAdvance = uint32_t(width * mAppUnitsPerDevUnit);
+ }
+ details->mXOffset = 0;
+ details->mYOffset = 0;
+ GetCharacterGlyphs()[aIndex].SetMissing(1);
+}
+
+bool
+gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh)
+{
+ if (IsIgnorable(aCh)) {
+ // There are a few default-ignorables of Letter category (currently,
+ // just the Hangul filler characters) that we'd better not discard
+ // if they're followed by additional characters in the same cluster.
+ // Some fonts use them to carry the width of a whole cluster of
+ // combining jamos; see bug 1238243.
+ if (GetGenCategory(aCh) == nsIUGenCategory::kLetter &&
+ aIndex + 1 < GetLength() &&
+ !GetCharacterGlyphs()[aIndex + 1].IsClusterStart()) {
+ return false;
+ }
+ DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+ details->mGlyphID = aCh;
+ details->mAdvance = 0;
+ details->mXOffset = 0;
+ details->mYOffset = 0;
+ GetCharacterGlyphs()[aIndex].SetMissing(1);
+ return true;
+ }
+ return false;
+}
+
+void
+gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset,
+ uint32_t aOffset,
+ uint32_t aLength)
+{
+ uint32_t synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit;
+ CompressedGlyph *charGlyphs = GetCharacterGlyphs();
+ for (uint32_t i = aOffset; i < aOffset + aLength; ++i) {
+ CompressedGlyph *glyphData = charGlyphs + i;
+ if (glyphData->IsSimpleGlyph()) {
+ // simple glyphs ==> just add the advance
+ int32_t advance = glyphData->GetSimpleAdvance() + synAppUnitOffset;
+ if (CompressedGlyph::IsSimpleAdvance(advance)) {
+ glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
+ } else {
+ // rare case, tested by making this the default
+ uint32_t glyphIndex = glyphData->GetSimpleGlyph();
+ glyphData->SetComplex(true, true, 1);
+ DetailedGlyph detail = {glyphIndex, advance, 0, 0};
+ SetGlyphs(i, *glyphData, &detail);
+ }
+ } else {
+ // complex glyphs ==> add offset at cluster/ligature boundaries
+ uint32_t detailedLength = glyphData->GetGlyphCount();
+ if (detailedLength) {
+ DetailedGlyph *details = GetDetailedGlyphs(i);
+ if (!details) {
+ continue;
+ }
+ if (IsRightToLeft()) {
+ details[0].mAdvance += synAppUnitOffset;
+ } else {
+ details[detailedLength - 1].mAdvance += synAppUnitOffset;
+ }
+ }
+ }
+ }
+}
+
+void
+gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft)
+{
+ mAscent = std::max(mAscent, aOther.mAscent);
+ mDescent = std::max(mDescent, aOther.mDescent);
+ if (aOtherIsOnLeft) {
+ mBoundingBox =
+ (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)).Union(aOther.mBoundingBox);
+ } else {
+ mBoundingBox =
+ mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0));
+ }
+ mAdvanceWidth += aOther.mAdvanceWidth;
+}
+
+gfxFont::gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
+ AntialiasOption anAAOption, cairo_scaled_font_t *aScaledFont) :
+ mScaledFont(aScaledFont),
+ mFontEntry(aFontEntry), mIsValid(true),
+ mApplySyntheticBold(false),
+ mMathInitialized(false),
+ mStyle(*aFontStyle),
+ mAdjustedSize(0.0),
+ mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized"
+ mAntialiasOption(anAAOption)
+{
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ ++gFontCount;
+#endif
+ mKerningSet = HasFeatureSet(HB_TAG('k','e','r','n'), mKerningEnabled);
+}
+
+gfxFont::~gfxFont()
+{
+ mFontEntry->NotifyFontDestroyed(this);
+
+ if (mGlyphChangeObservers) {
+ for (auto it = mGlyphChangeObservers->Iter(); !it.Done(); it.Next()) {
+ it.Get()->GetKey()->ForgetFont();
+ }
+ }
+}
+
+gfxFloat
+gfxFont::GetGlyphHAdvance(DrawTarget* aDrawTarget, uint16_t aGID)
+{
+ if (!SetupCairoFont(aDrawTarget)) {
+ return 0;
+ }
+ if (ProvidesGlyphWidths()) {
+ return GetGlyphWidth(*aDrawTarget, aGID) / 65536.0;
+ }
+ if (mFUnitsConvFactor < 0.0f) {
+ GetMetrics(eHorizontal);
+ }
+ NS_ASSERTION(mFUnitsConvFactor >= 0.0f,
+ "missing font unit conversion factor");
+ if (!mHarfBuzzShaper) {
+ mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
+ }
+ gfxHarfBuzzShaper* shaper =
+ static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
+ if (!shaper->Initialize()) {
+ return 0;
+ }
+ return shaper->GetGlyphHAdvance(aGID) / 65536.0;
+}
+
+static void
+CollectLookupsByFeature(hb_face_t *aFace, hb_tag_t aTableTag,
+ uint32_t aFeatureIndex, hb_set_t *aLookups)
+{
+ uint32_t lookups[32];
+ uint32_t i, len, offset;
+
+ offset = 0;
+ do {
+ len = ArrayLength(lookups);
+ hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex,
+ offset, &len, lookups);
+ for (i = 0; i < len; i++) {
+ hb_set_add(aLookups, lookups[i]);
+ }
+ offset += len;
+ } while (len == ArrayLength(lookups));
+}
+
+static void
+CollectLookupsByLanguage(hb_face_t *aFace, hb_tag_t aTableTag,
+ const nsTHashtable<nsUint32HashKey>&
+ aSpecificFeatures,
+ hb_set_t *aOtherLookups,
+ hb_set_t *aSpecificFeatureLookups,
+ uint32_t aScriptIndex, uint32_t aLangIndex)
+{
+ uint32_t reqFeatureIndex;
+ if (hb_ot_layout_language_get_required_feature_index(aFace, aTableTag,
+ aScriptIndex,
+ aLangIndex,
+ &reqFeatureIndex)) {
+ CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex,
+ aOtherLookups);
+ }
+
+ uint32_t featureIndexes[32];
+ uint32_t i, len, offset;
+
+ offset = 0;
+ do {
+ len = ArrayLength(featureIndexes);
+ hb_ot_layout_language_get_feature_indexes(aFace, aTableTag,
+ aScriptIndex, aLangIndex,
+ offset, &len, featureIndexes);
+
+ for (i = 0; i < len; i++) {
+ uint32_t featureIndex = featureIndexes[i];
+
+ // get the feature tag
+ hb_tag_t featureTag;
+ uint32_t tagLen = 1;
+ hb_ot_layout_language_get_feature_tags(aFace, aTableTag,
+ aScriptIndex, aLangIndex,
+ offset + i, &tagLen,
+ &featureTag);
+
+ // collect lookups
+ hb_set_t *lookups = aSpecificFeatures.GetEntry(featureTag) ?
+ aSpecificFeatureLookups : aOtherLookups;
+ CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups);
+ }
+ offset += len;
+ } while (len == ArrayLength(featureIndexes));
+}
+
+static bool
+HasLookupRuleWithGlyphByScript(hb_face_t *aFace, hb_tag_t aTableTag,
+ hb_tag_t aScriptTag, uint32_t aScriptIndex,
+ uint16_t aGlyph,
+ const nsTHashtable<nsUint32HashKey>&
+ aDefaultFeatures,
+ bool& aHasDefaultFeatureWithGlyph)
+{
+ uint32_t numLangs, lang;
+ hb_set_t *defaultFeatureLookups = hb_set_create();
+ hb_set_t *nonDefaultFeatureLookups = hb_set_create();
+
+ // default lang
+ CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
+ nonDefaultFeatureLookups, defaultFeatureLookups,
+ aScriptIndex,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
+
+ // iterate over langs
+ numLangs = hb_ot_layout_script_get_language_tags(aFace, aTableTag,
+ aScriptIndex, 0,
+ nullptr, nullptr);
+ for (lang = 0; lang < numLangs; lang++) {
+ CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
+ nonDefaultFeatureLookups,
+ defaultFeatureLookups,
+ aScriptIndex, lang);
+ }
+
+ // look for the glyph among default feature lookups
+ aHasDefaultFeatureWithGlyph = false;
+ hb_set_t *glyphs = hb_set_create();
+ hb_codepoint_t index = -1;
+ while (hb_set_next(defaultFeatureLookups, &index)) {
+ hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
+ glyphs, glyphs, glyphs,
+ nullptr);
+ if (hb_set_has(glyphs, aGlyph)) {
+ aHasDefaultFeatureWithGlyph = true;
+ break;
+ }
+ }
+
+ // look for the glyph among non-default feature lookups
+ // if no default feature lookups contained spaces
+ bool hasNonDefaultFeatureWithGlyph = false;
+ if (!aHasDefaultFeatureWithGlyph) {
+ hb_set_clear(glyphs);
+ index = -1;
+ while (hb_set_next(nonDefaultFeatureLookups, &index)) {
+ hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
+ glyphs, glyphs, glyphs,
+ nullptr);
+ if (hb_set_has(glyphs, aGlyph)) {
+ hasNonDefaultFeatureWithGlyph = true;
+ break;
+ }
+ }
+ }
+
+ hb_set_destroy(glyphs);
+ hb_set_destroy(defaultFeatureLookups);
+ hb_set_destroy(nonDefaultFeatureLookups);
+
+ return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph;
+}
+
+static void
+HasLookupRuleWithGlyph(hb_face_t *aFace, hb_tag_t aTableTag, bool& aHasGlyph,
+ hb_tag_t aSpecificFeature, bool& aHasGlyphSpecific,
+ uint16_t aGlyph)
+{
+ // iterate over the scripts in the font
+ uint32_t numScripts, numLangs, script, lang;
+ hb_set_t *otherLookups = hb_set_create();
+ hb_set_t *specificFeatureLookups = hb_set_create();
+ nsTHashtable<nsUint32HashKey> specificFeature;
+
+ specificFeature.PutEntry(aSpecificFeature);
+
+ numScripts = hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0,
+ nullptr, nullptr);
+
+ for (script = 0; script < numScripts; script++) {
+ // default lang
+ CollectLookupsByLanguage(aFace, aTableTag, specificFeature,
+ otherLookups, specificFeatureLookups,
+ script, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
+
+ // iterate over langs
+ numLangs = hb_ot_layout_script_get_language_tags(aFace, HB_OT_TAG_GPOS,
+ script, 0,
+ nullptr, nullptr);
+ for (lang = 0; lang < numLangs; lang++) {
+ CollectLookupsByLanguage(aFace, aTableTag, specificFeature,
+ otherLookups, specificFeatureLookups,
+ script, lang);
+ }
+ }
+
+ // look for the glyph among non-specific feature lookups
+ hb_set_t *glyphs = hb_set_create();
+ hb_codepoint_t index = -1;
+ while (hb_set_next(otherLookups, &index)) {
+ hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
+ glyphs, glyphs, glyphs,
+ nullptr);
+ if (hb_set_has(glyphs, aGlyph)) {
+ aHasGlyph = true;
+ break;
+ }
+ }
+
+ // look for the glyph among specific feature lookups
+ hb_set_clear(glyphs);
+ index = -1;
+ while (hb_set_next(specificFeatureLookups, &index)) {
+ hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
+ glyphs, glyphs, glyphs,
+ nullptr);
+ if (hb_set_has(glyphs, aGlyph)) {
+ aHasGlyphSpecific = true;
+ break;
+ }
+ }
+
+ hb_set_destroy(glyphs);
+ hb_set_destroy(specificFeatureLookups);
+ hb_set_destroy(otherLookups);
+}
+
+nsDataHashtable<nsUint32HashKey,Script> *gfxFont::sScriptTagToCode = nullptr;
+nsTHashtable<nsUint32HashKey> *gfxFont::sDefaultFeatures = nullptr;
+
+static inline bool
+HasSubstitution(uint32_t *aBitVector, Script aScript) {
+ return (aBitVector[static_cast<uint32_t>(aScript) >> 5]
+ & (1 << (static_cast<uint32_t>(aScript) & 0x1f))) != 0;
+}
+
+// union of all default substitution features across scripts
+static const hb_tag_t defaultFeatures[] = {
+ HB_TAG('a','b','v','f'),
+ HB_TAG('a','b','v','s'),
+ HB_TAG('a','k','h','n'),
+ HB_TAG('b','l','w','f'),
+ HB_TAG('b','l','w','s'),
+ HB_TAG('c','a','l','t'),
+ HB_TAG('c','c','m','p'),
+ HB_TAG('c','f','a','r'),
+ HB_TAG('c','j','c','t'),
+ HB_TAG('c','l','i','g'),
+ HB_TAG('f','i','n','2'),
+ HB_TAG('f','i','n','3'),
+ HB_TAG('f','i','n','a'),
+ HB_TAG('h','a','l','f'),
+ HB_TAG('h','a','l','n'),
+ HB_TAG('i','n','i','t'),
+ HB_TAG('i','s','o','l'),
+ HB_TAG('l','i','g','a'),
+ HB_TAG('l','j','m','o'),
+ HB_TAG('l','o','c','l'),
+ HB_TAG('l','t','r','a'),
+ HB_TAG('l','t','r','m'),
+ HB_TAG('m','e','d','2'),
+ HB_TAG('m','e','d','i'),
+ HB_TAG('m','s','e','t'),
+ HB_TAG('n','u','k','t'),
+ HB_TAG('p','r','e','f'),
+ HB_TAG('p','r','e','s'),
+ HB_TAG('p','s','t','f'),
+ HB_TAG('p','s','t','s'),
+ HB_TAG('r','c','l','t'),
+ HB_TAG('r','l','i','g'),
+ HB_TAG('r','k','r','f'),
+ HB_TAG('r','p','h','f'),
+ HB_TAG('r','t','l','a'),
+ HB_TAG('r','t','l','m'),
+ HB_TAG('t','j','m','o'),
+ HB_TAG('v','a','t','u'),
+ HB_TAG('v','e','r','t'),
+ HB_TAG('v','j','m','o')
+};
+
+void
+gfxFont::CheckForFeaturesInvolvingSpace()
+{
+ mFontEntry->mHasSpaceFeaturesInitialized = true;
+
+ bool log = LOG_FONTINIT_ENABLED();
+ TimeStamp start;
+ if (MOZ_UNLIKELY(log)) {
+ start = TimeStamp::Now();
+ }
+
+ bool result = false;
+
+ uint32_t spaceGlyph = GetSpaceGlyph();
+ if (!spaceGlyph) {
+ return;
+ }
+
+ hb_face_t *face = GetFontEntry()->GetHBFace();
+
+ // GSUB lookups - examine per script
+ if (hb_ot_layout_has_substitution(face)) {
+
+ // set up the script ==> code hashtable if needed
+ if (!sScriptTagToCode) {
+ sScriptTagToCode =
+ new nsDataHashtable<nsUint32HashKey,
+ Script>(size_t(Script::NUM_SCRIPT_CODES));
+ sScriptTagToCode->Put(HB_TAG('D','F','L','T'), Script::COMMON);
+ for (Script s = Script::ARABIC; s < Script::NUM_SCRIPT_CODES;
+ s = Script(static_cast<int>(s) + 1)) {
+ hb_script_t scriptTag = hb_script_t(GetScriptTagForCode(s));
+ hb_tag_t s1, s2;
+ hb_ot_tags_from_script(scriptTag, &s1, &s2);
+ sScriptTagToCode->Put(s1, s);
+ if (s2 != HB_OT_TAG_DEFAULT_SCRIPT) {
+ sScriptTagToCode->Put(s2, s);
+ }
+ }
+
+ uint32_t numDefaultFeatures = ArrayLength(defaultFeatures);
+ sDefaultFeatures =
+ new nsTHashtable<nsUint32HashKey>(numDefaultFeatures);
+ for (uint32_t i = 0; i < numDefaultFeatures; i++) {
+ sDefaultFeatures->PutEntry(defaultFeatures[i]);
+ }
+ }
+
+ // iterate over the scripts in the font
+ hb_tag_t scriptTags[8];
+
+ uint32_t len, offset = 0;
+ do {
+ len = ArrayLength(scriptTags);
+ hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset,
+ &len, scriptTags);
+ for (uint32_t i = 0; i < len; i++) {
+ bool isDefaultFeature = false;
+ Script s;
+ if (!HasLookupRuleWithGlyphByScript(face, HB_OT_TAG_GSUB,
+ scriptTags[i], offset + i,
+ spaceGlyph,
+ *sDefaultFeatures,
+ isDefaultFeature) ||
+ !sScriptTagToCode->Get(scriptTags[i], &s))
+ {
+ continue;
+ }
+ result = true;
+ uint32_t index = static_cast<uint32_t>(s) >> 5;
+ uint32_t bit = static_cast<uint32_t>(s) & 0x1f;
+ if (isDefaultFeature) {
+ mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit);
+ } else {
+ mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit);
+ }
+ }
+ offset += len;
+ } while (len == ArrayLength(scriptTags));
+ }
+
+ // spaces in default features of default script?
+ // ==> can't use word cache, skip GPOS analysis
+ bool canUseWordCache = true;
+ if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures,
+ Script::COMMON)) {
+ canUseWordCache = false;
+ }
+
+ // GPOS lookups - distinguish kerning from non-kerning features
+ mFontEntry->mHasSpaceFeaturesKerning = false;
+ mFontEntry->mHasSpaceFeaturesNonKerning = false;
+
+ if (canUseWordCache && hb_ot_layout_has_positioning(face)) {
+ bool hasKerning = false, hasNonKerning = false;
+ HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning,
+ HB_TAG('k','e','r','n'), hasKerning, spaceGlyph);
+ if (hasKerning || hasNonKerning) {
+ result = true;
+ }
+ mFontEntry->mHasSpaceFeaturesKerning = hasKerning;
+ mFontEntry->mHasSpaceFeaturesNonKerning = hasNonKerning;
+ }
+
+ hb_face_destroy(face);
+ mFontEntry->mHasSpaceFeatures = result;
+
+ if (MOZ_UNLIKELY(log)) {
+ TimeDuration elapsed = TimeStamp::Now() - start;
+ LOG_FONTINIT((
+ "(fontinit-spacelookups) font: %s - "
+ "subst default: %8.8x %8.8x %8.8x %8.8x "
+ "subst non-default: %8.8x %8.8x %8.8x %8.8x "
+ "kerning: %s non-kerning: %s time: %6.3f\n",
+ NS_ConvertUTF16toUTF8(mFontEntry->Name()).get(),
+ mFontEntry->mDefaultSubSpaceFeatures[3],
+ mFontEntry->mDefaultSubSpaceFeatures[2],
+ mFontEntry->mDefaultSubSpaceFeatures[1],
+ mFontEntry->mDefaultSubSpaceFeatures[0],
+ mFontEntry->mNonDefaultSubSpaceFeatures[3],
+ mFontEntry->mNonDefaultSubSpaceFeatures[2],
+ mFontEntry->mNonDefaultSubSpaceFeatures[1],
+ mFontEntry->mNonDefaultSubSpaceFeatures[0],
+ (mFontEntry->mHasSpaceFeaturesKerning ? "true" : "false"),
+ (mFontEntry->mHasSpaceFeaturesNonKerning ? "true" : "false"),
+ elapsed.ToMilliseconds()
+ ));
+ }
+}
+
+bool
+gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript)
+{
+ NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized,
+ "need to initialize space lookup flags");
+ NS_ASSERTION(aRunScript < Script::NUM_SCRIPT_CODES, "weird script code");
+ if (aRunScript == Script::INVALID ||
+ aRunScript >= Script::NUM_SCRIPT_CODES) {
+ return false;
+ }
+
+ // default features have space lookups ==> true
+ if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures,
+ Script::COMMON) ||
+ HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures,
+ aRunScript))
+ {
+ return true;
+ }
+
+ // non-default features have space lookups and some type of
+ // font feature, in font or style is specified ==> true
+ if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures,
+ Script::COMMON) ||
+ HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures,
+ aRunScript)) &&
+ (!mStyle.featureSettings.IsEmpty() ||
+ !mFontEntry->mFeatureSettings.IsEmpty()))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+gfxFont::SpaceMayParticipateInShaping(Script aRunScript)
+{
+ // avoid checking fonts known not to include default space-dependent features
+ if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) {
+ if (!mKerningSet && mStyle.featureSettings.IsEmpty() &&
+ mFontEntry->mFeatureSettings.IsEmpty()) {
+ return false;
+ }
+ }
+
+ if (FontCanSupportGraphite()) {
+ if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ return mFontEntry->HasGraphiteSpaceContextuals();
+ }
+ }
+
+ // We record the presence of space-dependent features in the font entry
+ // so that subsequent instantiations for the same font face won't
+ // require us to re-check the tables; however, the actual check is done
+ // by gfxFont because not all font entry subclasses know how to create
+ // a harfbuzz face for introspection.
+ if (!mFontEntry->mHasSpaceFeaturesInitialized) {
+ CheckForFeaturesInvolvingSpace();
+ }
+
+ if (!mFontEntry->mHasSpaceFeatures) {
+ return false;
+ }
+
+ // if font has substitution rules or non-kerning positioning rules
+ // that involve spaces, bypass
+ if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
+ mFontEntry->mHasSpaceFeaturesNonKerning) {
+ return true;
+ }
+
+ // if kerning explicitly enabled/disabled via font-feature-settings or
+ // font-kerning and kerning rules use spaces, only bypass when enabled
+ if (mKerningSet && mFontEntry->mHasSpaceFeaturesKerning) {
+ return mKerningEnabled;
+ }
+
+ return false;
+}
+
+bool
+gfxFont::SupportsFeature(Script aScript, uint32_t aFeatureTag)
+{
+ if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ return GetFontEntry()->SupportsGraphiteFeature(aFeatureTag);
+ }
+ return GetFontEntry()->SupportsOpenTypeFeature(aScript, aFeatureTag);
+}
+
+bool
+gfxFont::SupportsVariantCaps(Script aScript,
+ uint32_t aVariantCaps,
+ bool& aFallbackToSmallCaps,
+ bool& aSyntheticLowerToSmallCaps,
+ bool& aSyntheticUpperToSmallCaps)
+{
+ bool ok = true; // cases without fallback are fine
+ aFallbackToSmallCaps = false;
+ aSyntheticLowerToSmallCaps = false;
+ aSyntheticUpperToSmallCaps = false;
+ switch (aVariantCaps) {
+ case NS_FONT_VARIANT_CAPS_SMALLCAPS:
+ ok = SupportsFeature(aScript, HB_TAG('s','m','c','p'));
+ if (!ok) {
+ aSyntheticLowerToSmallCaps = true;
+ }
+ break;
+ case NS_FONT_VARIANT_CAPS_ALLSMALL:
+ ok = SupportsFeature(aScript, HB_TAG('s','m','c','p')) &&
+ SupportsFeature(aScript, HB_TAG('c','2','s','c'));
+ if (!ok) {
+ aSyntheticLowerToSmallCaps = true;
+ aSyntheticUpperToSmallCaps = true;
+ }
+ break;
+ case NS_FONT_VARIANT_CAPS_PETITECAPS:
+ ok = SupportsFeature(aScript, HB_TAG('p','c','a','p'));
+ if (!ok) {
+ ok = SupportsFeature(aScript, HB_TAG('s','m','c','p'));
+ aFallbackToSmallCaps = ok;
+ }
+ if (!ok) {
+ aSyntheticLowerToSmallCaps = true;
+ }
+ break;
+ case NS_FONT_VARIANT_CAPS_ALLPETITE:
+ ok = SupportsFeature(aScript, HB_TAG('p','c','a','p')) &&
+ SupportsFeature(aScript, HB_TAG('c','2','p','c'));
+ if (!ok) {
+ ok = SupportsFeature(aScript, HB_TAG('s','m','c','p')) &&
+ SupportsFeature(aScript, HB_TAG('c','2','s','c'));
+ aFallbackToSmallCaps = ok;
+ }
+ if (!ok) {
+ aSyntheticLowerToSmallCaps = true;
+ aSyntheticUpperToSmallCaps = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ NS_ASSERTION(!(ok && (aSyntheticLowerToSmallCaps ||
+ aSyntheticUpperToSmallCaps)),
+ "shouldn't use synthetic features if we found real ones");
+
+ NS_ASSERTION(!(!ok && aFallbackToSmallCaps),
+ "if we found a usable fallback, that counts as ok");
+
+ return ok;
+}
+
+bool
+gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
+ const uint8_t *aString,
+ uint32_t aLength, Script aRunScript)
+{
+ NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString),
+ aLength);
+ return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(),
+ aLength, aRunScript);
+}
+
+bool
+gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
+ const char16_t *aString,
+ uint32_t aLength, Script aRunScript)
+{
+ NS_ASSERTION(aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER ||
+ aSubSuperscript == NS_FONT_VARIANT_POSITION_SUB,
+ "unknown value of font-variant-position");
+
+ uint32_t feature = aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER ?
+ HB_TAG('s','u','p','s') : HB_TAG('s','u','b','s');
+
+ if (!SupportsFeature(aRunScript, feature)) {
+ return false;
+ }
+
+ // xxx - for graphite, don't really know how to sniff lookups so bail
+ if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ return true;
+ }
+
+ if (!mHarfBuzzShaper) {
+ mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
+ }
+ gfxHarfBuzzShaper* shaper =
+ static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
+ if (!shaper->Initialize()) {
+ return false;
+ }
+
+ // get the hbset containing input glyphs for the feature
+ const hb_set_t *inputGlyphs = mFontEntry->InputsForOpenTypeFeature(aRunScript, feature);
+
+ // create an hbset containing default glyphs for the script run
+ hb_set_t *defaultGlyphsInRun = hb_set_create();
+
+ // for each character, get the glyph id
+ for (uint32_t i = 0; i < aLength; i++) {
+ uint32_t ch = aString[i];
+
+ if ((i + 1 < aLength) && NS_IS_HIGH_SURROGATE(ch) &&
+ NS_IS_LOW_SURROGATE(aString[i + 1])) {
+ i++;
+ ch = SURROGATE_TO_UCS4(ch, aString[i]);
+ }
+
+ if (ch == 0xa0) {
+ ch = ' ';
+ }
+
+ hb_codepoint_t gid = shaper->GetNominalGlyph(ch);
+ hb_set_add(defaultGlyphsInRun, gid);
+ }
+
+ // intersect with input glyphs, if size is not the same ==> fallback
+ uint32_t origSize = hb_set_get_population(defaultGlyphsInRun);
+ hb_set_intersect(defaultGlyphsInRun, inputGlyphs);
+ uint32_t intersectionSize = hb_set_get_population(defaultGlyphsInRun);
+ hb_set_destroy(defaultGlyphsInRun);
+
+ return origSize == intersectionSize;
+}
+
+bool
+gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn)
+{
+ aFeatureOn = false;
+
+ if (mStyle.featureSettings.IsEmpty() &&
+ GetFontEntry()->mFeatureSettings.IsEmpty()) {
+ return false;
+ }
+
+ // add feature values from font
+ bool featureSet = false;
+ uint32_t i, count;
+
+ nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings;
+ count = fontFeatures.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = fontFeatures.ElementAt(i);
+ if (feature.mTag == aFeature) {
+ featureSet = true;
+ aFeatureOn = (feature.mValue != 0);
+ }
+ }
+
+ // add feature values from style rules
+ nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings;
+ count = styleFeatures.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = styleFeatures.ElementAt(i);
+ if (feature.mTag == aFeature) {
+ featureSet = true;
+ aFeatureOn = (feature.mValue != 0);
+ }
+ }
+
+ return featureSet;
+}
+
+/**
+ * A helper function in case we need to do any rounding or other
+ * processing here.
+ */
+#define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \
+ (double(aAppUnits)*double(aDevUnitsPerAppUnit))
+
+static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) {
+ switch (aAAOption) {
+ case gfxFont::kAntialiasSubpixel:
+ return AntialiasMode::SUBPIXEL;
+ case gfxFont::kAntialiasGrayscale:
+ return AntialiasMode::GRAY;
+ case gfxFont::kAntialiasNone:
+ return AntialiasMode::NONE;
+ default:
+ return AntialiasMode::DEFAULT;
+ }
+}
+
+class GlyphBufferAzure
+{
+public:
+ GlyphBufferAzure(const TextRunDrawParams& aRunParams,
+ const FontDrawParams& aFontParams)
+ : mRunParams(aRunParams)
+ , mFontParams(aFontParams)
+ , mNumGlyphs(0)
+ {
+ }
+
+ ~GlyphBufferAzure()
+ {
+ Flush(true); // flush any remaining buffered glyphs
+ }
+
+ void OutputGlyph(uint32_t aGlyphID, const gfxPoint& aPt)
+ {
+ Glyph *glyph = AppendGlyph();
+ glyph->mIndex = aGlyphID;
+ glyph->mPosition.x = aPt.x;
+ glyph->mPosition.y = aPt.y;
+ glyph->mPosition = mFontParams.matInv.TransformPoint(glyph->mPosition);
+ Flush(false); // this will flush only if the buffer is full
+ }
+
+ const TextRunDrawParams& mRunParams;
+ const FontDrawParams& mFontParams;
+
+private:
+#define GLYPH_BUFFER_SIZE (2048/sizeof(Glyph))
+
+ Glyph *AppendGlyph()
+ {
+ return &mGlyphBuffer[mNumGlyphs++];
+ }
+
+ static DrawMode
+ GetStrokeMode(DrawMode aMode)
+ {
+ return aMode & (DrawMode::GLYPH_STROKE |
+ DrawMode::GLYPH_STROKE_UNDERNEATH);
+ }
+
+ // Render the buffered glyphs to the draw target and clear the buffer.
+ // This actually flushes the glyphs only if the buffer is full, or if the
+ // aFinish parameter is true; otherwise it simply returns.
+ void Flush(bool aFinish)
+ {
+ // Ensure there's enough room for a glyph to be added to the buffer
+ if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) {
+ return;
+ }
+
+ if (mRunParams.isRTL) {
+ Glyph *begin = &mGlyphBuffer[0];
+ Glyph *end = &mGlyphBuffer[mNumGlyphs];
+ std::reverse(begin, end);
+ }
+
+ gfx::GlyphBuffer buf;
+ buf.mGlyphs = mGlyphBuffer;
+ buf.mNumGlyphs = mNumGlyphs;
+
+ gfxContext::AzureState state = mRunParams.context->CurrentState();
+ if (mRunParams.drawMode & DrawMode::GLYPH_FILL) {
+ if (state.pattern || mFontParams.contextPaint) {
+ Pattern *pat;
+
+ RefPtr<gfxPattern> fillPattern;
+ if (!mFontParams.contextPaint ||
+ !(fillPattern = mFontParams.contextPaint->GetFillPattern(
+ mRunParams.context->GetDrawTarget(),
+ mRunParams.context->CurrentMatrix()))) {
+ if (state.pattern) {
+ pat = state.pattern->GetPattern(mRunParams.dt,
+ state.patternTransformChanged ?
+ &state.patternTransform : nullptr);
+ } else {
+ pat = nullptr;
+ }
+ } else {
+ pat = fillPattern->GetPattern(mRunParams.dt);
+ }
+
+ if (pat) {
+ Matrix saved;
+ Matrix *mat = nullptr;
+ if (mFontParams.passedInvMatrix) {
+ // The brush matrix needs to be multiplied with the
+ // inverted matrix as well, to move the brush into the
+ // space of the glyphs.
+
+ // This relies on the returned Pattern not to be reused
+ // by others, but regenerated on GetPattern calls. This
+ // is true!
+ if (pat->GetType() == PatternType::LINEAR_GRADIENT) {
+ mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) {
+ mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::SURFACE) {
+ mat = &static_cast<SurfacePattern*>(pat)->mMatrix;
+ }
+
+ if (mat) {
+ saved = *mat;
+ *mat = (*mat) * (*mFontParams.passedInvMatrix);
+ }
+ }
+
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
+ *pat, mFontParams.drawOptions,
+ mFontParams.renderingOptions);
+
+ if (mat) {
+ *mat = saved;
+ }
+ }
+ } else if (state.sourceSurface) {
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
+ SurfacePattern(state.sourceSurface,
+ ExtendMode::CLAMP,
+ state.surfTransform),
+ mFontParams.drawOptions,
+ mFontParams.renderingOptions);
+ } else {
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
+ ColorPattern(state.color),
+ mFontParams.drawOptions,
+ mFontParams.renderingOptions);
+ }
+ }
+ if (GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE &&
+ mRunParams.strokeOpts) {
+ Pattern *pat;
+ if (mRunParams.textStrokePattern) {
+ pat = mRunParams.textStrokePattern->GetPattern(
+ mRunParams.dt, state.patternTransformChanged
+ ? &state.patternTransform
+ : nullptr);
+
+ if (pat) {
+ Matrix saved;
+ Matrix *mat = nullptr;
+ if (mFontParams.passedInvMatrix) {
+ // The brush matrix needs to be multiplied with the
+ // inverted matrix as well, to move the brush into the
+ // space of the glyphs.
+
+ // This relies on the returned Pattern not to be reused
+ // by others, but regenerated on GetPattern calls. This
+ // is true!
+ if (pat->GetType() == PatternType::LINEAR_GRADIENT) {
+ mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) {
+ mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::SURFACE) {
+ mat = &static_cast<SurfacePattern*>(pat)->mMatrix;
+ }
+
+ if (mat) {
+ saved = *mat;
+ *mat = (*mat) * (*mFontParams.passedInvMatrix);
+ }
+ }
+ FlushStroke(buf, *pat);
+
+ if (mat) {
+ *mat = saved;
+ }
+ }
+ } else {
+ FlushStroke(buf,
+ ColorPattern(
+ Color::FromABGR(mRunParams.textStrokeColor)));
+ }
+ }
+ if (mRunParams.drawMode & DrawMode::GLYPH_PATH) {
+ mRunParams.context->EnsurePathBuilder();
+ Matrix mat = mRunParams.dt->GetTransform();
+ mFontParams.scaledFont->CopyGlyphsToBuilder(
+ buf, mRunParams.context->mPathBuilder, &mat);
+ }
+
+ mNumGlyphs = 0;
+ }
+
+ void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern)
+ {
+ RefPtr<Path> path =
+ mFontParams.scaledFont->GetPathForGlyphs(aBuf, mRunParams.dt);
+ mRunParams.dt->Stroke(path, aPattern, *mRunParams.strokeOpts,
+ (mRunParams.drawOpts) ? *mRunParams.drawOpts
+ : DrawOptions());
+ }
+
+ Glyph mGlyphBuffer[GLYPH_BUFFER_SIZE];
+ unsigned int mNumGlyphs;
+
+#undef GLYPH_BUFFER_SIZE
+};
+
+// Bug 674909. When synthetic bolding text by drawing twice, need to
+// render using a pixel offset in device pixels, otherwise text
+// doesn't appear bolded, it appears as if a bad text shadow exists
+// when a non-identity transform exists. Use an offset factor so that
+// the second draw occurs at a constant offset in device pixels.
+
+double
+gfxFont::CalcXScale(DrawTarget* aDrawTarget)
+{
+ // determine magnitude of a 1px x offset in device space
+ Size t = aDrawTarget->GetTransform().TransformSize(Size(1.0, 0.0));
+ if (t.width == 1.0 && t.height == 0.0) {
+ // short-circuit the most common case to avoid sqrt() and division
+ return 1.0;
+ }
+
+ double m = sqrt(t.width * t.width + t.height * t.height);
+
+ NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding");
+ if (m == 0.0) {
+ return 0.0; // effectively disables offset
+ }
+
+ // scale factor so that offsets are 1px in device pixels
+ return 1.0 / m;
+}
+
+// Draw an individual glyph at a specific location.
+// *aPt is the glyph position in appUnits; it is converted to device
+// coordinates (devPt) here.
+void
+gfxFont::DrawOneGlyph(uint32_t aGlyphID, double aAdvance, gfxPoint *aPt,
+ GlyphBufferAzure& aBuffer, bool *aEmittedGlyphs) const
+{
+ const TextRunDrawParams& runParams(aBuffer.mRunParams);
+ const FontDrawParams& fontParams(aBuffer.mFontParams);
+
+ double glyphX, glyphY;
+ if (fontParams.isVerticalFont) {
+ glyphX = aPt->x;
+ if (runParams.isRTL) {
+ aPt->y -= aAdvance;
+ glyphY = aPt->y;
+ } else {
+ glyphY = aPt->y;
+ aPt->y += aAdvance;
+ }
+ } else {
+ glyphY = aPt->y;
+ if (runParams.isRTL) {
+ aPt->x -= aAdvance;
+ glyphX = aPt->x;
+ } else {
+ glyphX = aPt->x;
+ aPt->x += aAdvance;
+ }
+ }
+ gfxPoint devPt(ToDeviceUnits(glyphX, runParams.devPerApp),
+ ToDeviceUnits(glyphY, runParams.devPerApp));
+
+ if (fontParams.haveSVGGlyphs) {
+ if (!runParams.paintSVGGlyphs) {
+ return;
+ }
+ NS_WARNING_ASSERTION(
+ runParams.drawMode != DrawMode::GLYPH_PATH,
+ "Rendering SVG glyph despite request for glyph path");
+ if (RenderSVGGlyph(runParams.context, devPt,
+ aGlyphID, fontParams.contextPaint,
+ runParams.callbacks, *aEmittedGlyphs)) {
+ return;
+ }
+ }
+
+ if (fontParams.haveColorGlyphs &&
+ RenderColorGlyph(runParams.dt, runParams.context,
+ fontParams.scaledFont, fontParams.renderingOptions,
+ fontParams.drawOptions,
+ fontParams.matInv.TransformPoint(gfx::Point(devPt.x, devPt.y)),
+ aGlyphID)) {
+ return;
+ }
+
+ aBuffer.OutputGlyph(aGlyphID, devPt);
+
+ // Synthetic bolding (if required) by multi-striking.
+ for (int32_t i = 0; i < fontParams.extraStrikes; ++i) {
+ if (fontParams.isVerticalFont) {
+ devPt.y += fontParams.synBoldOnePixelOffset;
+ } else {
+ devPt.x += fontParams.synBoldOnePixelOffset;
+ }
+ aBuffer.OutputGlyph(aGlyphID, devPt);
+ }
+
+ *aEmittedGlyphs = true;
+}
+
+// Draw a run of CharacterGlyph records from the given offset in aShapedText.
+// Returns true if glyph paths were actually emitted.
+bool
+gfxFont::DrawGlyphs(const gfxShapedText *aShapedText,
+ uint32_t aOffset, // offset in the textrun
+ uint32_t aCount, // length of run to draw
+ gfxPoint *aPt,
+ const TextRunDrawParams& aRunParams,
+ const FontDrawParams& aFontParams)
+{
+ bool emittedGlyphs = false;
+ GlyphBufferAzure buffer(aRunParams, aFontParams);
+
+ gfxFloat& inlineCoord = aFontParams.isVerticalFont ? aPt->y : aPt->x;
+
+ if (aRunParams.spacing) {
+ inlineCoord += aRunParams.isRTL ? -aRunParams.spacing[0].mBefore
+ : aRunParams.spacing[0].mBefore;
+ }
+
+ const gfxShapedText::CompressedGlyph *glyphData =
+ &aShapedText->GetCharacterGlyphs()[aOffset];
+
+ for (uint32_t i = 0; i < aCount; ++i, ++glyphData) {
+ if (glyphData->IsSimpleGlyph()) {
+ DrawOneGlyph(glyphData->GetSimpleGlyph(),
+ glyphData->GetSimpleAdvance(),
+ aPt, buffer, &emittedGlyphs);
+ } else {
+ uint32_t glyphCount = glyphData->GetGlyphCount();
+ if (glyphCount > 0) {
+ const gfxShapedText::DetailedGlyph *details =
+ aShapedText->GetDetailedGlyphs(aOffset + i);
+ NS_ASSERTION(details, "detailedGlyph should not be missing!");
+ for (uint32_t j = 0; j < glyphCount; ++j, ++details) {
+ double advance = details->mAdvance;
+
+ if (glyphData->IsMissing()) {
+ // Default-ignorable chars will have zero advance width;
+ // we don't have to draw the hexbox for them.
+ if (aRunParams.drawMode != DrawMode::GLYPH_PATH &&
+ advance > 0) {
+ double glyphX = aPt->x;
+ double glyphY = aPt->y;
+ if (aRunParams.isRTL) {
+ if (aFontParams.isVerticalFont) {
+ glyphY -= advance;
+ } else {
+ glyphX -= advance;
+ }
+ }
+ Point pt(Float(ToDeviceUnits(glyphX, aRunParams.devPerApp)),
+ Float(ToDeviceUnits(glyphY, aRunParams.devPerApp)));
+ Float advanceDevUnits =
+ Float(ToDeviceUnits(advance, aRunParams.devPerApp));
+ Float height = GetMetrics(eHorizontal).maxAscent;
+ Rect glyphRect = aFontParams.isVerticalFont ?
+ Rect(pt.x - height / 2, pt.y,
+ height, advanceDevUnits) :
+ Rect(pt.x, pt.y - height,
+ advanceDevUnits, height);
+
+ // If there's a fake-italic skew in effect as part
+ // of the drawTarget's transform, we need to remove
+ // this before drawing the hexbox. (Bug 983985)
+ Matrix oldMat;
+ if (aFontParams.passedInvMatrix) {
+ oldMat = aRunParams.dt->GetTransform();
+ aRunParams.dt->SetTransform(
+ *aFontParams.passedInvMatrix * oldMat);
+ }
+
+ gfxFontMissingGlyphs::DrawMissingGlyph(
+ details->mGlyphID, glyphRect, *aRunParams.dt,
+ PatternFromState(aRunParams.context),
+ aShapedText->GetAppUnitsPerDevUnit());
+
+ // Restore the matrix, if we modified it before
+ // drawing the hexbox.
+ if (aFontParams.passedInvMatrix) {
+ aRunParams.dt->SetTransform(oldMat);
+ }
+ }
+ } else {
+ gfxPoint glyphXY(*aPt);
+ if (aFontParams.isVerticalFont) {
+ glyphXY.x += details->mYOffset;
+ glyphXY.y += details->mXOffset;
+ } else {
+ glyphXY.x += details->mXOffset;
+ glyphXY.y += details->mYOffset;
+ }
+ DrawOneGlyph(details->mGlyphID, advance, &glyphXY,
+ buffer, &emittedGlyphs);
+ }
+
+ inlineCoord += aRunParams.isRTL ? -advance : advance;
+ }
+ }
+ }
+
+ if (aRunParams.spacing) {
+ double space = aRunParams.spacing[i].mAfter;
+ if (i + 1 < aCount) {
+ space += aRunParams.spacing[i + 1].mBefore;
+ }
+ inlineCoord += aRunParams.isRTL ? -space : space;
+ }
+ }
+
+ return emittedGlyphs;
+}
+
+// This method is mostly parallel to DrawGlyphs.
+void
+gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfxPoint* aPt,
+ uint32_t aOffset, uint32_t aCount,
+ const EmphasisMarkDrawParams& aParams)
+{
+ gfxFloat& inlineCoord = aParams.isVertical ? aPt->y : aPt->x;
+ gfxTextRun::Range markRange(aParams.mark);
+ gfxTextRun::DrawParams params(aParams.context);
+
+ gfxFloat clusterStart = -std::numeric_limits<gfxFloat>::infinity();
+ bool shouldDrawEmphasisMark = false;
+ for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) {
+ if (aParams.spacing) {
+ inlineCoord += aParams.direction * aParams.spacing[i].mBefore;
+ }
+ if (aShapedText->IsClusterStart(idx) ||
+ clusterStart == -std::numeric_limits<gfxFloat>::infinity()) {
+ clusterStart = inlineCoord;
+ }
+ if (aShapedText->CharMayHaveEmphasisMark(idx)) {
+ shouldDrawEmphasisMark = true;
+ }
+ inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx);
+ if (shouldDrawEmphasisMark &&
+ (i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) {
+ gfxFloat clusterAdvance = inlineCoord - clusterStart;
+ // Move the coord backward to get the needed start point.
+ gfxFloat delta = (clusterAdvance + aParams.advance) / 2;
+ inlineCoord -= delta;
+ aParams.mark->Draw(markRange, *aPt, params);
+ inlineCoord += delta;
+ shouldDrawEmphasisMark = false;
+ }
+ if (aParams.spacing) {
+ inlineCoord += aParams.direction * aParams.spacing[i].mAfter;
+ }
+ }
+}
+
+void
+gfxFont::Draw(const gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
+ gfxPoint *aPt, const TextRunDrawParams& aRunParams,
+ uint16_t aOrientation)
+{
+ NS_ASSERTION(aRunParams.drawMode == DrawMode::GLYPH_PATH ||
+ !(int(aRunParams.drawMode) & int(DrawMode::GLYPH_PATH)),
+ "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH");
+
+ if (aStart >= aEnd) {
+ return;
+ }
+
+ FontDrawParams fontParams;
+
+ if (aRunParams.drawOpts) {
+ fontParams.drawOptions = *aRunParams.drawOpts;
+ }
+
+ fontParams.scaledFont = GetScaledFont(aRunParams.dt);
+ if (!fontParams.scaledFont) {
+ return;
+ }
+
+ fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
+ fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
+ fontParams.contextPaint = aRunParams.runContextPaint;
+ fontParams.isVerticalFont =
+ aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+
+ bool sideways = false;
+ gfxPoint origPt = *aPt;
+ if (aRunParams.isVerticalRun && !fontParams.isVerticalFont) {
+ sideways = true;
+ aRunParams.context->Save();
+ gfxPoint p(aPt->x * aRunParams.devPerApp,
+ aPt->y * aRunParams.devPerApp);
+ const Metrics& metrics = GetMetrics(eHorizontal);
+ // Get a matrix we can use to draw the (horizontally-shaped) textrun
+ // with 90-degree CW rotation.
+ const gfxFloat
+ rotation = (aOrientation ==
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT)
+ ? -M_PI / 2.0 : M_PI / 2.0;
+ gfxMatrix mat =
+ aRunParams.context->CurrentMatrix().
+ Translate(p). // translate origin for rotation
+ Rotate(rotation). // turn 90deg CCW (sideways-left) or CW (*-right)
+ Translate(-p); // undo the translation
+
+ // If we're drawing rotated horizontal text for an element styled
+ // text-orientation:mixed, the dominant baseline will be vertical-
+ // centered. So in this case, we need to adjust the position so that
+ // the rotated horizontal text (which uses an alphabetic baseline) will
+ // look OK when juxtaposed with upright glyphs (rendered on a centered
+ // vertical baseline). The adjustment here is somewhat ad hoc; we
+ // should eventually look for baseline tables[1] in the fonts and use
+ // those if available.
+ // [1] See http://www.microsoft.com/typography/otspec/base.htm
+ if (aTextRun->UseCenterBaseline()) {
+ gfxPoint baseAdj(0, (metrics.emAscent - metrics.emDescent) / 2);
+ mat.Translate(baseAdj);
+ }
+
+ aRunParams.context->SetMatrix(mat);
+ }
+
+ UniquePtr<SVGContextPaint> contextPaint;
+ if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) {
+ // If no pattern is specified for fill, use the current pattern
+ NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0,
+ "no pattern supplied for stroking text");
+ RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern();
+ contextPaint.reset(
+ new SimpleTextContextPaint(fillPattern, nullptr,
+ aRunParams.context->CurrentMatrix()));
+ fontParams.contextPaint = contextPaint.get();
+ }
+
+ // Synthetic-bold strikes are each offset one device pixel in run direction.
+ // (these values are only needed if IsSyntheticBold() is true)
+ if (IsSyntheticBold()) {
+ double xscale = CalcXScale(aRunParams.context->GetDrawTarget());
+ fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale;
+ if (xscale != 0.0) {
+ // use as many strikes as needed for the the increased advance
+ fontParams.extraStrikes =
+ std::max(1, NS_lroundf(GetSyntheticBoldOffset() / xscale));
+ }
+ } else {
+ fontParams.synBoldOnePixelOffset = 0;
+ fontParams.extraStrikes = 0;
+ }
+
+ bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA();
+ if (!AllowSubpixelAA()) {
+ aRunParams.dt->SetPermitSubpixelAA(false);
+ }
+
+ Matrix mat;
+ Matrix oldMat = aRunParams.dt->GetTransform();
+
+ // This is nullptr when we have inverse-transformed glyphs and we need
+ // to transform the Brush inside flush.
+ fontParams.passedInvMatrix = nullptr;
+
+ fontParams.renderingOptions = GetGlyphRenderingOptions(&aRunParams);
+ fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
+
+ // The cairo DrawTarget backend uses the cairo_scaled_font directly
+ // and so has the font skew matrix applied already.
+ if (mScaledFont &&
+ aRunParams.dt->GetBackendType() != BackendType::CAIRO) {
+ cairo_matrix_t matrix;
+ cairo_scaled_font_get_font_matrix(mScaledFont, &matrix);
+ if (matrix.xy != 0) {
+ // If this matrix applies a skew, which can happen when drawing
+ // oblique fonts, we will set the DrawTarget matrix to apply the
+ // skew. We'll need to move the glyphs by the inverse of the skew to
+ // get the glyphs positioned correctly in the new device space
+ // though, since the font matrix should only be applied to drawing
+ // the glyphs, and not to their position.
+ mat = Matrix(matrix.xx, matrix.yx,
+ matrix.xy, matrix.yy,
+ matrix.x0, matrix.y0);
+
+ mat._11 = mat._22 = 1.0;
+ mat._21 /= GetAdjustedSize();
+
+ aRunParams.dt->SetTransform(mat * oldMat);
+
+ fontParams.matInv = mat;
+ fontParams.matInv.Invert();
+
+ fontParams.passedInvMatrix = &fontParams.matInv;
+ }
+ }
+
+ gfxFloat& baseline = fontParams.isVerticalFont ? aPt->x : aPt->y;
+ gfxFloat origBaseline = baseline;
+ if (mStyle.baselineOffset != 0.0) {
+ baseline +=
+ mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit();
+ }
+
+ bool emittedGlyphs =
+ DrawGlyphs(aTextRun, aStart, aEnd - aStart, aPt,
+ aRunParams, fontParams);
+
+ baseline = origBaseline;
+
+ if (aRunParams.callbacks && emittedGlyphs) {
+ aRunParams.callbacks->NotifyGlyphPathEmitted();
+ }
+
+ aRunParams.dt->SetTransform(oldMat);
+ aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA);
+
+ if (sideways) {
+ aRunParams.context->Restore();
+ // adjust updated aPt to account for the transform we were using
+ gfxFloat advance = aPt->x - origPt.x;
+ if (aOrientation ==
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT) {
+ *aPt = gfxPoint(origPt.x, origPt.y - advance);
+ } else {
+ *aPt = gfxPoint(origPt.x, origPt.y + advance);
+ }
+ }
+}
+
+bool
+gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint,
+ uint32_t aGlyphId, SVGContextPaint* aContextPaint) const
+{
+ if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
+ return false;
+ }
+
+ const gfxFloat devUnitsPerSVGUnit =
+ GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
+ gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
+
+ aContext->Save();
+ aContext->SetMatrix(
+ aContext->CurrentMatrix().Translate(aPoint.x, aPoint.y).
+ Scale(devUnitsPerSVGUnit, devUnitsPerSVGUnit));
+
+ aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit);
+
+ bool rv = GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId,
+ aContextPaint);
+ aContext->Restore();
+ aContext->NewPath();
+ return rv;
+}
+
+bool
+gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint,
+ uint32_t aGlyphId, SVGContextPaint* aContextPaint,
+ gfxTextRunDrawCallbacks *aCallbacks,
+ bool& aEmittedGlyphs) const
+{
+ if (aCallbacks && aEmittedGlyphs) {
+ aCallbacks->NotifyGlyphPathEmitted();
+ aEmittedGlyphs = false;
+ }
+ return RenderSVGGlyph(aContext, aPoint, aGlyphId, aContextPaint);
+}
+
+bool
+gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget,
+ gfxContext* aContext,
+ mozilla::gfx::ScaledFont* scaledFont,
+ GlyphRenderingOptions* aRenderingOptions,
+ mozilla::gfx::DrawOptions aDrawOptions,
+ const mozilla::gfx::Point& aPoint,
+ uint32_t aGlyphId) const
+{
+ AutoTArray<uint16_t, 8> layerGlyphs;
+ AutoTArray<mozilla::gfx::Color, 8> layerColors;
+
+ mozilla::gfx::Color defaultColor;
+ if (!aContext->GetDeviceColor(defaultColor)) {
+ defaultColor = mozilla::gfx::Color(0, 0, 0);
+ }
+ if (!GetFontEntry()->GetColorLayersInfo(aGlyphId, defaultColor,
+ layerGlyphs, layerColors)) {
+ return false;
+ }
+
+ for (uint32_t layerIndex = 0; layerIndex < layerGlyphs.Length();
+ layerIndex++) {
+ Glyph glyph;
+ glyph.mIndex = layerGlyphs[layerIndex];
+ glyph.mPosition = aPoint;
+
+ mozilla::gfx::GlyphBuffer buffer;
+ buffer.mGlyphs = &glyph;
+ buffer.mNumGlyphs = 1;
+
+ aDrawTarget->FillGlyphs(scaledFont, buffer,
+ ColorPattern(layerColors[layerIndex]),
+ aDrawOptions, aRenderingOptions);
+ }
+ return true;
+}
+
+static void
+UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax)
+{
+ *aDestMin = std::min(*aDestMin, aX);
+ *aDestMax = std::max(*aDestMax, aX);
+}
+
+// We get precise glyph extents if the textrun creator requested them, or
+// if the font is a user font --- in which case the author may be relying
+// on overflowing glyphs.
+static bool
+NeedsGlyphExtents(gfxFont *aFont, const gfxTextRun *aTextRun)
+{
+ return (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) ||
+ aFont->GetFontEntry()->IsUserFont();
+}
+
+bool
+gfxFont::IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget,
+ const gfxTextRun* aTextRun)
+{
+ if (!mFontEntry->mSpaceGlyphIsInvisibleInitialized &&
+ GetAdjustedSize() >= 1.0) {
+ gfxGlyphExtents *extents =
+ GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
+ gfxRect glyphExtents;
+ mFontEntry->mSpaceGlyphIsInvisible =
+ extents->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget,
+ GetSpaceGlyph(), &glyphExtents) &&
+ glyphExtents.IsEmpty();
+ mFontEntry->mSpaceGlyphIsInvisibleInitialized = true;
+ }
+ return mFontEntry->mSpaceGlyphIsInvisible;
+}
+
+gfxFont::RunMetrics
+gfxFont::Measure(const gfxTextRun *aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ Spacing *aSpacing,
+ uint16_t aOrientation)
+{
+ // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS
+ // and the underlying cairo font may be antialiased,
+ // we need to create a copy in order to avoid getting cached extents.
+ // This is only used by MathML layout at present.
+ if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS &&
+ mAntialiasOption != kAntialiasNone) {
+ if (!mNonAAFont) {
+ mNonAAFont = Move(CopyWithAntialiasOption(kAntialiasNone));
+ }
+ // if font subclass doesn't implement CopyWithAntialiasOption(),
+ // it will return null and we'll proceed to use the existing font
+ if (mNonAAFont) {
+ return mNonAAFont->Measure(aTextRun, aStart, aEnd,
+ TIGHT_HINTED_OUTLINE_EXTENTS,
+ aRefDrawTarget, aSpacing, aOrientation);
+ }
+ }
+
+ const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+ // Current position in appunits
+ gfxFont::Orientation orientation =
+ aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT
+ ? eVertical : eHorizontal;
+ const gfxFont::Metrics& fontMetrics = GetMetrics(orientation);
+
+ gfxFloat baselineOffset = 0;
+ if (aTextRun->UseCenterBaseline() && orientation == eHorizontal) {
+ // For a horizontal font being used in vertical writing mode with
+ // text-orientation:mixed, the overall metrics we're accumulating
+ // will be aimed at a center baseline. But this font's metrics were
+ // based on the alphabetic baseline. So we compute a baseline offset
+ // that will be applied to ascent/descent values and glyph rects
+ // to effectively shift them relative to the baseline.
+ // XXX Eventually we should probably use the BASE table, if present.
+ // But it usually isn't, so we need an ad hoc adjustment for now.
+ baselineOffset = appUnitsPerDevUnit *
+ (fontMetrics.emAscent - fontMetrics.emDescent) / 2;
+ }
+
+ RunMetrics metrics;
+ metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit;
+ metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit;
+
+ if (aStart == aEnd) {
+ // exit now before we look at aSpacing[0], which is undefined
+ metrics.mAscent -= baselineOffset;
+ metrics.mDescent += baselineOffset;
+ metrics.mBoundingBox = gfxRect(0, -metrics.mAscent,
+ 0, metrics.mAscent + metrics.mDescent);
+ return metrics;
+ }
+
+ gfxFloat advanceMin = 0, advanceMax = 0;
+ const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
+ bool isRTL = aTextRun->IsRightToLeft();
+ double direction = aTextRun->GetDirection();
+ bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun);
+ gfxGlyphExtents *extents =
+ ((aBoundingBoxType == LOOSE_INK_EXTENTS &&
+ !needsGlyphExtents &&
+ !aTextRun->HasDetailedGlyphs()) ||
+ (MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0)) ||
+ (MOZ_UNLIKELY(GetStyle()->size == 0))) ? nullptr
+ : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
+ double x = 0;
+ if (aSpacing) {
+ x += direction*aSpacing[0].mBefore;
+ }
+ uint32_t spaceGlyph = GetSpaceGlyph();
+ bool allGlyphsInvisible = true;
+ uint32_t i;
+ for (i = aStart; i < aEnd; ++i) {
+ const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i];
+ if (glyphData->IsSimpleGlyph()) {
+ double advance = glyphData->GetSimpleAdvance();
+ uint32_t glyphIndex = glyphData->GetSimpleGlyph();
+ if (glyphIndex != spaceGlyph ||
+ !IsSpaceGlyphInvisible(aRefDrawTarget, aTextRun)) {
+ allGlyphsInvisible = false;
+ }
+ // Only get the real glyph horizontal extent if we were asked
+ // for the tight bounding box or we're in quality mode
+ if ((aBoundingBoxType != LOOSE_INK_EXTENTS || needsGlyphExtents) &&
+ extents){
+ uint16_t extentsWidth = extents->GetContainedGlyphWidthAppUnits(glyphIndex);
+ if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH &&
+ aBoundingBoxType == LOOSE_INK_EXTENTS) {
+ UnionRange(x, &advanceMin, &advanceMax);
+ UnionRange(x + direction*extentsWidth, &advanceMin, &advanceMax);
+ } else {
+ gfxRect glyphRect;
+ if (!extents->GetTightGlyphExtentsAppUnits(this,
+ aRefDrawTarget, glyphIndex, &glyphRect)) {
+ glyphRect = gfxRect(0, metrics.mBoundingBox.Y(),
+ advance, metrics.mBoundingBox.Height());
+ }
+ if (orientation == eVertical) {
+ Swap(glyphRect.x, glyphRect.y);
+ Swap(glyphRect.width, glyphRect.height);
+ }
+ if (isRTL) {
+ glyphRect -= gfxPoint(advance, 0);
+ }
+ glyphRect += gfxPoint(x, 0);
+ metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
+ }
+ }
+ x += direction*advance;
+ } else {
+ allGlyphsInvisible = false;
+ uint32_t glyphCount = glyphData->GetGlyphCount();
+ if (glyphCount > 0) {
+ const gfxTextRun::DetailedGlyph *details =
+ aTextRun->GetDetailedGlyphs(i);
+ NS_ASSERTION(details != nullptr,
+ "detailedGlyph record should not be missing!");
+ uint32_t j;
+ for (j = 0; j < glyphCount; ++j, ++details) {
+ uint32_t glyphIndex = details->mGlyphID;
+ gfxPoint glyphPt(x + details->mXOffset, details->mYOffset);
+ double advance = details->mAdvance;
+ gfxRect glyphRect;
+ if (glyphData->IsMissing() || !extents ||
+ !extents->GetTightGlyphExtentsAppUnits(this,
+ aRefDrawTarget, glyphIndex, &glyphRect)) {
+ // We might have failed to get glyph extents due to
+ // OOM or something
+ glyphRect = gfxRect(0, -metrics.mAscent,
+ advance, metrics.mAscent + metrics.mDescent);
+ }
+ if (orientation == eVertical) {
+ Swap(glyphRect.x, glyphRect.y);
+ Swap(glyphRect.width, glyphRect.height);
+ }
+ if (isRTL) {
+ glyphRect -= gfxPoint(advance, 0);
+ }
+ glyphRect += glyphPt;
+ metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
+ x += direction*advance;
+ }
+ }
+ }
+ // Every other glyph type is ignored
+ if (aSpacing) {
+ double space = aSpacing[i - aStart].mAfter;
+ if (i + 1 < aEnd) {
+ space += aSpacing[i + 1 - aStart].mBefore;
+ }
+ x += direction*space;
+ }
+ }
+
+ if (allGlyphsInvisible) {
+ metrics.mBoundingBox.SetEmpty();
+ } else {
+ if (aBoundingBoxType == LOOSE_INK_EXTENTS) {
+ UnionRange(x, &advanceMin, &advanceMax);
+ gfxRect fontBox(advanceMin, -metrics.mAscent,
+ advanceMax - advanceMin, metrics.mAscent + metrics.mDescent);
+ metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox);
+ }
+ if (isRTL) {
+ metrics.mBoundingBox -= gfxPoint(x, 0);
+ }
+ }
+
+ // If the font may be rendered with a fake-italic effect, we need to allow
+ // for the top-right of the glyphs being skewed to the right, and the
+ // bottom-left being skewed further left.
+ if (mStyle.style != NS_FONT_STYLE_NORMAL &&
+ mFontEntry->IsUpright() &&
+ mStyle.allowSyntheticStyle) {
+ gfxFloat extendLeftEdge =
+ ceil(OBLIQUE_SKEW_FACTOR * metrics.mBoundingBox.YMost());
+ gfxFloat extendRightEdge =
+ ceil(OBLIQUE_SKEW_FACTOR * -metrics.mBoundingBox.y);
+ metrics.mBoundingBox.width += extendLeftEdge + extendRightEdge;
+ metrics.mBoundingBox.x -= extendLeftEdge;
+ }
+
+ if (baselineOffset != 0) {
+ metrics.mAscent -= baselineOffset;
+ metrics.mDescent += baselineOffset;
+ metrics.mBoundingBox.y += baselineOffset;
+ }
+
+ metrics.mAdvanceWidth = x*direction;
+ return metrics;
+}
+
+void
+gfxFont::AgeCachedWords()
+{
+ if (mWordCache) {
+ for (auto it = mWordCache->Iter(); !it.Done(); it.Next()) {
+ CacheHashEntry *entry = it.Get();
+ if (!entry->mShapedWord) {
+ NS_ASSERTION(entry->mShapedWord,
+ "cache entry has no gfxShapedWord!");
+ it.Remove();
+ } else if (entry->mShapedWord->IncrementAge() ==
+ kShapedWordCacheMaxAge) {
+ it.Remove();
+ }
+ }
+ }
+}
+
+void
+gfxFont::NotifyGlyphsChanged()
+{
+ uint32_t i, count = mGlyphExtentsArray.Length();
+ for (i = 0; i < count; ++i) {
+ // Flush cached extents array
+ mGlyphExtentsArray[i]->NotifyGlyphsChanged();
+ }
+
+ if (mGlyphChangeObservers) {
+ for (auto it = mGlyphChangeObservers->Iter(); !it.Done(); it.Next()) {
+ it.Get()->GetKey()->NotifyGlyphsChanged();
+ }
+ }
+}
+
+// If aChar is a "word boundary" for shaped-word caching purposes, return it;
+// else return 0.
+static char16_t
+IsBoundarySpace(char16_t aChar, char16_t aNextChar)
+{
+ if ((aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar)) {
+ return aChar;
+ }
+ return 0;
+}
+
+#ifdef __GNUC__
+#define GFX_MAYBE_UNUSED __attribute__((unused))
+#else
+#define GFX_MAYBE_UNUSED
+#endif
+
+template<typename T>
+gfxShapedWord*
+gfxFont::GetShapedWord(DrawTarget *aDrawTarget,
+ const T *aText,
+ uint32_t aLength,
+ uint32_t aHash,
+ Script aRunScript,
+ bool aVertical,
+ int32_t aAppUnitsPerDevUnit,
+ uint32_t aFlags,
+ gfxTextPerfMetrics *aTextPerf GFX_MAYBE_UNUSED)
+{
+ // if the cache is getting too big, flush it and start over
+ uint32_t wordCacheMaxEntries =
+ gfxPlatform::GetPlatform()->WordCacheMaxEntries();
+ if (mWordCache->Count() > wordCacheMaxEntries) {
+ NS_WARNING("flushing shaped-word cache");
+ ClearCachedWords();
+ }
+
+ // if there's a cached entry for this word, just return it
+ CacheHashKey key(aText, aLength, aHash,
+ aRunScript,
+ aAppUnitsPerDevUnit,
+ aFlags);
+
+ CacheHashEntry *entry = mWordCache->PutEntry(key);
+ if (!entry) {
+ NS_WARNING("failed to create word cache entry - expect missing text");
+ return nullptr;
+ }
+ gfxShapedWord* sw = entry->mShapedWord.get();
+
+ bool isContent = !mStyle.systemFont;
+
+ if (sw) {
+ sw->ResetAge();
+ Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_HITS_CONTENT :
+ Telemetry::WORD_CACHE_HITS_CHROME),
+ aLength);
+#ifndef RELEASE_OR_BETA
+ if (aTextPerf) {
+ aTextPerf->current.wordCacheHit++;
+ }
+#endif
+ return sw;
+ }
+
+ Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_MISSES_CONTENT :
+ Telemetry::WORD_CACHE_MISSES_CHROME),
+ aLength);
+#ifndef RELEASE_OR_BETA
+ if (aTextPerf) {
+ aTextPerf->current.wordCacheMiss++;
+ }
+#endif
+
+ sw = gfxShapedWord::Create(aText, aLength, aRunScript, aAppUnitsPerDevUnit,
+ aFlags);
+ entry->mShapedWord.reset(sw);
+ if (!sw) {
+ NS_WARNING("failed to create gfxShapedWord - expect missing text");
+ return nullptr;
+ }
+
+ DebugOnly<bool> ok =
+ ShapeText(aDrawTarget, aText, 0, aLength, aRunScript, aVertical, sw);
+
+ NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text");
+
+ return sw;
+}
+
+bool
+gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
+{
+ const gfxShapedWord* sw = mShapedWord.get();
+ if (!sw) {
+ return false;
+ }
+ if (sw->GetLength() != aKey->mLength ||
+ sw->GetFlags() != aKey->mFlags ||
+ sw->GetAppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit ||
+ sw->GetScript() != aKey->mScript) {
+ return false;
+ }
+ if (sw->TextIs8Bit()) {
+ if (aKey->mTextIs8Bit) {
+ return (0 == memcmp(sw->Text8Bit(), aKey->mText.mSingle,
+ aKey->mLength * sizeof(uint8_t)));
+ }
+ // The key has 16-bit text, even though all the characters are < 256,
+ // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
+ // comparing with will have 8-bit text.
+ const uint8_t *s1 = sw->Text8Bit();
+ const char16_t *s2 = aKey->mText.mDouble;
+ const char16_t *s2end = s2 + aKey->mLength;
+ while (s2 < s2end) {
+ if (*s1++ != *s2++) {
+ return false;
+ }
+ }
+ return true;
+ }
+ NS_ASSERTION((aKey->mFlags & gfxTextRunFactory::TEXT_IS_8BIT) == 0 &&
+ !aKey->mTextIs8Bit, "didn't expect 8-bit text here");
+ return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble,
+ aKey->mLength * sizeof(char16_t)));
+}
+
+bool
+gfxFont::ShapeText(DrawTarget *aDrawTarget,
+ const uint8_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ nsDependentCSubstring ascii((const char*)aText, aLength);
+ nsAutoString utf16;
+ AppendASCIItoUTF16(ascii, utf16);
+ if (utf16.Length() != aLength) {
+ return false;
+ }
+ return ShapeText(aDrawTarget, utf16.BeginReading(), aOffset, aLength,
+ aScript, aVertical, aShapedText);
+}
+
+bool
+gfxFont::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ bool ok = false;
+
+ // XXX Currently, we do all vertical shaping through harfbuzz.
+ // Vertical graphite support may be wanted as a future enhancement.
+ if (FontCanSupportGraphite() && !aVertical) {
+ if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ if (!mGraphiteShaper) {
+ mGraphiteShaper = MakeUnique<gfxGraphiteShaper>(this);
+ }
+ ok = mGraphiteShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
+ aScript, aVertical, aShapedText);
+ }
+ }
+
+ if (!ok) {
+ if (!mHarfBuzzShaper) {
+ mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
+ }
+ ok = mHarfBuzzShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
+ aScript, aVertical, aShapedText);
+ }
+
+ NS_WARNING_ASSERTION(ok, "shaper failed, expect scrambled or missing text");
+
+ PostShapingFixup(aDrawTarget, aText, aOffset, aLength,
+ aVertical, aShapedText);
+
+ return ok;
+}
+
+void
+gfxFont::PostShapingFixup(DrawTarget* aDrawTarget,
+ const char16_t* aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ bool aVertical,
+ gfxShapedText* aShapedText)
+{
+ if (IsSyntheticBold()) {
+ const Metrics& metrics =
+ GetMetrics(aVertical ? eVertical : eHorizontal);
+ if (metrics.maxAdvance > metrics.aveCharWidth) {
+ float synBoldOffset =
+ GetSyntheticBoldOffset() * CalcXScale(aDrawTarget);
+ aShapedText->AdjustAdvancesForSyntheticBold(synBoldOffset,
+ aOffset, aLength);
+ }
+ }
+}
+
+#define MAX_SHAPING_LENGTH 32760 // slightly less than 32K, trying to avoid
+ // over-stressing platform shapers
+#define BACKTRACK_LIMIT 16 // backtrack this far looking for a good place
+ // to split into fragments for separate shaping
+
+template<typename T>
+bool
+gfxFont::ShapeFragmentWithoutWordCache(DrawTarget *aDrawTarget,
+ const T *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxTextRun *aTextRun)
+{
+ aTextRun->SetupClusterBoundaries(aOffset, aText, aLength);
+
+ bool ok = true;
+
+ while (ok && aLength > 0) {
+ uint32_t fragLen = aLength;
+
+ // limit the length of text we pass to shapers in a single call
+ if (fragLen > MAX_SHAPING_LENGTH) {
+ fragLen = MAX_SHAPING_LENGTH;
+
+ // in the 8-bit case, there are no multi-char clusters,
+ // so we don't need to do this check
+ if (sizeof(T) == sizeof(char16_t)) {
+ uint32_t i;
+ for (i = 0; i < BACKTRACK_LIMIT; ++i) {
+ if (aTextRun->IsClusterStart(aOffset + fragLen - i)) {
+ fragLen -= i;
+ break;
+ }
+ }
+ if (i == BACKTRACK_LIMIT) {
+ // if we didn't find any cluster start while backtracking,
+ // just check that we're not in the middle of a surrogate
+ // pair; back up by one code unit if we are.
+ if (NS_IS_LOW_SURROGATE(aText[fragLen]) &&
+ NS_IS_HIGH_SURROGATE(aText[fragLen - 1])) {
+ --fragLen;
+ }
+ }
+ }
+ }
+
+ ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aVertical,
+ aTextRun);
+
+ aText += fragLen;
+ aOffset += fragLen;
+ aLength -= fragLen;
+ }
+
+ return ok;
+}
+
+// Check if aCh is an unhandled control character that should be displayed
+// as a hexbox rather than rendered by some random font on the system.
+// We exclude \r as stray s are rather common (bug 941940).
+// Note that \n and \t don't come through here, as they have specific
+// meanings that have already been handled.
+static bool
+IsInvalidControlChar(uint32_t aCh)
+{
+ return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f);
+}
+
+template<typename T>
+bool
+gfxFont::ShapeTextWithoutWordCache(DrawTarget *aDrawTarget,
+ const T *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxTextRun *aTextRun)
+{
+ uint32_t fragStart = 0;
+ bool ok = true;
+
+ for (uint32_t i = 0; i <= aLength && ok; ++i) {
+ T ch = (i < aLength) ? aText[i] : '\n';
+ bool invalid = gfxFontGroup::IsInvalidChar(ch);
+ uint32_t length = i - fragStart;
+
+ // break into separate fragments when we hit an invalid char
+ if (!invalid) {
+ continue;
+ }
+
+ if (length > 0) {
+ ok = ShapeFragmentWithoutWordCache(aDrawTarget, aText + fragStart,
+ aOffset + fragStart, length,
+ aScript, aVertical, aTextRun);
+ }
+
+ if (i == aLength) {
+ break;
+ }
+
+ // fragment was terminated by an invalid char: skip it,
+ // unless it's a control char that we want to show as a hexbox,
+ // but record where TAB or NEWLINE occur
+ if (ch == '\t') {
+ aTextRun->SetIsTab(aOffset + i);
+ } else if (ch == '\n') {
+ aTextRun->SetIsNewline(aOffset + i);
+ } else if (IsInvalidControlChar(ch) &&
+ !(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS)) {
+ if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
+ ShapeFragmentWithoutWordCache(aDrawTarget, aText + i,
+ aOffset + i, 1,
+ aScript, aVertical, aTextRun);
+ } else {
+ aTextRun->SetMissingGlyph(aOffset + i, ch, this);
+ }
+ }
+ fragStart = i + 1;
+ }
+
+ NS_WARNING_ASSERTION(ok, "failed to shape text - expect garbled text");
+ return ok;
+}
+
+#ifndef RELEASE_OR_BETA
+#define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0)
+#else
+#define TEXT_PERF_INCR(tp, m)
+#endif
+
+inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; }
+inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; }
+
+inline static bool HasSpaces(const uint8_t *aString, uint32_t aLen)
+{
+ return memchr(aString, 0x20, aLen) != nullptr;
+}
+
+inline static bool HasSpaces(const char16_t *aString, uint32_t aLen)
+{
+ for (const char16_t *ch = aString; ch < aString + aLen; ch++) {
+ if (*ch == 0x20) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template<typename T>
+bool
+gfxFont::SplitAndInitTextRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const T *aString, // text for this font run
+ uint32_t aRunStart, // position in the textrun
+ uint32_t aRunLength,
+ Script aRunScript,
+ bool aVertical)
+{
+ if (aRunLength == 0) {
+ return true;
+ }
+
+ gfxTextPerfMetrics *tp = nullptr;
+
+#ifndef RELEASE_OR_BETA
+ tp = aTextRun->GetFontGroup()->GetTextPerfMetrics();
+ if (tp) {
+ if (mStyle.systemFont) {
+ tp->current.numChromeTextRuns++;
+ } else {
+ tp->current.numContentTextRuns++;
+ }
+ tp->current.numChars += aRunLength;
+ if (aRunLength > tp->current.maxTextRunLen) {
+ tp->current.maxTextRunLen = aRunLength;
+ }
+ }
+#endif
+
+ uint32_t wordCacheCharLimit =
+ gfxPlatform::GetPlatform()->WordCacheCharLimit();
+
+ // If spaces can participate in shaping (e.g. within lookups for automatic
+ // fractions), need to shape without using the word cache which segments
+ // textruns on space boundaries. Word cache can be used if the textrun
+ // is short enough to fit in the word cache and it lacks spaces.
+ if (SpaceMayParticipateInShaping(aRunScript)) {
+ if (aRunLength > wordCacheCharLimit ||
+ HasSpaces(aString, aRunLength)) {
+ TEXT_PERF_INCR(tp, wordCacheSpaceRules);
+ return ShapeTextWithoutWordCache(aDrawTarget, aString,
+ aRunStart, aRunLength,
+ aRunScript, aVertical,
+ aTextRun);
+ }
+ }
+
+ InitWordCache();
+
+ // the only flags we care about for ShapedWord construction/caching
+ uint32_t flags = aTextRun->GetFlags();
+ flags &= (gfxTextRunFactory::TEXT_IS_RTL |
+ gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES |
+ gfxTextRunFactory::TEXT_USE_MATH_SCRIPT |
+ gfxTextRunFactory::TEXT_ORIENT_MASK);
+ if (sizeof(T) == sizeof(uint8_t)) {
+ flags |= gfxTextRunFactory::TEXT_IS_8BIT;
+ }
+
+ uint32_t wordStart = 0;
+ uint32_t hash = 0;
+ bool wordIs8Bit = true;
+ int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+
+ T nextCh = aString[0];
+ for (uint32_t i = 0; i <= aRunLength; ++i) {
+ T ch = nextCh;
+ nextCh = (i < aRunLength - 1) ? aString[i + 1] : '\n';
+ T boundary = IsBoundarySpace(ch, nextCh);
+ bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch);
+ uint32_t length = i - wordStart;
+
+ // break into separate ShapedWords when we hit an invalid char,
+ // or a boundary space (always handled individually),
+ // or the first non-space after a space
+ if (!boundary && !invalid) {
+ if (!IsChar8Bit(ch)) {
+ wordIs8Bit = false;
+ }
+ // include this character in the hash, and move on to next
+ hash = gfxShapedWord::HashMix(hash, ch);
+ continue;
+ }
+
+ // We've decided to break here (i.e. we're at the end of a "word");
+ // shape the word and add it to the textrun.
+ // For words longer than the limit, we don't use the
+ // font's word cache but just shape directly into the textrun.
+ if (length > wordCacheCharLimit) {
+ TEXT_PERF_INCR(tp, wordCacheLong);
+ bool ok = ShapeFragmentWithoutWordCache(aDrawTarget,
+ aString + wordStart,
+ aRunStart + wordStart,
+ length,
+ aRunScript,
+ aVertical,
+ aTextRun);
+ if (!ok) {
+ return false;
+ }
+ } else if (length > 0) {
+ uint32_t wordFlags = flags;
+ // in the 8-bit version of this method, TEXT_IS_8BIT was
+ // already set as part of |flags|, so no need for a per-word
+ // adjustment here
+ if (sizeof(T) == sizeof(char16_t)) {
+ if (wordIs8Bit) {
+ wordFlags |= gfxTextRunFactory::TEXT_IS_8BIT;
+ }
+ }
+ gfxShapedWord* sw = GetShapedWord(aDrawTarget,
+ aString + wordStart, length,
+ hash, aRunScript, aVertical,
+ appUnitsPerDevUnit,
+ wordFlags, tp);
+ if (sw) {
+ aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart);
+ } else {
+ return false; // failed, presumably out of memory?
+ }
+ }
+
+ if (boundary) {
+ // word was terminated by a space: add that to the textrun
+ uint16_t orientation = flags & gfxTextRunFactory::TEXT_ORIENT_MASK;
+ if (orientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED) {
+ orientation = aVertical ?
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT :
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ }
+ if (boundary != ' ' ||
+ !aTextRun->SetSpaceGlyphIfSimple(this, aRunStart + i, ch,
+ orientation)) {
+ // Currently, the only "boundary" characters we recognize are
+ // space and no-break space, which are both 8-bit, so we force
+ // that flag (below). If we ever change IsBoundarySpace, we
+ // may need to revise this.
+ // Avoid tautological-constant-out-of-range-compare in 8-bit:
+ DebugOnly<char16_t> boundary16 = boundary;
+ NS_ASSERTION(boundary16 < 256, "unexpected boundary!");
+ gfxShapedWord *sw =
+ GetShapedWord(aDrawTarget, &boundary, 1,
+ gfxShapedWord::HashMix(0, boundary),
+ aRunScript, aVertical, appUnitsPerDevUnit,
+ flags | gfxTextRunFactory::TEXT_IS_8BIT, tp);
+ if (sw) {
+ aTextRun->CopyGlyphDataFrom(sw, aRunStart + i);
+ } else {
+ return false;
+ }
+ }
+ hash = 0;
+ wordStart = i + 1;
+ wordIs8Bit = true;
+ continue;
+ }
+
+ if (i == aRunLength) {
+ break;
+ }
+
+ NS_ASSERTION(invalid,
+ "how did we get here except via an invalid char?");
+
+ // word was terminated by an invalid char: skip it,
+ // unless it's a control char that we want to show as a hexbox,
+ // but record where TAB or NEWLINE occur
+ if (ch == '\t') {
+ aTextRun->SetIsTab(aRunStart + i);
+ } else if (ch == '\n') {
+ aTextRun->SetIsNewline(aRunStart + i);
+ } else if (IsInvalidControlChar(ch) &&
+ !(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS)) {
+ if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
+ ShapeFragmentWithoutWordCache(aDrawTarget, aString + i,
+ aRunStart + i, 1,
+ aRunScript, aVertical, aTextRun);
+ } else {
+ aTextRun->SetMissingGlyph(aRunStart + i, ch, this);
+ }
+ }
+
+ hash = 0;
+ wordStart = i + 1;
+ wordIs8Bit = true;
+ }
+
+ return true;
+}
+
+// Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure
+template bool
+gfxFont::SplitAndInitTextRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const uint8_t *aString,
+ uint32_t aRunStart,
+ uint32_t aRunLength,
+ Script aRunScript,
+ bool aVertical);
+template bool
+gfxFont::SplitAndInitTextRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const char16_t *aString,
+ uint32_t aRunStart,
+ uint32_t aRunLength,
+ Script aRunScript,
+ bool aVertical);
+
+template<>
+bool
+gfxFont::InitFakeSmallCapsRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ uint8_t aMatchType,
+ uint16_t aOrientation,
+ Script aScript,
+ bool aSyntheticLower,
+ bool aSyntheticUpper)
+{
+ bool ok = true;
+
+ RefPtr<gfxFont> smallCapsFont = GetSmallCapsFont();
+ if (!smallCapsFont) {
+ NS_WARNING("failed to get reduced-size font for smallcaps!");
+ smallCapsFont = this;
+ }
+
+ enum RunCaseAction {
+ kNoChange,
+ kUppercaseReduce,
+ kUppercase
+ };
+
+ RunCaseAction runAction = kNoChange;
+ uint32_t runStart = 0;
+ bool vertical =
+ aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+
+ for (uint32_t i = 0; i <= aLength; ++i) {
+ uint32_t extraCodeUnits = 0; // Will be set to 1 if we need to consume
+ // a trailing surrogate as well as the
+ // current code unit.
+ RunCaseAction chAction = kNoChange;
+ // Unless we're at the end, figure out what treatment the current
+ // character will need.
+ if (i < aLength) {
+ uint32_t ch = aText[i];
+ if (NS_IS_HIGH_SURROGATE(ch) && i < aLength - 1 &&
+ NS_IS_LOW_SURROGATE(aText[i + 1])) {
+ ch = SURROGATE_TO_UCS4(ch, aText[i + 1]);
+ extraCodeUnits = 1;
+ }
+ // Characters that aren't the start of a cluster are ignored here.
+ // They get added to whatever lowercase/non-lowercase run we're in.
+ if (IsClusterExtender(ch)) {
+ chAction = runAction;
+ } else {
+ if (ch != ToUpperCase(ch) || SpecialUpper(ch)) {
+ // ch is lower case
+ chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange);
+ } else if (ch != ToLowerCase(ch)) {
+ // ch is upper case
+ chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange);
+ if (mStyle.explicitLanguage &&
+ mStyle.language == nsGkAtoms::el) {
+ // In Greek, check for characters that will be modified by
+ // the GreekUpperCase mapping - this catches accented
+ // capitals where the accent is to be removed (bug 307039).
+ // These are handled by using the full-size font with the
+ // uppercasing transform.
+ mozilla::GreekCasing::State state;
+ bool markEta, updateEta;
+ uint32_t ch2 =
+ mozilla::GreekCasing::UpperCase(ch, state, markEta,
+ updateEta);
+ if ((ch != ch2 || markEta) && !aSyntheticUpper) {
+ chAction = kUppercase;
+ }
+ }
+ }
+ }
+ }
+
+ // At the end of the text or when the current character needs different
+ // casing treatment from the current run, finish the run-in-progress
+ // and prepare to accumulate a new run.
+ // Note that we do not look at any source data for offset [i] here,
+ // as that would be invalid in the case where i==length.
+ if ((i == aLength || runAction != chAction) && runStart < i) {
+ uint32_t runLength = i - runStart;
+ gfxFont* f = this;
+ switch (runAction) {
+ case kNoChange:
+ // just use the current font and the existing string
+ aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true,
+ aOrientation);
+ if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
+ aText + runStart,
+ aOffset + runStart, runLength,
+ aScript, vertical)) {
+ ok = false;
+ }
+ break;
+
+ case kUppercaseReduce:
+ // use reduced-size font, then fall through to uppercase the text
+ f = smallCapsFont;
+ MOZ_FALLTHROUGH;
+
+ case kUppercase:
+ // apply uppercase transform to the string
+ nsDependentSubstring origString(aText + runStart, runLength);
+ nsAutoString convertedString;
+ AutoTArray<bool,50> charsToMergeArray;
+ AutoTArray<bool,50> deletedCharsArray;
+
+ bool mergeNeeded = nsCaseTransformTextRunFactory::
+ TransformString(origString,
+ convertedString,
+ true,
+ mStyle.explicitLanguage
+ ? mStyle.language.get() : nullptr,
+ charsToMergeArray,
+ deletedCharsArray);
+
+ if (mergeNeeded) {
+ // This is the hard case: the transformation caused chars
+ // to be inserted or deleted, so we can't shape directly
+ // into the destination textrun but have to handle the
+ // mismatch of character positions.
+ gfxTextRunFactory::Parameters params = {
+ aDrawTarget, nullptr, nullptr, nullptr, 0,
+ aTextRun->GetAppUnitsPerDevUnit()
+ };
+ RefPtr<gfxTextRun> tempRun(
+ gfxTextRun::Create(¶ms, convertedString.Length(),
+ aTextRun->GetFontGroup(), 0));
+ tempRun->AddGlyphRun(f, aMatchType, 0, true, aOrientation);
+ if (!f->SplitAndInitTextRun(aDrawTarget, tempRun.get(),
+ convertedString.BeginReading(),
+ 0, convertedString.Length(),
+ aScript, vertical)) {
+ ok = false;
+ } else {
+ RefPtr<gfxTextRun> mergedRun(
+ gfxTextRun::Create(¶ms, runLength,
+ aTextRun->GetFontGroup(), 0));
+ MergeCharactersInTextRun(mergedRun.get(), tempRun.get(),
+ charsToMergeArray.Elements(),
+ deletedCharsArray.Elements());
+ gfxTextRun::Range runRange(0, runLength);
+ aTextRun->CopyGlyphDataFrom(mergedRun.get(), runRange,
+ aOffset + runStart);
+ }
+ } else {
+ aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart,
+ true, aOrientation);
+ if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
+ convertedString.BeginReading(),
+ aOffset + runStart, runLength,
+ aScript, vertical)) {
+ ok = false;
+ }
+ }
+ break;
+ }
+
+ runStart = i;
+ }
+
+ i += extraCodeUnits;
+ if (i < aLength) {
+ runAction = chAction;
+ }
+ }
+
+ return ok;
+}
+
+template<>
+bool
+gfxFont::InitFakeSmallCapsRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const uint8_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ uint8_t aMatchType,
+ uint16_t aOrientation,
+ Script aScript,
+ bool aSyntheticLower,
+ bool aSyntheticUpper)
+{
+ NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText),
+ aLength);
+ return InitFakeSmallCapsRun(aDrawTarget, aTextRun, static_cast<const char16_t*>(unicodeString.get()),
+ aOffset, aLength, aMatchType, aOrientation,
+ aScript, aSyntheticLower, aSyntheticUpper);
+}
+
+already_AddRefed<gfxFont>
+gfxFont::GetSmallCapsFont()
+{
+ gfxFontStyle style(*GetStyle());
+ style.size *= SMALL_CAPS_SCALE_FACTOR;
+ style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
+ gfxFontEntry* fe = GetFontEntry();
+ bool needsBold = style.weight >= 600 && !fe->IsBold();
+ return fe->FindOrMakeFont(&style, needsBold, mUnicodeRangeMap);
+}
+
+already_AddRefed<gfxFont>
+gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel)
+{
+ gfxFontStyle style(*GetStyle());
+ style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
+ gfxFontEntry* fe = GetFontEntry();
+ bool needsBold = style.weight >= 600 && !fe->IsBold();
+ return fe->FindOrMakeFont(&style, needsBold, mUnicodeRangeMap);
+}
+
+static void
+DestroyRefCairo(void* aData)
+{
+ cairo_t* refCairo = static_cast<cairo_t*>(aData);
+ MOZ_ASSERT(refCairo);
+ cairo_destroy(refCairo);
+}
+
+/* static */ cairo_t *
+gfxFont::RefCairo(DrawTarget* aDT)
+{
+ // DrawTargets that don't use a Cairo backend can be given a 1x1 "reference"
+ // |cairo_t*|, stored in the DrawTarget's user data, for doing font-related
+ // operations.
+ static UserDataKey sRefCairo;
+
+ cairo_t* refCairo = nullptr;
+ if (aDT->GetBackendType() == BackendType::CAIRO) {
+ refCairo = static_cast<cairo_t*>
+ (aDT->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
+ if (refCairo) {
+ return refCairo;
+ }
+ }
+
+ refCairo = static_cast<cairo_t*>(aDT->GetUserData(&sRefCairo));
+ if (!refCairo) {
+ refCairo = cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface());
+ aDT->AddUserData(&sRefCairo, refCairo, DestroyRefCairo);
+ }
+
+ return refCairo;
+}
+
+gfxGlyphExtents *
+gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) {
+ uint32_t i, count = mGlyphExtentsArray.Length();
+ for (i = 0; i < count; ++i) {
+ if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
+ return mGlyphExtentsArray[i].get();
+ }
+ gfxGlyphExtents *glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit);
+ if (glyphExtents) {
+ mGlyphExtentsArray.AppendElement(glyphExtents);
+ // Initialize the extents of a space glyph, assuming that spaces don't
+ // render anything!
+ glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0);
+ }
+ return glyphExtents;
+}
+
+void
+gfxFont::SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID,
+ bool aNeedTight, gfxGlyphExtents *aExtents)
+{
+ gfxRect svgBounds;
+ if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) &&
+ mFontEntry->GetSVGGlyphExtents(aDrawTarget, aGlyphID, &svgBounds)) {
+ gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
+ aExtents->SetTightGlyphExtents(aGlyphID,
+ gfxRect(svgBounds.x * d2a,
+ svgBounds.y * d2a,
+ svgBounds.width * d2a,
+ svgBounds.height * d2a));
+ return;
+ }
+
+ cairo_glyph_t glyph;
+ glyph.index = aGlyphID;
+ glyph.x = 0;
+ glyph.y = 0;
+ cairo_text_extents_t extents;
+ cairo_glyph_extents(gfxFont::RefCairo(aDrawTarget), &glyph, 1, &extents);
+
+ const Metrics& fontMetrics = GetMetrics(eHorizontal);
+ int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
+ if (!aNeedTight && extents.x_bearing >= 0 &&
+ extents.y_bearing >= -fontMetrics.maxAscent &&
+ extents.height + extents.y_bearing <= fontMetrics.maxDescent) {
+ uint32_t appUnitsWidth =
+ uint32_t(ceil((extents.x_bearing + extents.width)*appUnitsPerDevUnit));
+ if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
+ aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, uint16_t(appUnitsWidth));
+ return;
+ }
+ }
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ if (!aNeedTight) {
+ ++gGlyphExtentsSetupFallBackToTight;
+ }
+#endif
+
+ gfxFloat d2a = appUnitsPerDevUnit;
+ gfxRect bounds(extents.x_bearing*d2a, extents.y_bearing*d2a,
+ extents.width*d2a, extents.height*d2a);
+ aExtents->SetTightGlyphExtents(aGlyphID, bounds);
+}
+
+// Try to initialize font metrics by reading sfnt tables directly;
+// set mIsValid=TRUE and return TRUE on success.
+// Return FALSE if the gfxFontEntry subclass does not
+// implement GetFontTable(), or for non-sfnt fonts where tables are
+// not available.
+// If this returns TRUE without setting the mIsValid flag, then we -did-
+// apparently find an sfnt, but it was too broken to be used.
+bool
+gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics)
+{
+ mIsValid = false; // font is NOT valid in case of early return
+
+ const uint32_t kHheaTableTag = TRUETYPE_TAG('h','h','e','a');
+ const uint32_t kPostTableTag = TRUETYPE_TAG('p','o','s','t');
+ const uint32_t kOS_2TableTag = TRUETYPE_TAG('O','S','/','2');
+
+ uint32_t len;
+
+ if (mFUnitsConvFactor < 0.0) {
+ // If the conversion factor from FUnits is not yet set,
+ // get the unitsPerEm from the 'head' table via the font entry
+ uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm();
+ if (unitsPerEm == gfxFontEntry::kInvalidUPEM) {
+ return false;
+ }
+ mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm;
+ }
+
+ // 'hhea' table is required to get vertical extents
+ gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
+ if (!hheaTable) {
+ return false; // no 'hhea' table -> not an sfnt
+ }
+ const MetricsHeader* hhea =
+ reinterpret_cast<const MetricsHeader*>
+ (hb_blob_get_data(hheaTable, &len));
+ if (len < sizeof(MetricsHeader)) {
+ return false;
+ }
+
+#define SET_UNSIGNED(field,src) aMetrics.field = uint16_t(src) * mFUnitsConvFactor
+#define SET_SIGNED(field,src) aMetrics.field = int16_t(src) * mFUnitsConvFactor
+
+ SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax);
+ SET_SIGNED(maxAscent, hhea->ascender);
+ SET_SIGNED(maxDescent, -int16_t(hhea->descender));
+ SET_SIGNED(externalLeading, hhea->lineGap);
+
+ // 'post' table is required for underline metrics
+ gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag);
+ if (!postTable) {
+ return true; // no 'post' table -> sfnt is not valid
+ }
+ const PostTable *post =
+ reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len));
+ if (len < offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) {
+ return true; // bad post table -> sfnt is not valid
+ }
+
+ SET_SIGNED(underlineOffset, post->underlinePosition);
+ SET_UNSIGNED(underlineSize, post->underlineThickness);
+
+ // 'OS/2' table is optional, if not found we'll estimate xHeight
+ // and aveCharWidth by measuring glyphs
+ gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
+ if (os2Table) {
+ const OS2Table *os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
+ // although sxHeight and sCapHeight are signed fields, we consider
+ // negative values to be erroneous and just ignore them
+ if (uint16_t(os2->version) >= 2) {
+ // version 2 and later includes the x-height and cap-height fields
+ if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) &&
+ int16_t(os2->sxHeight) > 0) {
+ SET_SIGNED(xHeight, os2->sxHeight);
+ }
+ if (len >= offsetof(OS2Table, sCapHeight) + sizeof(int16_t) &&
+ int16_t(os2->sCapHeight) > 0) {
+ SET_SIGNED(capHeight, os2->sCapHeight);
+ }
+ }
+ // this should always be present in any valid OS/2 of any version
+ if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) {
+ SET_SIGNED(aveCharWidth, os2->xAvgCharWidth);
+ SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
+ SET_SIGNED(strikeoutOffset, os2->yStrikeoutPosition);
+
+ // for fonts with USE_TYPO_METRICS set in the fsSelection field,
+ // let the OS/2 sTypo* metrics override those from the hhea table
+ // (see http://www.microsoft.com/typography/otspec/os2.htm#fss)
+ const uint16_t kUseTypoMetricsMask = 1 << 7;
+ if (uint16_t(os2->fsSelection) & kUseTypoMetricsMask) {
+ SET_SIGNED(maxAscent, os2->sTypoAscender);
+ SET_SIGNED(maxDescent, - int16_t(os2->sTypoDescender));
+ SET_SIGNED(externalLeading, os2->sTypoLineGap);
+ }
+ }
+ }
+
+#undef SET_SIGNED
+#undef SET_UNSIGNED
+
+ mIsValid = true;
+
+ return true;
+}
+
+static double
+RoundToNearestMultiple(double aValue, double aFraction)
+{
+ return floor(aValue/aFraction + 0.5) * aFraction;
+}
+
+void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics)
+{
+ aMetrics.maxAscent =
+ ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1/1024.0));
+ aMetrics.maxDescent =
+ ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1/1024.0));
+
+ if (aMetrics.xHeight <= 0) {
+ // only happens if we couldn't find either font metrics
+ // or a char to measure;
+ // pick an arbitrary value that's better than zero
+ aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR;
+ }
+
+ // If we have a font that doesn't provide a capHeight value, use maxAscent
+ // as a reasonable fallback.
+ if (aMetrics.capHeight <= 0) {
+ aMetrics.capHeight = aMetrics.maxAscent;
+ }
+
+ aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent;
+
+ if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) {
+ aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight;
+ } else {
+ aMetrics.internalLeading = 0.0;
+ }
+
+ aMetrics.emAscent = aMetrics.maxAscent * aMetrics.emHeight
+ / aMetrics.maxHeight;
+ aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent;
+
+ if (GetFontEntry()->IsFixedPitch()) {
+ // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
+ // advance than the average character width... this forces
+ // those fonts to be recognized like fixed pitch fonts by layout.
+ aMetrics.maxAdvance = aMetrics.aveCharWidth;
+ }
+
+ if (!aMetrics.strikeoutOffset) {
+ aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5;
+ }
+ if (!aMetrics.strikeoutSize) {
+ aMetrics.strikeoutSize = aMetrics.underlineSize;
+ }
+}
+
+void
+gfxFont::SanitizeMetrics(gfxFont::Metrics *aMetrics, bool aIsBadUnderlineFont)
+{
+ // Even if this font size is zero, this font is created with non-zero size.
+ // However, for layout and others, we should return the metrics of zero size font.
+ if (mStyle.size == 0.0 || mStyle.sizeAdjust == 0.0) {
+ memset(aMetrics, 0, sizeof(gfxFont::Metrics));
+ return;
+ }
+
+ aMetrics->underlineSize = std::max(1.0, aMetrics->underlineSize);
+ aMetrics->strikeoutSize = std::max(1.0, aMetrics->strikeoutSize);
+
+ aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -1.0);
+
+ if (aMetrics->maxAscent < 1.0) {
+ // We cannot draw strikeout line and overline in the ascent...
+ aMetrics->underlineSize = 0;
+ aMetrics->underlineOffset = 0;
+ aMetrics->strikeoutSize = 0;
+ aMetrics->strikeoutOffset = 0;
+ return;
+ }
+
+ /**
+ * Some CJK fonts have bad underline offset. Therefore, if this is such font,
+ * we need to lower the underline offset to bottom of *em* descent.
+ * However, if this is system font, we should not do this for the rendering compatibility with
+ * another application's UI on the platform.
+ * XXX Should not use this hack if the font size is too small?
+ * Such text cannot be read, this might be used for tight CSS rendering? (E.g., Acid2)
+ */
+ if (!mStyle.systemFont && aIsBadUnderlineFont) {
+ // First, we need 2 pixels between baseline and underline at least. Because many CJK characters
+ // put their glyphs on the baseline, so, 1 pixel is too close for CJK characters.
+ aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -2.0);
+
+ // Next, we put the underline to bottom of below of the descent space.
+ if (aMetrics->internalLeading + aMetrics->externalLeading > aMetrics->underlineSize) {
+ aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -aMetrics->emDescent);
+ } else {
+ aMetrics->underlineOffset = std::min(aMetrics->underlineOffset,
+ aMetrics->underlineSize - aMetrics->emDescent);
+ }
+ }
+ // If underline positioned is too far from the text, descent position is preferred so that underline
+ // will stay within the boundary.
+ else if (aMetrics->underlineSize - aMetrics->underlineOffset > aMetrics->maxDescent) {
+ if (aMetrics->underlineSize > aMetrics->maxDescent)
+ aMetrics->underlineSize = std::max(aMetrics->maxDescent, 1.0);
+ // The max underlineOffset is 1px (the min underlineSize is 1px, and min maxDescent is 0px.)
+ aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent;
+ }
+
+ // If strikeout line is overflowed from the ascent, the line should be resized and moved for
+ // that being in the ascent space.
+ // Note that the strikeoutOffset is *middle* of the strikeout line position.
+ gfxFloat halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
+ if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) {
+ if (aMetrics->strikeoutSize > aMetrics->maxAscent) {
+ aMetrics->strikeoutSize = std::max(aMetrics->maxAscent, 1.0);
+ halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
+ }
+ gfxFloat ascent = floor(aMetrics->maxAscent + 0.5);
+ aMetrics->strikeoutOffset = std::max(halfOfStrikeoutSize, ascent / 2.0);
+ }
+
+ // If overline is larger than the ascent, the line should be resized.
+ if (aMetrics->underlineSize > aMetrics->maxAscent) {
+ aMetrics->underlineSize = aMetrics->maxAscent;
+ }
+}
+
+// Create a Metrics record to be used for vertical layout. This should never
+// fail, as we've already decided this is a valid font. We do not have the
+// option of marking it invalid (as can happen if we're unable to read
+// horizontal metrics), because that could break a font that we're already
+// using for horizontal text.
+// So we will synthesize *something* usable here even if there aren't any of the
+// usual font tables (which can happen in the case of a legacy bitmap or Type1
+// font for which the platform-specific backend used platform APIs instead of
+// sfnt tables to create the horizontal metrics).
+const gfxFont::Metrics*
+gfxFont::CreateVerticalMetrics()
+{
+ const uint32_t kHheaTableTag = TRUETYPE_TAG('h','h','e','a');
+ const uint32_t kVheaTableTag = TRUETYPE_TAG('v','h','e','a');
+ const uint32_t kPostTableTag = TRUETYPE_TAG('p','o','s','t');
+ const uint32_t kOS_2TableTag = TRUETYPE_TAG('O','S','/','2');
+ uint32_t len;
+
+ Metrics* metrics = new Metrics;
+ ::memset(metrics, 0, sizeof(Metrics));
+
+ // Some basic defaults, in case the font lacks any real metrics tables.
+ // TODO: consider what rounding (if any) we should apply to these.
+ metrics->emHeight = GetAdjustedSize();
+ metrics->emAscent = metrics->emHeight / 2;
+ metrics->emDescent = metrics->emHeight - metrics->emAscent;
+
+ metrics->maxAscent = metrics->emAscent;
+ metrics->maxDescent = metrics->emDescent;
+
+ const float UNINITIALIZED_LEADING = -10000.0f;
+ metrics->externalLeading = UNINITIALIZED_LEADING;
+
+ if (mFUnitsConvFactor < 0.0) {
+ uint16_t upem = GetFontEntry()->UnitsPerEm();
+ if (upem != gfxFontEntry::kInvalidUPEM) {
+ mFUnitsConvFactor = GetAdjustedSize() / upem;
+ }
+ }
+
+#define SET_UNSIGNED(field,src) metrics->field = uint16_t(src) * mFUnitsConvFactor
+#define SET_SIGNED(field,src) metrics->field = int16_t(src) * mFUnitsConvFactor
+
+ gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
+ if (os2Table && mFUnitsConvFactor >= 0.0) {
+ const OS2Table *os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
+ // These fields should always be present in any valid OS/2 table
+ if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) {
+ SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
+ // Use ascent+descent from the horizontal metrics as the default
+ // advance (aveCharWidth) in vertical mode
+ gfxFloat ascentDescent = gfxFloat(mFUnitsConvFactor) *
+ (int16_t(os2->sTypoAscender) - int16_t(os2->sTypoDescender));
+ metrics->aveCharWidth =
+ std::max(metrics->emHeight, ascentDescent);
+ // Use xAvgCharWidth from horizontal metrics as minimum font extent
+ // for vertical layout, applying half of it to ascent and half to
+ // descent (to work with a default centered baseline).
+ gfxFloat halfCharWidth =
+ int16_t(os2->xAvgCharWidth) * gfxFloat(mFUnitsConvFactor) / 2;
+ metrics->maxAscent = std::max(metrics->maxAscent, halfCharWidth);
+ metrics->maxDescent = std::max(metrics->maxDescent, halfCharWidth);
+ }
+ }
+
+ // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics
+ // and use the line height from its ascent/descent.
+ if (!metrics->aveCharWidth) {
+ gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
+ if (hheaTable && mFUnitsConvFactor >= 0.0) {
+ const MetricsHeader* hhea =
+ reinterpret_cast<const MetricsHeader*>
+ (hb_blob_get_data(hheaTable, &len));
+ if (len >= sizeof(MetricsHeader)) {
+ SET_SIGNED(aveCharWidth, int16_t(hhea->ascender) -
+ int16_t(hhea->descender));
+ metrics->maxAscent = metrics->aveCharWidth / 2;
+ metrics->maxDescent =
+ metrics->aveCharWidth - metrics->maxAscent;
+ }
+ }
+ }
+
+ // Read real vertical metrics if available.
+ gfxFontEntry::AutoTable vheaTable(mFontEntry, kVheaTableTag);
+ if (vheaTable && mFUnitsConvFactor >= 0.0) {
+ const MetricsHeader* vhea =
+ reinterpret_cast<const MetricsHeader*>
+ (hb_blob_get_data(vheaTable, &len));
+ if (len >= sizeof(MetricsHeader)) {
+ SET_UNSIGNED(maxAdvance, vhea->advanceWidthMax);
+ // Redistribute space between ascent/descent because we want a
+ // centered vertical baseline by default.
+ gfxFloat halfExtent = 0.5 * gfxFloat(mFUnitsConvFactor) *
+ (int16_t(vhea->ascender) + std::abs(int16_t(vhea->descender)));
+ // Some bogus fonts have ascent and descent set to zero in 'vhea'.
+ // In that case we just ignore them and keep our synthetic values
+ // from above.
+ if (halfExtent > 0) {
+ metrics->maxAscent = halfExtent;
+ metrics->maxDescent = halfExtent;
+ SET_SIGNED(externalLeading, vhea->lineGap);
+ }
+ }
+ }
+
+ // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt
+ // font of some kind (Type1, bitmap, vector, ...), so fall back to using
+ // whatever the platform backend figured out for horizontal layout.
+ // And if we haven't set externalLeading yet, then copy that from the
+ // horizontal metrics as well, to help consistency of CSS line-height.
+ if (!metrics->aveCharWidth ||
+ metrics->externalLeading == UNINITIALIZED_LEADING) {
+ const Metrics& horizMetrics = GetHorizontalMetrics();
+ if (!metrics->aveCharWidth) {
+ metrics->aveCharWidth = horizMetrics.maxAscent + horizMetrics.maxDescent;
+ }
+ if (metrics->externalLeading == UNINITIALIZED_LEADING) {
+ metrics->externalLeading = horizMetrics.externalLeading;
+ }
+ }
+
+ // Get underline thickness from the 'post' table if available.
+ gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag);
+ if (postTable) {
+ const PostTable *post =
+ reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable,
+ &len));
+ if (len >= offsetof(PostTable, underlineThickness) +
+ sizeof(uint16_t)) {
+ SET_UNSIGNED(underlineSize, post->underlineThickness);
+ // Also use for strikeout if we didn't find that in OS/2 above.
+ if (!metrics->strikeoutSize) {
+ metrics->strikeoutSize = metrics->underlineSize;
+ }
+ }
+ }
+
+#undef SET_UNSIGNED
+#undef SET_SIGNED
+
+ // If we didn't read this from a vhea table, it will still be zero.
+ // In any case, let's make sure it is not less than the value we've
+ // come up with for aveCharWidth.
+ metrics->maxAdvance = std::max(metrics->maxAdvance, metrics->aveCharWidth);
+
+ // Thickness of underline and strikeout may have been read from tables,
+ // but in case they were not present, ensure a minimum of 1 pixel.
+ // We synthesize our own positions, as font metrics don't provide these
+ // for vertical layout.
+ metrics->underlineSize = std::max(1.0, metrics->underlineSize);
+ metrics->underlineOffset = - metrics->maxDescent - metrics->underlineSize;
+
+ metrics->strikeoutSize = std::max(1.0, metrics->strikeoutSize);
+ metrics->strikeoutOffset = - 0.5 * metrics->strikeoutSize;
+
+ // Somewhat arbitrary values for now, subject to future refinement...
+ metrics->spaceWidth = metrics->aveCharWidth;
+ metrics->zeroOrAveCharWidth = metrics->aveCharWidth;
+ metrics->maxHeight = metrics->maxAscent + metrics->maxDescent;
+ metrics->xHeight = metrics->emHeight / 2;
+ metrics->capHeight = metrics->maxAscent;
+
+ return metrics;
+}
+
+gfxFloat
+gfxFont::SynthesizeSpaceWidth(uint32_t aCh)
+{
+ // return an appropriate width for various Unicode space characters
+ // that we "fake" if they're not actually present in the font;
+ // returns negative value if the char is not a known space.
+ switch (aCh) {
+ case 0x2000: // en quad
+ case 0x2002: return GetAdjustedSize() / 2; // en space
+ case 0x2001: // em quad
+ case 0x2003: return GetAdjustedSize(); // em space
+ case 0x2004: return GetAdjustedSize() / 3; // three-per-em space
+ case 0x2005: return GetAdjustedSize() / 4; // four-per-em space
+ case 0x2006: return GetAdjustedSize() / 6; // six-per-em space
+ case 0x2007: return GetMetrics(eHorizontal).zeroOrAveCharWidth; // figure space
+ case 0x2008: return GetMetrics(eHorizontal).spaceWidth; // punctuation space
+ case 0x2009: return GetAdjustedSize() / 5; // thin space
+ case 0x200a: return GetAdjustedSize() / 10; // hair space
+ case 0x202f: return GetAdjustedSize() / 5; // narrow no-break space
+ default: return -1.0;
+ }
+}
+
+void
+gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) {
+ aSizes->mFontInstances +=
+ mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mWordCache) {
+ aSizes->mShapedWords += mWordCache->SizeOfIncludingThis(aMallocSizeOf);
+ }
+}
+
+void
+gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ aSizes->mFontInstances += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+void
+gfxFont::AddGlyphChangeObserver(GlyphChangeObserver *aObserver)
+{
+ if (!mGlyphChangeObservers) {
+ mGlyphChangeObservers.reset(
+ new nsTHashtable<nsPtrHashKey<GlyphChangeObserver>>);
+ }
+ mGlyphChangeObservers->PutEntry(aObserver);
+}
+
+void
+gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver)
+{
+ NS_ASSERTION(mGlyphChangeObservers, "No observers registered");
+ NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), "Observer not registered");
+ mGlyphChangeObservers->RemoveEntry(aObserver);
+}
+
+
+#define DEFAULT_PIXEL_FONT_SIZE 16.0f
+
+/*static*/ uint32_t
+gfxFontStyle::ParseFontLanguageOverride(const nsString& aLangTag)
+{
+ if (!aLangTag.Length() || aLangTag.Length() > 4) {
+ return NO_FONT_LANGUAGE_OVERRIDE;
+ }
+ uint32_t index, result = 0;
+ for (index = 0; index < aLangTag.Length(); ++index) {
+ char16_t ch = aLangTag[index];
+ if (!nsCRT::IsAscii(ch)) { // valid tags are pure ASCII
+ return NO_FONT_LANGUAGE_OVERRIDE;
+ }
+ result = (result << 8) + ch;
+ }
+ while (index++ < 4) {
+ result = (result << 8) + 0x20;
+ }
+ return result;
+}
+
+gfxFontStyle::gfxFontStyle() :
+ language(nsGkAtoms::x_western),
+ size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(-1.0f), baselineOffset(0.0f),
+ languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
+ weight(NS_FONT_WEIGHT_NORMAL), stretch(NS_FONT_STRETCH_NORMAL),
+ style(NS_FONT_STYLE_NORMAL),
+ variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+ variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
+ systemFont(true), printerFont(false), useGrayscaleAntialiasing(false),
+ allowSyntheticWeight(true), allowSyntheticStyle(true),
+ noFallbackVariantFeatures(true),
+ explicitLanguage(false)
+{
+}
+
+gfxFontStyle::gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
+ gfxFloat aSize,
+ nsIAtom *aLanguage, bool aExplicitLanguage,
+ float aSizeAdjust, bool aSystemFont,
+ bool aPrinterFont,
+ bool aAllowWeightSynthesis,
+ bool aAllowStyleSynthesis,
+ const nsString& aLanguageOverride):
+ language(aLanguage),
+ size(aSize), sizeAdjust(aSizeAdjust), baselineOffset(0.0f),
+ languageOverride(ParseFontLanguageOverride(aLanguageOverride)),
+ weight(aWeight), stretch(aStretch),
+ style(aStyle),
+ variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+ variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
+ systemFont(aSystemFont), printerFont(aPrinterFont),
+ useGrayscaleAntialiasing(false),
+ allowSyntheticWeight(aAllowWeightSynthesis),
+ allowSyntheticStyle(aAllowStyleSynthesis),
+ noFallbackVariantFeatures(true),
+ explicitLanguage(aExplicitLanguage)
+{
+ MOZ_ASSERT(!mozilla::IsNaN(size));
+ MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust));
+
+ if (weight > 900)
+ weight = 900;
+ if (weight < 100)
+ weight = 100;
+
+ if (size >= FONT_MAX_SIZE) {
+ size = FONT_MAX_SIZE;
+ sizeAdjust = -1.0f;
+ } else if (size < 0.0) {
+ NS_WARNING("negative font size");
+ size = 0.0;
+ }
+
+ if (!language) {
+ NS_WARNING("null language");
+ language = nsGkAtoms::x_western;
+ }
+}
+
+int8_t
+gfxFontStyle::ComputeWeight() const
+{
+ int8_t baseWeight = (weight + 50) / 100;
+
+ if (baseWeight < 0)
+ baseWeight = 0;
+ if (baseWeight > 9)
+ baseWeight = 9;
+
+ return baseWeight;
+}
+
+void
+gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel)
+{
+ NS_PRECONDITION(variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL &&
+ baselineOffset == 0,
+ "can't adjust this style for sub/superscript");
+
+ // calculate the baseline offset (before changing the size)
+ if (variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) {
+ baselineOffset = size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO;
+ } else {
+ baselineOffset = size * NS_FONT_SUBSCRIPT_OFFSET_RATIO;
+ }
+
+ // calculate reduced size, roughly mimicing behavior of font-size: smaller
+ float cssSize = size * aAppUnitsPerDevPixel / AppUnitsPerCSSPixel();
+ if (cssSize < NS_FONT_SUB_SUPER_SMALL_SIZE) {
+ size *= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL;
+ } else if (cssSize >= NS_FONT_SUB_SUPER_LARGE_SIZE) {
+ size *= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
+ } else {
+ gfxFloat t = (cssSize - NS_FONT_SUB_SUPER_SMALL_SIZE) /
+ (NS_FONT_SUB_SUPER_LARGE_SIZE -
+ NS_FONT_SUB_SUPER_SMALL_SIZE);
+ size *= (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL +
+ t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
+ }
+
+ // clear the variant field
+ variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL;
+}
+
+bool
+gfxFont::TryGetMathTable()
+{
+ if (!mMathInitialized) {
+ mMathInitialized = true;
+
+ hb_face_t *face = GetFontEntry()->GetHBFace();
+ if (face) {
+ if (hb_ot_math_has_data(face)) {
+ mMathTable = MakeUnique<gfxMathTable>(face, GetAdjustedSize());
+ }
+ hb_face_destroy(face);
+ }
+ }
+
+ return !!mMathTable;
+}
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxFontInfoLoader.cpp.orig
@@ -0,0 +1,280 @@
+/* -*- Mode: C++; tab-width: 20; 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 "gfxFontInfoLoader.h"
+#include "nsCRT.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h" // for nsRunnable
+#include "gfxPlatformFontList.h"
+
+using namespace mozilla;
+using services::GetObserverService;
+
+#define LOG_FONTINIT(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), \
+ LogLevel::Debug, args)
+#define LOG_FONTINIT_ENABLED() MOZ_LOG_TEST( \
+ gfxPlatform::GetLog(eGfxLog_fontinit), \
+ LogLevel::Debug)
+
+void
+FontInfoData::Load()
+{
+ TimeStamp start = TimeStamp::Now();
+
+ uint32_t i, n = mFontFamiliesToLoad.Length();
+ mLoadStats.families = n;
+ for (i = 0; i < n && !mCanceled; i++) {
+ // font file memory mapping sometimes causes exceptions - bug 1100949
+ MOZ_SEH_TRY {
+ LoadFontFamilyData(mFontFamiliesToLoad[i]);
+ } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalError() <<
+ "Exception occurred reading font data for " <<
+ NS_ConvertUTF16toUTF8(mFontFamiliesToLoad[i]).get();
+ }
+ }
+
+ mLoadTime = TimeStamp::Now() - start;
+}
+
+class FontInfoLoadCompleteEvent : public Runnable {
+ virtual ~FontInfoLoadCompleteEvent() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit FontInfoLoadCompleteEvent(FontInfoData *aFontInfo) :
+ mFontInfo(aFontInfo)
+ {}
+
+ NS_IMETHOD Run() override;
+
+ RefPtr<FontInfoData> mFontInfo;
+};
+
+class AsyncFontInfoLoader : public Runnable {
+ virtual ~AsyncFontInfoLoader() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit AsyncFontInfoLoader(FontInfoData *aFontInfo) :
+ mFontInfo(aFontInfo)
+ {
+ mCompleteEvent = new FontInfoLoadCompleteEvent(aFontInfo);
+ }
+
+ NS_IMETHOD Run() override;
+
+ RefPtr<FontInfoData> mFontInfo;
+ RefPtr<FontInfoLoadCompleteEvent> mCompleteEvent;
+};
+
+class ShutdownThreadEvent : public Runnable {
+ virtual ~ShutdownThreadEvent() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
+ NS_IMETHOD Run() override {
+ mThread->Shutdown();
+ return NS_OK;
+ }
+ nsCOMPtr<nsIThread> mThread;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED0(ShutdownThreadEvent, Runnable);
+
+// runs on main thread after async font info loading is done
+nsresult
+FontInfoLoadCompleteEvent::Run()
+{
+ gfxFontInfoLoader *loader =
+ static_cast<gfxFontInfoLoader*>(gfxPlatformFontList::PlatformFontList());
+
+ loader->FinalizeLoader(mFontInfo);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(FontInfoLoadCompleteEvent, Runnable);
+
+// runs on separate thread
+nsresult
+AsyncFontInfoLoader::Run()
+{
+ // load platform-specific font info
+ mFontInfo->Load();
+
+ // post a completion event that transfer the data to the fontlist
+ NS_DispatchToMainThread(mCompleteEvent);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(AsyncFontInfoLoader, Runnable);
+
+NS_IMPL_ISUPPORTS(gfxFontInfoLoader::ShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *someData)
+{
+ if (!nsCRT::strcmp(aTopic, "quit-application")) {
+ mLoader->CancelLoader();
+ } else {
+ NS_NOTREACHED("unexpected notification topic");
+ }
+ return NS_OK;
+}
+
+void
+gfxFontInfoLoader::StartLoader(uint32_t aDelay, uint32_t aInterval)
+{
+ mInterval = aInterval;
+
+ NS_ASSERTION(!mFontInfo,
+ "fontinfo should be null when starting font loader");
+
+ // sanity check
+ if (mState != stateInitial &&
+ mState != stateTimerOff &&
+ mState != stateTimerOnDelay) {
+ CancelLoader();
+ }
+
+ // set up timer
+ if (!mTimer) {
+ mTimer = new mozilla::timer;
+ if (!mTimer) {
+ NS_WARNING("Failure to create font info loader timer");
+ return;
+ }
+ }
+
+ AddShutdownObserver();
+
+ // delay? ==> start async thread after a delay
+ if (aDelay) {
+ mTimer->InitWithFuncCallback(DelayedStartCallback, this, aDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ return;
+ }
+
+ mFontInfo = CreateFontInfoData();
+
+ // initialize
+ InitLoader();
+
+ // start async load
+ nsresult rv = NS_NewNamedThread("Font Loader",
+ getter_AddRefs(mFontLoaderThread),
+ nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ mState = stateAsyncLoad;
+
+ nsCOMPtr<nsIRunnable> loadEvent = new AsyncFontInfoLoader(mFontInfo);
+
+ mFontLoaderThread->Dispatch(loadEvent.forget(), NS_DISPATCH_NORMAL);
+
+ if (LOG_FONTINIT_ENABLED()) {
+ LOG_FONTINIT(("(fontinit) fontloader started (fontinfo: %p)\n",
+ mFontInfo.get()));
+ }
+}
+
+void
+gfxFontInfoLoader::FinalizeLoader(FontInfoData *aFontInfo)
+{
+ // Avoid loading data if loader has already been canceled.
+ // This should mean that CancelLoader() ran and the Load
+ // thread has already Shutdown(), and likely before processing
+ // the Shutdown event it handled the load event and sent back
+ // our Completion event, thus we end up here.
+ if (mState != stateAsyncLoad || mFontInfo != aFontInfo) {
+ return;
+ }
+
+ mLoadTime = mFontInfo->mLoadTime;
+
+ // try to load all font data immediately
+ if (LoadFontInfo()) {
+ CancelLoader();
+ return;
+ }
+
+ // not all work completed ==> run load on interval
+ mState = stateTimerOnInterval;
+ mTimer->InitWithFuncCallback(LoadFontInfoCallback, this, mInterval,
+ nsITimer::TYPE_REPEATING_SLACK);
+}
+
+void
+gfxFontInfoLoader::CancelLoader()
+{
+ if (mState == stateInitial) {
+ return;
+ }
+ mState = stateTimerOff;
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (mFontInfo) // null during any initial delay
+ mFontInfo->mCanceled = true;
+ if (mFontLoaderThread) {
+ NS_DispatchToMainThread(new ShutdownThreadEvent(mFontLoaderThread));
+ mFontLoaderThread = nullptr;
+ }
+ RemoveShutdownObserver();
+ CleanupLoader();
+}
+
+void
+gfxFontInfoLoader::LoadFontInfoTimerFire()
+{
+ if (mState == stateTimerOnDelay) {
+ mState = stateTimerOnInterval;
+ mTimer->SetDelay(mInterval);
+ }
+
+ bool done = LoadFontInfo();
+ if (done) {
+ CancelLoader();
+ }
+}
+
+gfxFontInfoLoader::~gfxFontInfoLoader()
+{
+ RemoveShutdownObserver();
+ MOZ_COUNT_DTOR(gfxFontInfoLoader);
+}
+
+void
+gfxFontInfoLoader::AddShutdownObserver()
+{
+ if (mObserver) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ mObserver = new ShutdownObserver(this);
+ obs->AddObserver(mObserver, "quit-application", false);
+ }
+}
+
+void
+gfxFontInfoLoader::RemoveShutdownObserver()
+{
+ if (mObserver) {
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(mObserver, "quit-application");
+ mObserver = nullptr;
+ }
+ }
+}
new file mode 100644
--- /dev/null
+++ b/hglog.txt
@@ -0,0 +1,53 @@
+changeset: 332043:f985243bb630
+tag: tip
+user: Phil Ringnalda <philringnalda@gmail.com>
+date: Wed Feb 01 19:17:21 2017 -0800
+summary: Backed out changeset b03c9f4ac1b0 (bug 1335294) for Windows PGO bustage
+
+changeset: 332042:b2b2713bf5e8
+user: Phil Ringnalda <philringnalda@gmail.com>
+date: Wed Feb 01 19:16:41 2017 -0800
+summary: No bug, backed out 2 changesets (HPKP and HSTS updates) for being generated by a patch being backed out
+
+changeset: 332041:ece0e0fd16a3
+user: Wes Kocher <wkocher@mozilla.com>
+date: Wed Feb 01 15:14:08 2017 -0800
+summary: Bug 1315042 - redisable browser_referrer_middle_click_in_container.js for failures a=me
+
+changeset: 332040:1ebbde84a39a
+parent: 331989:8196774c6b8a
+parent: 332039:40871b109d72
+user: Wes Kocher <wkocher@mozilla.com>
+date: Wed Feb 01 16:22:11 2017 -0800
+summary: Merge inbound to m-c a=merge
+
+changeset: 332039:40871b109d72
+user: Felipe Gomes <felipc@gmail.com>
+date: Wed Feb 01 18:26:48 2017 -0200
+summary: Bug 1332692 - Block e10s rollout for LastPass. r=mkaply
+
+changeset: 332038:90df369672f6
+user: Jeff Muizelaar <jmuizelaar@mozilla.com>
+date: Wed Feb 01 15:04:38 2017 -0500
+summary: Bug 1335149. Remove unnecessary flush() calls from canvas implementation.
+
+changeset: 332037:c2314298c556
+user: Shu-yu Guo <shu@rfrn.org>
+date: Wed Feb 01 12:04:30 2017 -0800
+summary: Bug 1333946 - Make IonBuilder::processIterators transitive. (r=jandem)
+
+changeset: 332036:e0026c3d1302
+user: stefanh@inbox.com
+date: Wed Feb 01 20:55:09 2017 +0100
+summary: Bug 1333485 - Remove LogUtils.jsm from services/crypto/modules/. r=markh.
+
+changeset: 332035:3f8bc6f7131a
+user: jdarcangelo <justindarc@gmail.com>
+date: Wed Feb 01 14:52:40 2017 -0500
+summary: Bug 1335200 - [FlyWeb] Crash on Android when calling publishServer(), r=sebastian
+
+changeset: 332034:c2aee4595f1a
+user: Trevor Saunders <tbsaunde@tbsaunde.org>
+date: Tue Jan 31 17:32:52 2017 -0500
+summary: bug 1326084 - ClearChildDoc() should leave the proxy marked as being an outerdoc r=aklotz
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit/MIRIns.h
@@ -0,0 +1,465 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+/*
+ * Everything needed to build actual MIR instructions: the actual opcodes and
+ * instructions, the instruction interface, and use chains.
+ */
+
+ #ifndef jit_MIR_Ins_h
+ #define jit_MIR_Ins_h
+
+ #include "MIR.h"
+
+template <size_t Arity>
+class MAryInstruction : public MInstruction
+{
+ mozilla::Array<MUse, Arity> operands_;
+
+ protected:
+ MUse* getUseFor(size_t index) final override {
+ return &operands_[index];
+ }
+ const MUse* getUseFor(size_t index) const final override {
+ return &operands_[index];
+ }
+ void initOperand(size_t index, MDefinition* operand) {
+ operands_[index].init(operand, this);
+ }
+
+ public:
+ MDefinition* getOperand(size_t index) const final override {
+ return operands_[index].producer();
+ }
+ size_t numOperands() const final override {
+ return Arity;
+ }
+#ifdef DEBUG
+ static const size_t staticNumOperands = Arity;
+#endif
+ size_t indexOf(const MUse* u) const final override {
+ MOZ_ASSERT(u >= &operands_[0]);
+ MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
+ return u - &operands_[0];
+ }
+ void replaceOperand(size_t index, MDefinition* operand) final override {
+ operands_[index].replaceProducer(operand);
+ }
+
+ explicit MAryInstruction(Opcode op)
+ : MInstruction(op)
+ { }
+
+ explicit MAryInstruction(const MAryInstruction<Arity>& other)
+ : MInstruction(other)
+ {
+ for (int i = 0; i < (int) Arity; i++) // N.B. use |int| to avoid warnings when Arity == 0
+ operands_[i].init(other.operands_[i].producer(), this);
+ }
+};
+
+class MTableSwitch final
+ : public MControlInstruction,
+ public NoFloatPolicy<0>::Data
+{
+ // The successors of the tableswitch
+ // - First successor = the default case
+ // - Successors 2 and higher = the cases
+ Vector<MBasicBlock*, 0, JitAllocPolicy> successors_;
+ // Index into successors_ sorted on case index
+ Vector<size_t, 0, JitAllocPolicy> cases_;
+
+ MUse operand_;
+ int32_t low_;
+ int32_t high_;
+
+ void initOperand(size_t index, MDefinition* operand) {
+ MOZ_ASSERT(index == 0);
+ operand_.init(operand, this);
+ }
+
+ MTableSwitch(TempAllocator& alloc, MDefinition* ins,
+ int32_t low, int32_t high)
+ : MControlInstruction(classOpcode),
+ successors_(alloc),
+ cases_(alloc),
+ low_(low),
+ high_(high)
+ {
+ initOperand(0, ins);
+ }
+
+ protected:
+ MUse* getUseFor(size_t index) override {
+ MOZ_ASSERT(index == 0);
+ return &operand_;
+ }
+
+ const MUse* getUseFor(size_t index) const override {
+ MOZ_ASSERT(index == 0);
+ return &operand_;
+ }
+
+ public:
+ INSTRUCTION_HEADER(TableSwitch)
+ static MTableSwitch* New(TempAllocator& alloc, MDefinition* ins, int32_t low, int32_t high);
+
+ size_t numSuccessors() const override {
+ return successors_.length();
+ }
+
+ MOZ_MUST_USE bool addSuccessor(MBasicBlock* successor, size_t* index) {
+ MOZ_ASSERT(successors_.length() < (size_t)(high_ - low_ + 2));
+ MOZ_ASSERT(!successors_.empty());
+ *index = successors_.length();
+ return successors_.append(successor);
+ }
+
+ MBasicBlock* getSuccessor(size_t i) const override {
+ MOZ_ASSERT(i < numSuccessors());
+ return successors_[i];
+ }
+
+ void replaceSuccessor(size_t i, MBasicBlock* successor) override {
+ MOZ_ASSERT(i < numSuccessors());
+ successors_[i] = successor;
+ }
+
+ int32_t low() const {
+ return low_;
+ }
+
+ int32_t high() const {
+ return high_;
+ }
+
+ MBasicBlock* getDefault() const {
+ return getSuccessor(0);
+ }
+
+ MBasicBlock* getCase(size_t i) const {
+ return getSuccessor(cases_[i]);
+ }
+
+ MOZ_MUST_USE bool addDefault(MBasicBlock* block, size_t* index = nullptr) {
+ MOZ_ASSERT(successors_.empty());
+ if (index)
+ *index = 0;
+ return successors_.append(block);
+ }
+
+ MOZ_MUST_USE bool addCase(size_t successorIndex) {
+ return cases_.append(successorIndex);
+ }
+
+ size_t numCases() const {
+ return high() - low() + 1;
+ }
+
+ MDefinition* getOperand(size_t index) const override {
+ MOZ_ASSERT(index == 0);
+ return operand_.producer();
+ }
+
+ size_t numOperands() const override {
+ return 1;
+ }
+
+ size_t indexOf(const MUse* u) const final override {
+ MOZ_ASSERT(u == getUseFor(0));
+ return 0;
+ }
+
+ void replaceOperand(size_t index, MDefinition* operand) final override {
+ MOZ_ASSERT(index == 0);
+ operand_.replaceProducer(operand);
+ }
+
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+};
+
+// Polymorphic dispatch for inlining, keyed off incoming JSFunction*.
+class MFunctionDispatch : public MDispatchInstruction
+{
+ MFunctionDispatch(TempAllocator& alloc, MDefinition* input)
+ : MDispatchInstruction(alloc, classOpcode, input)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(FunctionDispatch)
+
+ static MFunctionDispatch* New(TempAllocator& alloc, MDefinition* ins) {
+ return new(alloc) MFunctionDispatch(alloc, ins);
+ }
+ bool appendRoots(MRootList& roots) const override;
+};
+
+// Polymorphic dispatch for inlining, keyed off incoming ObjectGroup.
+class MObjectGroupDispatch : public MDispatchInstruction
+{
+ // Map ObjectGroup (of CallProp's Target Object) -> JSFunction (yielded by the CallProp).
+ InlinePropertyTable* inlinePropertyTable_;
+
+ MObjectGroupDispatch(TempAllocator& alloc, MDefinition* input, InlinePropertyTable* table)
+ : MDispatchInstruction(alloc, classOpcode, input),
+ inlinePropertyTable_(table)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(ObjectGroupDispatch)
+
+ static MObjectGroupDispatch* New(TempAllocator& alloc, MDefinition* ins,
+ InlinePropertyTable* table)
+ {
+ return new(alloc) MObjectGroupDispatch(alloc, ins, table);
+ }
+
+ InlinePropertyTable* propTable() const {
+ return inlinePropertyTable_;
+ }
+ bool appendRoots(MRootList& roots) const override;
+};
+
+class MThrow
+ : public MAryControlInstruction<1, 0>,
+ public BoxInputsPolicy::Data
+{
+ explicit MThrow(MDefinition* ins)
+ : MAryControlInstruction(classOpcode)
+ {
+ initOperand(0, ins);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Throw)
+ TRIVIAL_NEW_WRAPPERS
+
+ virtual AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+ bool possiblyCalls() const override {
+ return true;
+ }
+};
+
+// Returns from this function to the previous caller.
+class MReturn
+ : public MAryControlInstruction<1, 0>,
+ public BoxInputsPolicy::Data
+{
+ explicit MReturn(MDefinition* ins)
+ : MAryControlInstruction(classOpcode)
+ {
+ initOperand(0, ins);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Return)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, input))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Jump to the start of another basic block.
+class MGoto
+ : public MAryControlInstruction<0, 1>,
+ public NoTypePolicy::Data
+{
+ explicit MGoto(MBasicBlock* target)
+ : MAryControlInstruction(classOpcode)
+ {
+ setSuccessor(0, target);
+ }
+
+ public:
+ INSTRUCTION_HEADER(Goto)
+ static MGoto* New(TempAllocator& alloc, MBasicBlock* target);
+ static MGoto* New(TempAllocator::Fallible alloc, MBasicBlock* target);
+
+ // Variant that may patch the target later.
+ static MGoto* New(TempAllocator& alloc);
+
+ static const size_t TargetIndex = 0;
+
+ MBasicBlock* target() {
+ return getSuccessor(0);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Tests if the input instruction evaluates to true or false, and jumps to the
+// start of a corresponding basic block.
+class MTest
+ : public MAryControlInstruction<1, 2>,
+ public TestPolicy::Data
+{
+ bool operandMightEmulateUndefined_;
+
+ MTest(MDefinition* ins, MBasicBlock* trueBranch, MBasicBlock* falseBranch)
+ : MAryControlInstruction(classOpcode),
+ operandMightEmulateUndefined_(true)
+ {
+ initOperand(0, ins);
+ setSuccessor(0, trueBranch);
+ setSuccessor(1, falseBranch);
+ }
+
+ // Variant which may patch the ifTrue branch later.
+ MTest(MDefinition* ins, MBasicBlock* falseBranch)
+ : MTest(ins, nullptr, falseBranch)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(Test)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, input))
+
+ static const size_t TrueBranchIndex = 0;
+
+ MBasicBlock* ifTrue() const {
+ return getSuccessor(0);
+ }
+ MBasicBlock* ifFalse() const {
+ return getSuccessor(1);
+ }
+ MBasicBlock* branchSuccessor(BranchDirection dir) const {
+ return (dir == TRUE_BRANCH) ? ifTrue() : ifFalse();
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ // We cache whether our operand might emulate undefined, but we don't want
+ // to do that from New() or the constructor, since those can be called on
+ // background threads. So make callers explicitly call it if they want us
+ // to check whether the operand might do this. If this method is never
+ // called, we'll assume our operand can emulate undefined.
+ void cacheOperandMightEmulateUndefined(CompilerConstraintList* constraints);
+ MDefinition* foldsDoubleNegation(TempAllocator& alloc);
+ MDefinition* foldsConstant(TempAllocator& alloc);
+ MDefinition* foldsTypes(TempAllocator& alloc);
+ MDefinition* foldsNeedlessControlFlow(TempAllocator& alloc);
+ MDefinition* foldsTo(TempAllocator& alloc) override;
+ void filtersUndefinedOrNull(bool trueBranch, MDefinition** subject, bool* filtersUndefined,
+ bool* filtersNull);
+
+ void markNoOperandEmulatesUndefined() {
+ operandMightEmulateUndefined_ = false;
+ }
+ bool operandMightEmulateUndefined() const {
+ return operandMightEmulateUndefined_;
+ }
+#ifdef DEBUG
+ bool isConsistentFloat32Use(MUse* use) const override {
+ return true;
+ }
+#endif
+};
+
+// Equivalent to MTest(true, successor, fake), except without the foldsTo
+// method. This allows IonBuilder to insert fake CFG edges to magically protect
+// control flow for try-catch blocks.
+class MGotoWithFake
+ : public MAryControlInstruction<0, 2>,
+ public NoTypePolicy::Data
+{
+ MGotoWithFake(MBasicBlock* successor, MBasicBlock* fake)
+ : MAryControlInstruction(classOpcode)
+ {
+ setSuccessor(0, successor);
+ setSuccessor(1, fake);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GotoWithFake)
+ TRIVIAL_NEW_WRAPPERS
+
+ MBasicBlock* target() const {
+ return getSuccessor(0);
+ }
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+class MUnreachable
+ : public MAryControlInstruction<0, 0>,
+ public NoTypePolicy::Data
+{
+ MUnreachable()
+ : MAryControlInstruction(classOpcode)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(Unreachable)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+};
+
+// Directly jumps to the indicated trap, leaving Wasm code and reporting a
+// runtime error.
+class MWasmTrap
+ : public MAryControlInstruction<0, 0>,
+ public NoTypePolicy::Data
+{
+ wasm::Trap trap_;
+ wasm::BytecodeOffset bytecodeOffset_;
+
+ explicit MWasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset)
+ : MAryControlInstruction(classOpcode),
+ trap_(trap),
+ bytecodeOffset_(bytecodeOffset)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(WasmTrap)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ wasm::Trap trap() const { return trap_; }
+ wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
+};
+
+class MWasmReturn
+ : public MAryControlInstruction<1, 0>,
+ public NoTypePolicy::Data
+{
+ explicit MWasmReturn(MDefinition* ins)
+ : MAryControlInstruction(classOpcode)
+ {
+ initOperand(0, ins);
+ }
+
+ public:
+ INSTRUCTION_HEADER(WasmReturn)
+ TRIVIAL_NEW_WRAPPERS
+};
+
+class MWasmReturnVoid
+ : public MAryControlInstruction<0, 0>,
+ public NoTypePolicy::Data
+{
+ MWasmReturnVoid()
+ : MAryControlInstruction(classOpcode)
+ { }
+
+ public:
+ INSTRUCTION_HEADER(WasmReturnVoid)
+ TRIVIAL_NEW_WRAPPERS
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/manderc3@
@@ -0,0 +1,669 @@
+# HG changeset patch
+# User Chris Mander <mandercs3@gmail.com>
+# Parent 3bf44c3230c850478c29b5624f4d83d3f7b97b0b
+
+diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h
+--- a/js/src/jit/MIR.h
++++ b/js/src/jit/MIR.h
+@@ -15,7 +15,7 @@
+ #include "mozilla/Alignment.h"
+ #include "mozilla/Array.h"
+ #include "mozilla/Attributes.h"
+-#include "mozilla/MacroForEach.h"
++#include "mozilla/MacroMForEach.h"
+
+ #include "builtin/SIMD.h"
+ #include "jit/AtomicOp.h"
+@@ -1132,6 +1132,80 @@ class MRootList : public TempObject
+ }
+ };
+
++// An instruction is an SSA name that is inserted into a basic block's IR
++// stream.
++class MInstruction
++ : public MDefinition,
++ public InlineListNode<MInstruction>
++{
++ MResumePoint* resumePoint_;
++
++ protected:
++ // All MInstructions are using the "MFoo::New(alloc)" notation instead of
++ // the TempObject new operator. This code redefines the new operator as
++ // protected, and delegates to the TempObject new operator. Thus, the
++ // following code prevents calls to "new(alloc) MFoo" outside the MFoo
++ // members.
++ inline void* operator new(size_t nbytes, TempAllocator::Fallible view) throw() {
++ return TempObject::operator new(nbytes, view);
++ }
++ inline void* operator new(size_t nbytes, TempAllocator& alloc) {
++ return TempObject::operator new(nbytes, alloc);
++ }
++ template <class T>
++ inline void* operator new(size_t nbytes, T* pos) {
++ return TempObject::operator new(nbytes, pos);
++ }
++
++ public:
++ explicit MInstruction(Opcode op)
++ : MDefinition(op),
++ resumePoint_(nullptr)
++ { }
++
++ // Copying an instruction leaves the block and resume point as empty.
++ explicit MInstruction(const MInstruction& other)
++ : MDefinition(other),
++ resumePoint_(nullptr)
++ { }
++
++ // Convenient function used for replacing a load by the value of the store
++ // if the types are match, and boxing the value if they do not match.
++ MDefinition* foldsToStore(TempAllocator& alloc);
++
++ void setResumePoint(MResumePoint* resumePoint);
++
++ // Used to transfer the resume point to the rewritten instruction.
++ void stealResumePoint(MInstruction* ins);
++ void moveResumePointAsEntry();
++ void clearResumePoint();
++ MResumePoint* resumePoint() const {
++ return resumePoint_;
++ }
++
++ // For instructions which can be cloned with new inputs, with all other
++ // information being the same. clone() implementations do not need to worry
++ // about cloning generic MInstruction/MDefinition state like flags and
++ // resume points.
++ virtual bool canClone() const {
++ return false;
++ }
++ virtual MInstruction* clone(TempAllocator& alloc, const MDefinitionVector& inputs) const {
++ MOZ_CRASH();
++ }
++
++ // MIR instructions containing GC pointers should override this to append
++ // these pointers to the root list.
++ virtual bool appendRoots(MRootList& roots) const {
++ return true;
++ }
++
++ // Instructions needing to hook into type analysis should return a
++ // TypePolicy.
++ virtual TypePolicy* typePolicy() = 0;
++ virtual MIRType typePolicySpecialization() = 0;
++};
++
+ #define INSTRUCTION_HEADER_WITHOUT_TYPEPOLICY(opcode) \
+ static const Opcode classOpcode = Opcode::opcode; \
+ using MThisOpcode = M##opcode; \
+@@ -1203,53 +1277,6 @@ class MRootList : public TempObject
+ #define NAMED_OPERANDS(...) \
+ MOZ_FOR_EACH(NAMED_OPERAND_ACCESSOR_APPLY, (), (__VA_ARGS__))
+
+-template <size_t Arity>
+-class MAryInstruction : public MInstruction
+-{
+- mozilla::Array<MUse, Arity> operands_;
+-
+- protected:
+- MUse* getUseFor(size_t index) final override {
+- return &operands_[index];
+- }
+- const MUse* getUseFor(size_t index) const final override {
+- return &operands_[index];
+- }
+- void initOperand(size_t index, MDefinition* operand) {
+- operands_[index].init(operand, this);
+- }
+-
+- public:
+- MDefinition* getOperand(size_t index) const final override {
+- return operands_[index].producer();
+- }
+- size_t numOperands() const final override {
+- return Arity;
+- }
+-#ifdef DEBUG
+- static const size_t staticNumOperands = Arity;
+-#endif
+- size_t indexOf(const MUse* u) const final override {
+- MOZ_ASSERT(u >= &operands_[0]);
+- MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
+- return u - &operands_[0];
+- }
+- void replaceOperand(size_t index, MDefinition* operand) final override {
+- operands_[index].replaceProducer(operand);
+- }
+-
+- explicit MAryInstruction(Opcode op)
+- : MInstruction(op)
+- { }
+-
+- explicit MAryInstruction(const MAryInstruction<Arity>& other)
+- : MInstruction(other)
+- {
+- for (int i = 0; i < (int) Arity; i++) // N.B. use |int| to avoid warnings when Arity == 0
+- operands_[i].init(other.operands_[i].producer(), this);
+- }
+-};
+-
+ class MNullaryInstruction
+ : public MAryInstruction<0>,
+ public NoTypePolicy::Data
+@@ -3059,146 +3086,6 @@ NegateBranchDirection(BranchDirection di
+ return (dir == FALSE_BRANCH) ? TRUE_BRANCH : FALSE_BRANCH;
+ }
+
+-// Tests if the input instruction evaluates to true or false, and jumps to the
+-// start of a corresponding basic block.
+-class MTest
+- : public MAryControlInstruction<1, 2>,
+- public TestPolicy::Data
+-{
+- bool operandMightEmulateUndefined_;
+-
+- MTest(MDefinition* ins, MBasicBlock* trueBranch, MBasicBlock* falseBranch)
+- : MAryControlInstruction(classOpcode),
+- operandMightEmulateUndefined_(true)
+- {
+- initOperand(0, ins);
+- setSuccessor(0, trueBranch);
+- setSuccessor(1, falseBranch);
+- }
+-
+- // Variant which may patch the ifTrue branch later.
+- MTest(MDefinition* ins, MBasicBlock* falseBranch)
+- : MTest(ins, nullptr, falseBranch)
+- {}
+-
+- public:
+- INSTRUCTION_HEADER(Test)
+- TRIVIAL_NEW_WRAPPERS
+- NAMED_OPERANDS((0, input))
+-
+- static const size_t TrueBranchIndex = 0;
+-
+- MBasicBlock* ifTrue() const {
+- return getSuccessor(0);
+- }
+- MBasicBlock* ifFalse() const {
+- return getSuccessor(1);
+- }
+- MBasicBlock* branchSuccessor(BranchDirection dir) const {
+- return (dir == TRUE_BRANCH) ? ifTrue() : ifFalse();
+- }
+-
+- AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+-
+- // We cache whether our operand might emulate undefined, but we don't want
+- // to do that from New() or the constructor, since those can be called on
+- // background threads. So make callers explicitly call it if they want us
+- // to check whether the operand might do this. If this method is never
+- // called, we'll assume our operand can emulate undefined.
+- void cacheOperandMightEmulateUndefined(CompilerConstraintList* constraints);
+- MDefinition* foldsDoubleNegation(TempAllocator& alloc);
+- MDefinition* foldsConstant(TempAllocator& alloc);
+- MDefinition* foldsTypes(TempAllocator& alloc);
+- MDefinition* foldsNeedlessControlFlow(TempAllocator& alloc);
+- MDefinition* foldsTo(TempAllocator& alloc) override;
+- void filtersUndefinedOrNull(bool trueBranch, MDefinition** subject, bool* filtersUndefined,
+- bool* filtersNull);
+-
+- void markNoOperandEmulatesUndefined() {
+- operandMightEmulateUndefined_ = false;
+- }
+- bool operandMightEmulateUndefined() const {
+- return operandMightEmulateUndefined_;
+- }
+-#ifdef DEBUG
+- bool isConsistentFloat32Use(MUse* use) const override {
+- return true;
+- }
+-#endif
+-};
+-
+-// Equivalent to MTest(true, successor, fake), except without the foldsTo
+-// method. This allows IonBuilder to insert fake CFG edges to magically protect
+-// control flow for try-catch blocks.
+-class MGotoWithFake
+- : public MAryControlInstruction<0, 2>,
+- public NoTypePolicy::Data
+-{
+- MGotoWithFake(MBasicBlock* successor, MBasicBlock* fake)
+- : MAryControlInstruction(classOpcode)
+- {
+- setSuccessor(0, successor);
+- setSuccessor(1, fake);
+- }
+-
+- public:
+- INSTRUCTION_HEADER(GotoWithFake)
+- TRIVIAL_NEW_WRAPPERS
+-
+- MBasicBlock* target() const {
+- return getSuccessor(0);
+- }
+-
+- AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+-};
+-
+-// Returns from this function to the previous caller.
+-class MReturn
+- : public MAryControlInstruction<1, 0>,
+- public BoxInputsPolicy::Data
+-{
+- explicit MReturn(MDefinition* ins)
+- : MAryControlInstruction(classOpcode)
+- {
+- initOperand(0, ins);
+- }
+-
+- public:
+- INSTRUCTION_HEADER(Return)
+- TRIVIAL_NEW_WRAPPERS
+- NAMED_OPERANDS((0, input))
+-
+- AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+-};
+-
+-class MThrow
+- : public MAryControlInstruction<1, 0>,
+- public BoxInputsPolicy::Data
+-{
+- explicit MThrow(MDefinition* ins)
+- : MAryControlInstruction(classOpcode)
+- {
+- initOperand(0, ins);
+- }
+-
+- public:
+- INSTRUCTION_HEADER(Throw)
+- TRIVIAL_NEW_WRAPPERS
+-
+- virtual AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+- bool possiblyCalls() const override {
+- return true;
+- }
+-};
+-
+ // Fabricate a type set containing only the type of the specified object.
+ TemporaryTypeSet*
+ MakeSingletonTypeSet(TempAllocator& alloc, CompilerConstraintList* constraints, JSObject* obj);
+@@ -4372,23 +4259,6 @@ class MBail : public MNullaryInstruction
+ }
+ };
+
+-class MUnreachable
+- : public MAryControlInstruction<0, 0>,
+- public NoTypePolicy::Data
+-{
+- MUnreachable()
+- : MAryControlInstruction(classOpcode)
+- { }
+-
+- public:
+- INSTRUCTION_HEADER(Unreachable)
+- TRIVIAL_NEW_WRAPPERS
+-
+- AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+-};
+-
+ // This class serve as a way to force the encoding of a snapshot, even if there
+ // is no resume point using it. This is useful to run MAssertRecoveredOnBailout
+ // assertions.
+@@ -6943,6 +6813,7 @@ class MMathFunction
+ switch(function_) {
+ case Sin:
+ case Log:
++ case Ceil:
+ case Floor:
+ case Round:
+ return true;
+@@ -7686,6 +7557,194 @@ class MArrowNewTarget
+ }
+ };
+
++class MPhifinal
++ : public MDefinition,
++ public InlineListNode<MPhi>,
++ public NoTypePolicy::Data
++{
++ using InputVector = js::Vector<MUse, 2, JitAllocPolicy>;
++ InputVector inputs_;
++
++ TruncateKind truncateKind_;
++ bool hasBackedgeType_;
++ bool triedToSpecialize_;
++ bool isIterator_;
++ bool canProduceFloat32_;
++ bool canConsumeFloat32_;
++
++#if DEBUG
++ bool specialized_;
++#endif
++
++ protected:
++ MUse* getUseFor(size_t index) override {
++ // Note: after the initial IonBuilder pass, it is OK to change phi
++ // operands such that they do not include the type sets of their
++ // operands. This can arise during e.g. value numbering, where
++ // definitions producing the same value may have different type sets.
++ MOZ_ASSERT(index < numOperands());
++ return &inputs_[index];
++ }
++ const MUse* getUseFor(size_t index) const override {
++ return &inputs_[index];
++ }
++
++ public:
++ INSTRUCTION_HEADER_WITHOUT_TYPEPOLICY(Phi)
++ virtual TypePolicy* typePolicy();
++ virtual MIRType typePolicySpecialization();
++
++ MPhi(TempAllocator& alloc, MIRType resultType)
++ : MDefinition(classOpcode),
++ inputs_(alloc),
++ truncateKind_(NoTruncate),
++ hasBackedgeType_(false),
++ triedToSpecialize_(false),
++ isIterator_(false),
++ canProduceFloat32_(false),
++ canConsumeFloat32_(false)
++#if DEBUG
++ , specialized_(false)
++#endif
++ {
++ setResultType(resultType);
++ }
++
++ static MPhi* New(TempAllocator& alloc, MIRType resultType = MIRType::Value) {
++ return new(alloc) MPhi(alloc, resultType);
++ }
++ static MPhi* New(TempAllocator::Fallible alloc, MIRType resultType = MIRType::Value) {
++ return new(alloc) MPhi(alloc.alloc, resultType);
++ }
++
++ void removeOperand(size_t index);
++ void removeAllOperands();
++
++ MDefinition* getOperand(size_t index) const override {
++ return inputs_[index].producer();
++ }
++ size_t numOperands() const override {
++ return inputs_.length();
++ }
++ size_t indexOf(const MUse* u) const final override {
++ MOZ_ASSERT(u >= &inputs_[0]);
++ MOZ_ASSERT(u <= &inputs_[numOperands() - 1]);
++ return u - &inputs_[0];
++ }
++ void replaceOperand(size_t index, MDefinition* operand) final override {
++ inputs_[index].replaceProducer(operand);
++ }
++ bool hasBackedgeType() const {
++ return hasBackedgeType_;
++ }
++ bool triedToSpecialize() const {
++ return triedToSpecialize_;
++ }
++ void specialize(MIRType type) {
++ triedToSpecialize_ = true;
++ setResultType(type);
++ }
++ bool specializeType(TempAllocator& alloc);
++
++#ifdef DEBUG
++ // Assert that this is a phi in a loop header with a unique predecessor and
++ // a unique backedge.
++ void assertLoopPhi() const;
++#else
++ void assertLoopPhi() const {}
++#endif
++
++ // Assuming this phi is in a loop header with a unique loop entry, return
++ // the phi operand along the loop entry.
++ MDefinition* getLoopPredecessorOperand() const {
++ assertLoopPhi();
++ return getOperand(0);
++ }
++
++ // Assuming this phi is in a loop header with a unique loop entry, return
++ // the phi operand along the loop backedge.
++ MDefinition* getLoopBackedgeOperand() const {
++ assertLoopPhi();
++ return getOperand(1);
++ }
++
++ // Whether this phi's type already includes information for def.
++ bool typeIncludes(MDefinition* def);
++
++ // Add types for this phi which speculate about new inputs that may come in
++ // via a loop backedge.
++ MOZ_MUST_USE bool addBackedgeType(TempAllocator& alloc, MIRType type,
++ TemporaryTypeSet* typeSet);
++
++ // Initializes the operands vector to the given capacity,
++ // permitting use of addInput() instead of addInputSlow().
++ MOZ_MUST_USE bool reserveLength(size_t length) {
++ return inputs_.reserve(length);
++ }
++
++ // Use only if capacity has been reserved by reserveLength
++ void addInput(MDefinition* ins) {
++ inputs_.infallibleEmplaceBack(ins, this);
++ }
++
++ // Appends a new input to the input vector. May perform reallocation.
++ // Prefer reserveLength() and addInput() instead, where possible.
++ MOZ_MUST_USE bool addInputSlow(MDefinition* ins) {
++ return inputs_.emplaceBack(ins, this);
++ }
++
++ // Appends a new input to the input vector. Infallible because
++ // we know the inputs fits in the vector's inline storage.
++ void addInlineInput(MDefinition* ins) {
++ MOZ_ASSERT(inputs_.length() < InputVector::InlineLength);
++ MOZ_ALWAYS_TRUE(addInputSlow(ins));
++ }
++
++ // Update the type of this phi after adding |ins| as an input. Set
++ // |*ptypeChange| to true if the type changed.
++ bool checkForTypeChange(TempAllocator& alloc, MDefinition* ins, bool* ptypeChange);
++
++ MDefinition* foldsTo(TempAllocator& alloc) override;
++ MDefinition* foldsTernary(TempAllocator& alloc);
++ MDefinition* foldsFilterTypeSet();
++
++ bool congruentTo(const MDefinition* ins) const override;
++
++ bool isIterator() const {
++ return isIterator_;
++ }
++ void setIterator() {
++ isIterator_ = true;
++ }
++
++ AliasSet getAliasSet() const override {
++ return AliasSet::None();
++ }
++ void computeRange(TempAllocator& alloc) override;
++
++ MDefinition* operandIfRedundant();
++
++ bool canProduceFloat32() const override {
++ return canProduceFloat32_;
++ }
++
++ void setCanProduceFloat32(bool can) {
++ canProduceFloat32_ = can;
++ }
++
++ bool canConsumeFloat32(MUse* use) const override {
++ return canConsumeFloat32_;
++ }
++
++ void setCanConsumeFloat32(bool can) {
++ canConsumeFloat32_ = can;
++ }
++
++ TruncateKind operandTruncateKind(size_t index) const override;
++ bool needTruncation(TruncateKind kind) override;
++ void truncate() override;
++};
++
+ // The goal of a Beta node is to split a def at a conditionally taken
+ // branch, so that uses dominated by it have a different name.
+ class MBeta
+@@ -7943,34 +8002,6 @@ class MInterruptCheck : public MNullaryI
+ }
+ };
+
+-// Directly jumps to the indicated trap, leaving Wasm code and reporting a
+-// runtime error.
+-
+-class MWasmTrap
+- : public MAryControlInstruction<0, 0>,
+- public NoTypePolicy::Data
+-{
+- wasm::Trap trap_;
+- wasm::BytecodeOffset bytecodeOffset_;
+-
+- explicit MWasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset)
+- : MAryControlInstruction(classOpcode),
+- trap_(trap),
+- bytecodeOffset_(bytecodeOffset)
+- {}
+-
+- public:
+- INSTRUCTION_HEADER(WasmTrap)
+- TRIVIAL_NEW_WRAPPERS
+-
+- AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+-
+- wasm::Trap trap() const { return trap_; }
+- wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
+-};
+-
+ // Checks if a value is JS_UNINITIALIZED_LEXICAL, bailout out if so, leaving
+ // it to baseline to throw at the correct pc.
+ class MLexicalCheck
+@@ -11046,48 +11077,6 @@ class MDispatchInstruction
+ bool appendRoots(MRootList& roots) const override;
+ };
+
+-// Polymorphic dispatch for inlining, keyed off incoming ObjectGroup.
+-class MObjectGroupDispatch : public MDispatchInstruction
+-{
+- // Map ObjectGroup (of CallProp's Target Object) -> JSFunction (yielded by the CallProp).
+- InlinePropertyTable* inlinePropertyTable_;
+-
+- MObjectGroupDispatch(TempAllocator& alloc, MDefinition* input, InlinePropertyTable* table)
+- : MDispatchInstruction(alloc, classOpcode, input),
+- inlinePropertyTable_(table)
+- { }
+-
+- public:
+- INSTRUCTION_HEADER(ObjectGroupDispatch)
+-
+- static MObjectGroupDispatch* New(TempAllocator& alloc, MDefinition* ins,
+- InlinePropertyTable* table)
+- {
+- return new(alloc) MObjectGroupDispatch(alloc, ins, table);
+- }
+-
+- InlinePropertyTable* propTable() const {
+- return inlinePropertyTable_;
+- }
+- bool appendRoots(MRootList& roots) const override;
+-};
+-
+-// Polymorphic dispatch for inlining, keyed off incoming JSFunction*.
+-class MFunctionDispatch : public MDispatchInstruction
+-{
+- MFunctionDispatch(TempAllocator& alloc, MDefinition* input)
+- : MDispatchInstruction(alloc, classOpcode, input)
+- { }
+-
+- public:
+- INSTRUCTION_HEADER(FunctionDispatch)
+-
+- static MFunctionDispatch* New(TempAllocator& alloc, MDefinition* ins) {
+- return new(alloc) MFunctionDispatch(alloc, ins);
+- }
+- bool appendRoots(MRootList& roots) const override;
+-};
+-
+ class MBindNameCache
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+@@ -12361,6 +12350,18 @@ class MNearbyInt
+
+ void printOpcode(GenericPrinter& out) const override;
+
++ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
++
++ bool canRecoverOnBailout() const override {
++ switch (roundingMode_) {
++ case RoundingMode::Up:
++ case RoundingMode::Down:
++ return true;
++ default:
++ return false;
++ }
++ }
++
+ ALLOW_CLONE(MNearbyInt)
+ };
+
+@@ -14383,34 +14384,6 @@ class MWasmParameter : public MNullaryIn
+ ABIArg abi() const { return abi_; }
+ };
+
+-class MWasmReturn
+- : public MAryControlInstruction<1, 0>,
+- public NoTypePolicy::Data
+-{
+- explicit MWasmReturn(MDefinition* ins)
+- : MAryControlInstruction(classOpcode)
+- {
+- initOperand(0, ins);
+- }
+-
+- public:
+- INSTRUCTION_HEADER(WasmReturn)
+- TRIVIAL_NEW_WRAPPERS
+-};
+-
+-class MWasmReturnVoid
+- : public MAryControlInstruction<0, 0>,
+- public NoTypePolicy::Data
+-{
+- MWasmReturnVoid()
+- : MAryControlInstruction(classOpcode)
+- { }
+-
+- public:
+- INSTRUCTION_HEADER(WasmReturnVoid)
+- TRIVIAL_NEW_WRAPPERS
+-};
+-
+ class MWasmStackArg
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+@@ -14785,3 +14758,5 @@ class AlignmentFinder<js::jit::MInstruct
+ } // namespace mozilla
+
+ #endif /* jit_MIR_h */
++
++This page was generated by Searchfox 2017-11-15 20:07.
new file mode 100644
--- /dev/null
+++ b/manderc3@192.168.42.22
@@ -0,0 +1,669 @@
+# HG changeset patch
+# User Chris Mander <mandercs3@gmail.com>
+# Parent 3bf44c3230c850478c29b5624f4d83d3f7b97b0b
+
+diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h
+--- a/js/src/jit/MIR.h
++++ b/js/src/jit/MIR.h
+@@ -15,7 +15,7 @@
+ #include "mozilla/Alignment.h"
+ #include "mozilla/Array.h"
+ #include "mozilla/Attributes.h"
+-#include "mozilla/MacroForEach.h"
++#include "mozilla/MacroMForEach.h"
+
+ #include "builtin/SIMD.h"
+ #include "jit/AtomicOp.h"
+@@ -1132,6 +1132,80 @@ class MRootList : public TempObject
+ }
+ };
+
++// An instruction is an SSA name that is inserted into a basic block's IR
++// stream.
++class MInstruction
++ : public MDefinition,
++ public InlineListNode<MInstruction>
++{
++ MResumePoint* resumePoint_;
++
++ protected:
++ // All MInstructions are using the "MFoo::New(alloc)" notation instead of
++ // the TempObject new operator. This code redefines the new operator as
++ // protected, and delegates to the TempObject new operator. Thus, the
++ // following code prevents calls to "new(alloc) MFoo" outside the MFoo
++ // members.
++ inline void* operator new(size_t nbytes, TempAllocator::Fallible view) throw() {
++ return TempObject::operator new(nbytes, view);
++ }
++ inline void* operator new(size_t nbytes, TempAllocator& alloc) {
++ return TempObject::operator new(nbytes, alloc);
++ }
++ template <class T>
++ inline void* operator new(size_t nbytes, T* pos) {
++ return TempObject::operator new(nbytes, pos);
++ }
++
++ public:
++ explicit MInstruction(Opcode op)
++ : MDefinition(op),
++ resumePoint_(nullptr)
++ { }
++
++ // Copying an instruction leaves the block and resume point as empty.
++ explicit MInstruction(const MInstruction& other)
++ : MDefinition(other),
++ resumePoint_(nullptr)
++ { }
++
++ // Convenient function used for replacing a load by the value of the store
++ // if the types are match, and boxing the value if they do not match.
++ MDefinition* foldsToStore(TempAllocator& alloc);
++
++ void setResumePoint(MResumePoint* resumePoint);
++
++ // Used to transfer the resume point to the rewritten instruction.
++ void stealResumePoint(MInstruction* ins);
++ void moveResumePointAsEntry();
++ void clearResumePoint();
++ MResumePoint* resumePoint() const {
++ return resumePoint_;
++ }
++
++ // For instructions which can be cloned with new inputs, with all other
++ // information being the same. clone() implementations do not need to worry
++ // about cloning generic MInstruction/MDefinition state like flags and
++ // resume points.
++ virtual bool canClone() const {
++ return false;
++ }
++ virtual MInstruction* clone(TempAllocator& alloc, const MDefinitionVector& inputs) const {
++ MOZ_CRASH();
++ }
++
++ // MIR instructions containing GC pointers should override this to append
++ // these pointers to the root list.
++ virtual bool appendRoots(MRootList& roots) const {
++ return true;
++ }
++
++ // Instructions needing to hook into type analysis should return a
++ // TypePolicy.
++ virtual TypePolicy* typePolicy() = 0;
++ virtual MIRType typePolicySpecialization() = 0;
++};
++
+ #define INSTRUCTION_HEADER_WITHOUT_TYPEPOLICY(opcode) \
+ static const Opcode classOpcode = Opcode::opcode; \
+ using MThisOpcode = M##opcode; \
+@@ -1203,53 +1277,6 @@ class MRootList : public TempObject
+ #define NAMED_OPERANDS(...) \
+ MOZ_FOR_EACH(NAMED_OPERAND_ACCESSOR_APPLY, (), (__VA_ARGS__))
+
+-template <size_t Arity>
+-class MAryInstruction : public MInstruction
+-{
+- mozilla::Array<MUse, Arity> operands_;
+-
+- protected:
+- MUse* getUseFor(size_t index) final override {
+- return &operands_[index];
+- }
+- const MUse* getUseFor(size_t index) const final override {
+- return &operands_[index];
+- }
+- void initOperand(size_t index, MDefinition* operand) {
+- operands_[index].init(operand, this);
+- }
+-
+- public:
+- MDefinition* getOperand(size_t index) const final override {
+- return operands_[index].producer();
+- }
+- size_t numOperands() const final override {
+- return Arity;
+- }
+-#ifdef DEBUG
+- static const size_t staticNumOperands = Arity;
+-#endif
+- size_t indexOf(const MUse* u) const final override {
+- MOZ_ASSERT(u >= &operands_[0]);
+- MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
+- return u - &operands_[0];
+- }
+- void replaceOperand(size_t index, MDefinition* operand) final override {
+- operands_[index].replaceProducer(operand);
+- }
+-
+- explicit MAryInstruction(Opcode op)
+- : MInstruction(op)
+- { }
+-
+- explicit MAryInstruction(const MAryInstruction<Arity>& other)
+- : MInstruction(other)
+- {
+- for (int i = 0; i < (int) Arity; i++) // N.B. use |int| to avoid warnings when Arity == 0
+- operands_[i].init(other.operands_[i].producer(), this);
+- }
+-};
+-
+ class MNullaryInstruction
+ : public MAryInstruction<0>,
+ public NoTypePolicy::Data
+@@ -3059,146 +3086,6 @@ NegateBranchDirection(BranchDirection di
+ return (dir == FALSE_BRANCH) ? TRUE_BRANCH : FALSE_BRANCH;
+ }
+
+-// Tests if the input instruction evaluates to true or false, and jumps to the
+-// start of a corresponding basic block.
+-class MTest
+- : public MAryControlInstruction<1, 2>,
+- public TestPolicy::Data
+-{
+- bool operandMightEmulateUndefined_;
+-
+- MTest(MDefinition* ins, MBasicBlock* trueBranch, MBasicBlock* falseBranch)
+- : MAryControlInstruction(classOpcode),
+- operandMightEmulateUndefined_(true)
+- {
+- initOperand(0, ins);
+- setSuccessor(0, trueBranch);
+- setSuccessor(1, falseBranch);
+- }
+-
+- // Variant which may patch the ifTrue branch later.
+- MTest(MDefinition* ins, MBasicBlock* falseBranch)
+- : MTest(ins, nullptr, falseBranch)
+- {}
+-
+- public:
+- INSTRUCTION_HEADER(Test)
+- TRIVIAL_NEW_WRAPPERS
+- NAMED_OPERANDS((0, input))
+-
+- static const size_t TrueBranchIndex = 0;
+-
+- MBasicBlock* ifTrue() const {
+- return getSuccessor(0);
+- }
+- MBasicBlock* ifFalse() const {
+- return getSuccessor(1);
+- }
+- MBasicBlock* branchSuccessor(BranchDirection dir) const {
+- return (dir == TRUE_BRANCH) ? ifTrue() : ifFalse();
+- }
+-
+- AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+-
+- // We cache whether our operand might emulate undefined, but we don't want
+- // to do that from New() or the constructor, since those can be called on
+- // background threads. So make callers explicitly call it if they want us
+- // to check whether the operand might do this. If this method is never
+- // called, we'll assume our operand can emulate undefined.
+- void cacheOperandMightEmulateUndefined(CompilerConstraintList* constraints);
+- MDefinition* foldsDoubleNegation(TempAllocator& alloc);
+- MDefinition* foldsConstant(TempAllocator& alloc);
+- MDefinition* foldsTypes(TempAllocator& alloc);
+- MDefinition* foldsNeedlessControlFlow(TempAllocator& alloc);
+- MDefinition* foldsTo(TempAllocator& alloc) override;
+- void filtersUndefinedOrNull(bool trueBranch, MDefinition** subject, bool* filtersUndefined,
+- bool* filtersNull);
+-
+- void markNoOperandEmulatesUndefined() {
+- operandMightEmulateUndefined_ = false;
+- }
+- bool operandMightEmulateUndefined() const {
+- return operandMightEmulateUndefined_;
+- }
+-#ifdef DEBUG
+- bool isConsistentFloat32Use(MUse* use) const override {
+- return true;
+- }
+-#endif
+-};
+-
+-// Equivalent to MTest(true, successor, fake), except without the foldsTo
+-// method. This allows IonBuilder to insert fake CFG edges to magically protect
+-// control flow for try-catch blocks.
+-class MGotoWithFake
+- : public MAryControlInstruction<0, 2>,
+- public NoTypePolicy::Data
+-{
+- MGotoWithFake(MBasicBlock* successor, MBasicBlock* fake)
+- : MAryControlInstruction(classOpcode)
+- {
+- setSuccessor(0, successor);
+- setSuccessor(1, fake);
+- }
+-
+- public:
+- INSTRUCTION_HEADER(GotoWithFake)
+- TRIVIAL_NEW_WRAPPERS
+-
+- MBasicBlock* target() const {
+- return getSuccessor(0);
+- }
+-
+- AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+-};
+-
+-// Returns from this function to the previous caller.
+-class MReturn
+- : public MAryControlInstruction<1, 0>,
+- public BoxInputsPolicy::Data
+-{
+- explicit MReturn(MDefinition* ins)
+- : MAryControlInstruction(classOpcode)
+- {
+- initOperand(0, ins);
+- }
+-
+- public:
+- INSTRUCTION_HEADER(Return)
+- TRIVIAL_NEW_WRAPPERS
+- NAMED_OPERANDS((0, input))
+-
+- AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+-};
+-
+-class MThrow
+- : public MAryControlInstruction<1, 0>,
+- public BoxInputsPolicy::Data
+-{
+- explicit MThrow(MDefinition* ins)
+- : MAryControlInstruction(classOpcode)
+- {
+- initOperand(0, ins);
+- }
+-
+- public:
+- INSTRUCTION_HEADER(Throw)
+- TRIVIAL_NEW_WRAPPERS
+-
+- virtual AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+- bool possiblyCalls() const override {
+- return true;
+- }
+-};
+-
+ // Fabricate a type set containing only the type of the specified object.
+ TemporaryTypeSet*
+ MakeSingletonTypeSet(TempAllocator& alloc, CompilerConstraintList* constraints, JSObject* obj);
+@@ -4372,23 +4259,6 @@ class MBail : public MNullaryInstruction
+ }
+ };
+
+-class MUnreachable
+- : public MAryControlInstruction<0, 0>,
+- public NoTypePolicy::Data
+-{
+- MUnreachable()
+- : MAryControlInstruction(classOpcode)
+- { }
+-
+- public:
+- INSTRUCTION_HEADER(Unreachable)
+- TRIVIAL_NEW_WRAPPERS
+-
+- AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+-};
+-
+ // This class serve as a way to force the encoding of a snapshot, even if there
+ // is no resume point using it. This is useful to run MAssertRecoveredOnBailout
+ // assertions.
+@@ -6943,6 +6813,7 @@ class MMathFunction
+ switch(function_) {
+ case Sin:
+ case Log:
++ case Ceil:
+ case Floor:
+ case Round:
+ return true;
+@@ -7686,6 +7557,194 @@ class MArrowNewTarget
+ }
+ };
+
++class MPhifinal
++ : public MDefinition,
++ public InlineListNode<MPhi>,
++ public NoTypePolicy::Data
++{
++ using InputVector = js::Vector<MUse, 2, JitAllocPolicy>;
++ InputVector inputs_;
++
++ TruncateKind truncateKind_;
++ bool hasBackedgeType_;
++ bool triedToSpecialize_;
++ bool isIterator_;
++ bool canProduceFloat32_;
++ bool canConsumeFloat32_;
++
++#if DEBUG
++ bool specialized_;
++#endif
++
++ protected:
++ MUse* getUseFor(size_t index) override {
++ // Note: after the initial IonBuilder pass, it is OK to change phi
++ // operands such that they do not include the type sets of their
++ // operands. This can arise during e.g. value numbering, where
++ // definitions producing the same value may have different type sets.
++ MOZ_ASSERT(index < numOperands());
++ return &inputs_[index];
++ }
++ const MUse* getUseFor(size_t index) const override {
++ return &inputs_[index];
++ }
++
++ public:
++ INSTRUCTION_HEADER_WITHOUT_TYPEPOLICY(Phi)
++ virtual TypePolicy* typePolicy();
++ virtual MIRType typePolicySpecialization();
++
++ MPhi(TempAllocator& alloc, MIRType resultType)
++ : MDefinition(classOpcode),
++ inputs_(alloc),
++ truncateKind_(NoTruncate),
++ hasBackedgeType_(false),
++ triedToSpecialize_(false),
++ isIterator_(false),
++ canProduceFloat32_(false),
++ canConsumeFloat32_(false)
++#if DEBUG
++ , specialized_(false)
++#endif
++ {
++ setResultType(resultType);
++ }
++
++ static MPhi* New(TempAllocator& alloc, MIRType resultType = MIRType::Value) {
++ return new(alloc) MPhi(alloc, resultType);
++ }
++ static MPhi* New(TempAllocator::Fallible alloc, MIRType resultType = MIRType::Value) {
++ return new(alloc) MPhi(alloc.alloc, resultType);
++ }
++
++ void removeOperand(size_t index);
++ void removeAllOperands();
++
++ MDefinition* getOperand(size_t index) const override {
++ return inputs_[index].producer();
++ }
++ size_t numOperands() const override {
++ return inputs_.length();
++ }
++ size_t indexOf(const MUse* u) const final override {
++ MOZ_ASSERT(u >= &inputs_[0]);
++ MOZ_ASSERT(u <= &inputs_[numOperands() - 1]);
++ return u - &inputs_[0];
++ }
++ void replaceOperand(size_t index, MDefinition* operand) final override {
++ inputs_[index].replaceProducer(operand);
++ }
++ bool hasBackedgeType() const {
++ return hasBackedgeType_;
++ }
++ bool triedToSpecialize() const {
++ return triedToSpecialize_;
++ }
++ void specialize(MIRType type) {
++ triedToSpecialize_ = true;
++ setResultType(type);
++ }
++ bool specializeType(TempAllocator& alloc);
++
++#ifdef DEBUG
++ // Assert that this is a phi in a loop header with a unique predecessor and
++ // a unique backedge.
++ void assertLoopPhi() const;
++#else
++ void assertLoopPhi() const {}
++#endif
++
++ // Assuming this phi is in a loop header with a unique loop entry, return
++ // the phi operand along the loop entry.
++ MDefinition* getLoopPredecessorOperand() const {
++ assertLoopPhi();
++ return getOperand(0);
++ }
++
++ // Assuming this phi is in a loop header with a unique loop entry, return
++ // the phi operand along the loop backedge.
++ MDefinition* getLoopBackedgeOperand() const {
++ assertLoopPhi();
++ return getOperand(1);
++ }
++
++ // Whether this phi's type already includes information for def.
++ bool typeIncludes(MDefinition* def);
++
++ // Add types for this phi which speculate about new inputs that may come in
++ // via a loop backedge.
++ MOZ_MUST_USE bool addBackedgeType(TempAllocator& alloc, MIRType type,
++ TemporaryTypeSet* typeSet);
++
++ // Initializes the operands vector to the given capacity,
++ // permitting use of addInput() instead of addInputSlow().
++ MOZ_MUST_USE bool reserveLength(size_t length) {
++ return inputs_.reserve(length);
++ }
++
++ // Use only if capacity has been reserved by reserveLength
++ void addInput(MDefinition* ins) {
++ inputs_.infallibleEmplaceBack(ins, this);
++ }
++
++ // Appends a new input to the input vector. May perform reallocation.
++ // Prefer reserveLength() and addInput() instead, where possible.
++ MOZ_MUST_USE bool addInputSlow(MDefinition* ins) {
++ return inputs_.emplaceBack(ins, this);
++ }
++
++ // Appends a new input to the input vector. Infallible because
++ // we know the inputs fits in the vector's inline storage.
++ void addInlineInput(MDefinition* ins) {
++ MOZ_ASSERT(inputs_.length() < InputVector::InlineLength);
++ MOZ_ALWAYS_TRUE(addInputSlow(ins));
++ }
++
++ // Update the type of this phi after adding |ins| as an input. Set
++ // |*ptypeChange| to true if the type changed.
++ bool checkForTypeChange(TempAllocator& alloc, MDefinition* ins, bool* ptypeChange);
++
++ MDefinition* foldsTo(TempAllocator& alloc) override;
++ MDefinition* foldsTernary(TempAllocator& alloc);
++ MDefinition* foldsFilterTypeSet();
++
++ bool congruentTo(const MDefinition* ins) const override;
++
++ bool isIterator() const {
++ return isIterator_;
++ }
++ void setIterator() {
++ isIterator_ = true;
++ }
++
++ AliasSet getAliasSet() const override {
++ return AliasSet::None();
++ }
++ void computeRange(TempAllocator& alloc) override;
++
++ MDefinition* operandIfRedundant();
++
++ bool canProduceFloat32() const override {
++ return canProduceFloat32_;
++ }
++
++ void setCanProduceFloat32(bool can) {
++ canProduceFloat32_ = can;
++ }
++
++ bool canConsumeFloat32(MUse* use) const override {
++ return canConsumeFloat32_;
++ }
++
++ void setCanConsumeFloat32(bool can) {
++ canConsumeFloat32_ = can;
++ }
++
++ TruncateKind operandTruncateKind(size_t index) const override;
++ bool needTruncation(TruncateKind kind) override;
++ void truncate() override;
++};
++
+ // The goal of a Beta node is to split a def at a conditionally taken
+ // branch, so that uses dominated by it have a different name.
+ class MBeta
+@@ -7943,34 +8002,6 @@ class MInterruptCheck : public MNullaryI
+ }
+ };
+
+-// Directly jumps to the indicated trap, leaving Wasm code and reporting a
+-// runtime error.
+-
+-class MWasmTrap
+- : public MAryControlInstruction<0, 0>,
+- public NoTypePolicy::Data
+-{
+- wasm::Trap trap_;
+- wasm::BytecodeOffset bytecodeOffset_;
+-
+- explicit MWasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset)
+- : MAryControlInstruction(classOpcode),
+- trap_(trap),
+- bytecodeOffset_(bytecodeOffset)
+- {}
+-
+- public:
+- INSTRUCTION_HEADER(WasmTrap)
+- TRIVIAL_NEW_WRAPPERS
+-
+- AliasSet getAliasSet() const override {
+- return AliasSet::None();
+- }
+-
+- wasm::Trap trap() const { return trap_; }
+- wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
+-};
+-
+ // Checks if a value is JS_UNINITIALIZED_LEXICAL, bailout out if so, leaving
+ // it to baseline to throw at the correct pc.
+ class MLexicalCheck
+@@ -11046,48 +11077,6 @@ class MDispatchInstruction
+ bool appendRoots(MRootList& roots) const override;
+ };
+
+-// Polymorphic dispatch for inlining, keyed off incoming ObjectGroup.
+-class MObjectGroupDispatch : public MDispatchInstruction
+-{
+- // Map ObjectGroup (of CallProp's Target Object) -> JSFunction (yielded by the CallProp).
+- InlinePropertyTable* inlinePropertyTable_;
+-
+- MObjectGroupDispatch(TempAllocator& alloc, MDefinition* input, InlinePropertyTable* table)
+- : MDispatchInstruction(alloc, classOpcode, input),
+- inlinePropertyTable_(table)
+- { }
+-
+- public:
+- INSTRUCTION_HEADER(ObjectGroupDispatch)
+-
+- static MObjectGroupDispatch* New(TempAllocator& alloc, MDefinition* ins,
+- InlinePropertyTable* table)
+- {
+- return new(alloc) MObjectGroupDispatch(alloc, ins, table);
+- }
+-
+- InlinePropertyTable* propTable() const {
+- return inlinePropertyTable_;
+- }
+- bool appendRoots(MRootList& roots) const override;
+-};
+-
+-// Polymorphic dispatch for inlining, keyed off incoming JSFunction*.
+-class MFunctionDispatch : public MDispatchInstruction
+-{
+- MFunctionDispatch(TempAllocator& alloc, MDefinition* input)
+- : MDispatchInstruction(alloc, classOpcode, input)
+- { }
+-
+- public:
+- INSTRUCTION_HEADER(FunctionDispatch)
+-
+- static MFunctionDispatch* New(TempAllocator& alloc, MDefinition* ins) {
+- return new(alloc) MFunctionDispatch(alloc, ins);
+- }
+- bool appendRoots(MRootList& roots) const override;
+-};
+-
+ class MBindNameCache
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+@@ -12361,6 +12350,18 @@ class MNearbyInt
+
+ void printOpcode(GenericPrinter& out) const override;
+
++ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
++
++ bool canRecoverOnBailout() const override {
++ switch (roundingMode_) {
++ case RoundingMode::Up:
++ case RoundingMode::Down:
++ return true;
++ default:
++ return false;
++ }
++ }
++
+ ALLOW_CLONE(MNearbyInt)
+ };
+
+@@ -14383,34 +14384,6 @@ class MWasmParameter : public MNullaryIn
+ ABIArg abi() const { return abi_; }
+ };
+
+-class MWasmReturn
+- : public MAryControlInstruction<1, 0>,
+- public NoTypePolicy::Data
+-{
+- explicit MWasmReturn(MDefinition* ins)
+- : MAryControlInstruction(classOpcode)
+- {
+- initOperand(0, ins);
+- }
+-
+- public:
+- INSTRUCTION_HEADER(WasmReturn)
+- TRIVIAL_NEW_WRAPPERS
+-};
+-
+-class MWasmReturnVoid
+- : public MAryControlInstruction<0, 0>,
+- public NoTypePolicy::Data
+-{
+- MWasmReturnVoid()
+- : MAryControlInstruction(classOpcode)
+- { }
+-
+- public:
+- INSTRUCTION_HEADER(WasmReturnVoid)
+- TRIVIAL_NEW_WRAPPERS
+-};
+-
+ class MWasmStackArg
+ : public MUnaryInstruction,
+ public NoTypePolicy::Data
+@@ -14785,3 +14758,5 @@ class AlignmentFinder<js::jit::MInstruct
+ } // namespace mozilla
+
+ #endif /* jit_MIR_h */
++
++This page was generated by Searchfox 2017-11-15 20:07.
new file mode 100644
--- /dev/null
+++ b/nohup.out
@@ -0,0 +1,76 @@
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 46635 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 47779 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 48221 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 50224 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 52520 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 55588 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 57509 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 58274 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 60575 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 61578 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 61594 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 63189 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 63203 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 65539 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 66700 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 76363 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 80105 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 80668 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 82603 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 83827 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 85533 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 94673 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 102981 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 106014 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 108259 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 110174 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 110931 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 113977 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 116967 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 118098 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 118115 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 119164 was not found when attempting to remove it
+
+(sublime_text:2250): GLib-CRITICAL **: Source ID 119181 was not found when attempting to remove it
+
+(sublime_text:2264): GLib-CRITICAL **: Source ID 46993 was not found when attempting to remove it
+
+(sublime_text:2245): GLib-CRITICAL **: Source ID 49747 was not found when attempting to remove it
+
+(sublime_text:2245): GLib-CRITICAL **: Source ID 52029 was not found when attempting to remove it
+
+(sublime_text:2245): GLib-CRITICAL **: Source ID 55066 was not found when attempting to remove it
+
+(sublime_text:2243): GLib-CRITICAL **: Source ID 36841 was not found when attempting to remove it
new file mode 100755
index 0000000000000000000000000000000000000000..7b20de1ceb4ed01563882312ef8dcf6d503b5eb3
GIT binary patch
literal 31318
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
BK<NMg
new file mode 100755
index 0000000000000000000000000000000000000000..05c134eecb783a5cf7fa5b0f11a3f0fb2c11fc79
GIT binary patch
literal 18824
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
new file mode 100755
index 0000000000000000000000000000000000000000..7b20de1ceb4ed01563882312ef8dcf6d503b5eb3
GIT binary patch
literal 31318
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
BK<NMg
new file mode 100755
index 0000000000000000000000000000000000000000..05c134eecb783a5cf7fa5b0f11a3f0fb2c11fc79
GIT binary patch
literal 18824
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
new file mode 100755
index 0000000000000000000000000000000000000000..5a6c2129fec2254b4cdd16c1e2ea7da87a7ed8e5
GIT binary patch
literal 47902
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	`7`p%5sK4iTm
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
PT%f*vWL7QlODF#i(Tlr9
new file mode 100755
index 0000000000000000000000000000000000000000..63becd37aac4fafdda98b3923fd6fe58a9ec500d
GIT binary patch
literal 28454
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
new file mode 100644
index 0000000000000000000000000000000000000000..065fefc28bc4c082103f14cc4dd6a34e289ddf04
GIT binary patch
literal 64504
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
new file mode 100644
index 0000000000000000000000000000000000000000..3559a996bda6efeebe3c6aef473e6e0490c19241
GIT binary patch
literal 31176
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
new file mode 100755
index 0000000000000000000000000000000000000000..5a6c2129fec2254b4cdd16c1e2ea7da87a7ed8e5
GIT binary patch
literal 47902
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	`7`p%5sK4iTm
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
PT%f*vWL7QlODF#i(Tlr9
new file mode 100755
index 0000000000000000000000000000000000000000..63becd37aac4fafdda98b3923fd6fe58a9ec500d
GIT binary patch
literal 28454
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
new file mode 100644
--- /dev/null
+++ b/toolkit/components/downloads/nsDownloadManager.cpp.orig
@@ -0,0 +1,3783 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Unused.h"
+
+#include "mozIStorageService.h"
+#include "nsIAlertsService.h"
+#include "nsIArray.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIDOMWindow.h"
+#include "nsIDownloadHistory.h"
+#include "nsIDownloadManagerUI.h"
+#include "nsIFileURL.h"
+#include "nsIMIMEService.h"
+#include "nsIParentalControlsService.h"
+#include "nsIPrefService.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIPromptService.h"
+#include "nsIPropertyBag2.h"
+#include "nsIResumableChannel.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsIWindowMediator.h"
+#include "nsILocalFileWin.h"
+#include "nsILoadContext.h"
+#include "nsIXULAppInfo.h"
+#include "nsContentUtils.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsArrayEnumerator.h"
+#include "nsCExternalHandlerService.h"
+#include "nsCRTGlue.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDownloadManager.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "prtime.h"
+
+#include "mozStorageCID.h"
+#include "nsDocShellCID.h"
+#include "nsEmbedCID.h"
+#include "nsToolkitCompsCID.h"
+
+#include "mozilla/net/ReferrerPolicy.h"
+
+#include "SQLFunctions.h"
+
+#include "mozilla/Preferences.h"
+
+#ifdef XP_WIN
+#include <shlobj.h>
+#include "nsWindowsHelpers.h"
+#ifdef DOWNLOAD_SCANNER
+#include "nsDownloadScanner.h"
+#endif
+#endif
+
+#ifdef XP_MACOSX
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "FennecJNIWrappers.h"
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+#include <gtk/gtk.h>
+#endif
+
+using namespace mozilla;
+using mozilla::downloads::GenerateGUID;
+
+#define DOWNLOAD_MANAGER_BUNDLE "chrome://mozapps/locale/downloads/downloads.properties"
+#define DOWNLOAD_MANAGER_ALERT_ICON "chrome://mozapps/skin/downloads/downloadIcon.png"
+#define PREF_BD_USEJSTRANSFER "browser.download.useJSTransfer"
+#define PREF_BDM_SHOWALERTONCOMPLETE "browser.download.manager.showAlertOnComplete"
+#define PREF_BDM_SHOWALERTINTERVAL "browser.download.manager.showAlertInterval"
+#define PREF_BDM_RETENTION "browser.download.manager.retention"
+#define PREF_BDM_QUITBEHAVIOR "browser.download.manager.quitBehavior"
+#define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
+#define PREF_BDM_SCANWHENDONE "browser.download.manager.scanWhenDone"
+#define PREF_BDM_RESUMEONWAKEDELAY "browser.download.manager.resumeOnWakeDelay"
+#define PREF_BH_DELETETEMPFILEONEXIT "browser.helperApps.deleteTempFileOnExit"
+
+static const int64_t gUpdateInterval = 400 * PR_USEC_PER_MSEC;
+
+#define DM_SCHEMA_VERSION 9
+#define DM_DB_NAME NS_LITERAL_STRING("downloads.sqlite")
+#define DM_DB_CORRUPT_FILENAME NS_LITERAL_STRING("downloads.sqlite.corrupt")
+
+#define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsDownloadManager
+
+NS_IMPL_ISUPPORTS(
+ nsDownloadManager
+, nsIDownloadManager
+, nsINavHistoryObserver
+, nsIObserver
+, nsISupportsWeakReference
+)
+
+nsDownloadManager *nsDownloadManager::gDownloadManagerService = nullptr;
+
+nsDownloadManager *
+nsDownloadManager::GetSingleton()
+{
+ if (gDownloadManagerService) {
+ NS_ADDREF(gDownloadManagerService);
+ return gDownloadManagerService;
+ }
+
+ gDownloadManagerService = new nsDownloadManager();
+ if (gDownloadManagerService) {
+#if defined(MOZ_WIDGET_GTK)
+ g_type_init();
+#endif
+ NS_ADDREF(gDownloadManagerService);
+ if (NS_FAILED(gDownloadManagerService->Init()))
+ NS_RELEASE(gDownloadManagerService);
+ }
+
+ return gDownloadManagerService;
+}
+
+nsDownloadManager::~nsDownloadManager()
+{
+#ifdef DOWNLOAD_SCANNER
+ if (mScanner) {
+ delete mScanner;
+ mScanner = nullptr;
+ }
+#endif
+ gDownloadManagerService = nullptr;
+}
+
+nsresult
+nsDownloadManager::ResumeRetry(nsDownload *aDl)
+{
+ // Keep a reference in case we need to cancel the download
+ RefPtr<nsDownload> dl = aDl;
+
+ // Try to resume the active download
+ nsresult rv = dl->Resume();
+
+ // If not, try to retry the download
+ if (NS_FAILED(rv)) {
+ // First cancel the download so it's no longer active
+ rv = dl->Cancel();
+
+ // Then retry it
+ if (NS_SUCCEEDED(rv))
+ rv = dl->Retry();
+ }
+
+ return rv;
+}
+
+nsresult
+nsDownloadManager::PauseAllDownloads(bool aSetResume)
+{
+ nsresult rv = PauseAllDownloads(mCurrentDownloads, aSetResume);
+ nsresult rv2 = PauseAllDownloads(mCurrentPrivateDownloads, aSetResume);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::PauseAllDownloads(nsCOMArray<nsDownload>& aDownloads, bool aSetResume)
+{
+ nsresult retVal = NS_OK;
+ for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
+ RefPtr<nsDownload> dl = aDownloads[i];
+
+ // Only pause things that need to be paused
+ if (!dl->IsPaused()) {
+ // Set auto-resume before pausing so that it gets into the DB
+ dl->mAutoResume = aSetResume ? nsDownload::AUTO_RESUME :
+ nsDownload::DONT_RESUME;
+
+ // Try to pause the download but don't bail now if we fail
+ nsresult rv = dl->Pause();
+ if (NS_FAILED(rv))
+ retVal = rv;
+ }
+ }
+
+ return retVal;
+}
+
+nsresult
+nsDownloadManager::ResumeAllDownloads(bool aResumeAll)
+{
+ nsresult rv = ResumeAllDownloads(mCurrentDownloads, aResumeAll);
+ nsresult rv2 = ResumeAllDownloads(mCurrentPrivateDownloads, aResumeAll);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::ResumeAllDownloads(nsCOMArray<nsDownload>& aDownloads, bool aResumeAll)
+{
+ nsresult retVal = NS_OK;
+ for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
+ RefPtr<nsDownload> dl = aDownloads[i];
+
+ // If aResumeAll is true, then resume everything; otherwise, check if the
+ // download should auto-resume
+ if (aResumeAll || dl->ShouldAutoResume()) {
+ // Reset auto-resume before retrying so that it gets into the DB through
+ // ResumeRetry's eventual call to SetState. We clear the value now so we
+ // don't accidentally query completed downloads that were previously
+ // auto-resumed (and try to resume them).
+ dl->mAutoResume = nsDownload::DONT_RESUME;
+
+ // Try to resume/retry the download but don't bail now if we fail
+ nsresult rv = ResumeRetry(dl);
+ if (NS_FAILED(rv))
+ retVal = rv;
+ }
+ }
+
+ return retVal;
+}
+
+nsresult
+nsDownloadManager::RemoveAllDownloads()
+{
+ nsresult rv = RemoveAllDownloads(mCurrentDownloads);
+ nsresult rv2 = RemoveAllDownloads(mCurrentPrivateDownloads);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::RemoveAllDownloads(nsCOMArray<nsDownload>& aDownloads)
+{
+ nsresult rv = NS_OK;
+ for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
+ RefPtr<nsDownload> dl = aDownloads[0];
+
+ nsresult result = NS_OK;
+ if (!dl->mPrivate && dl->IsPaused() && GetQuitBehavior() != QUIT_AND_CANCEL)
+ aDownloads.RemoveObject(dl);
+ else
+ result = dl->Cancel();
+
+ // Track the failure, but don't miss out on other downloads
+ if (NS_FAILED(result))
+ rv = result;
+ }
+
+ return rv;
+}
+
+nsresult
+nsDownloadManager::RemoveDownloadsForURI(mozIStorageStatement* aStatement, nsIURI *aURI)
+{
+ mozStorageStatementScoper scope(aStatement);
+
+ nsAutoCString source;
+ nsresult rv = aURI->GetSpec(source);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStatement->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("source"), source);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ AutoTArray<nsCString, 4> downloads;
+ // Get all the downloads that match the provided URI
+ while (NS_SUCCEEDED(aStatement->ExecuteStep(&hasMore)) &&
+ hasMore) {
+ nsAutoCString downloadGuid;
+ rv = aStatement->GetUTF8String(0, downloadGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ downloads.AppendElement(downloadGuid);
+ }
+
+ // Remove each download ignoring any failure so we reach other downloads
+ for (int32_t i = downloads.Length(); --i >= 0; )
+ (void)RemoveDownload(downloads[i]);
+
+ return NS_OK;
+}
+
+void // static
+nsDownloadManager::ResumeOnWakeCallback(nsITimer *aTimer, void *aClosure)
+{
+ // Resume the downloads that were set to autoResume
+ nsDownloadManager *dlMgr = static_cast<nsDownloadManager *>(aClosure);
+ (void)dlMgr->ResumeAllDownloads(false);
+}
+
+already_AddRefed<mozIStorageConnection>
+nsDownloadManager::GetFileDBConnection(nsIFile *dbFile) const
+{
+ NS_ASSERTION(dbFile, "GetFileDBConnection called with an invalid nsIFile");
+
+ nsCOMPtr<mozIStorageService> storage =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(storage, nullptr);
+
+ nsCOMPtr<mozIStorageConnection> conn;
+ nsresult rv = storage->OpenDatabase(dbFile, getter_AddRefs(conn));
+ if (rv == NS_ERROR_FILE_CORRUPTED) {
+ // delete and try again, since we don't care so much about losing a user's
+ // download history
+ rv = dbFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ rv = storage->OpenDatabase(dbFile, getter_AddRefs(conn));
+ }
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return conn.forget();
+}
+
+already_AddRefed<mozIStorageConnection>
+nsDownloadManager::GetPrivateDBConnection() const
+{
+ nsCOMPtr<mozIStorageService> storage =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(storage, nullptr);
+
+ nsCOMPtr<mozIStorageConnection> conn;
+ nsresult rv = storage->OpenSpecialDatabase("memory", getter_AddRefs(conn));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return conn.forget();
+}
+
+void
+nsDownloadManager::CloseAllDBs()
+{
+ CloseDB(mDBConn, mUpdateDownloadStatement, mGetIdsForURIStatement);
+ CloseDB(mPrivateDBConn, mUpdatePrivateDownloadStatement, mGetPrivateIdsForURIStatement);
+}
+
+void
+nsDownloadManager::CloseDB(mozIStorageConnection* aDBConn,
+ mozIStorageStatement* aUpdateStmt,
+ mozIStorageStatement* aGetIdsStmt)
+{
+ DebugOnly<nsresult> rv = aGetIdsStmt->Finalize();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = aUpdateStmt->Finalize();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = aDBConn->AsyncClose(nullptr);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+static nsresult
+InitSQLFunctions(mozIStorageConnection* aDBConn)
+{
+ nsresult rv = mozilla::downloads::GenerateGUIDFunction::create(aDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::InitPrivateDB()
+{
+ bool ready = false;
+ if (mPrivateDBConn && NS_SUCCEEDED(mPrivateDBConn->GetConnectionReady(&ready)) && ready)
+ CloseDB(mPrivateDBConn, mUpdatePrivateDownloadStatement, mGetPrivateIdsForURIStatement);
+ mPrivateDBConn = GetPrivateDBConnection();
+ if (!mPrivateDBConn)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = InitSQLFunctions(mPrivateDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CreateTable(mPrivateDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitStatements(mPrivateDBConn, getter_AddRefs(mUpdatePrivateDownloadStatement),
+ getter_AddRefs(mGetPrivateIdsForURIStatement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::InitFileDB()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> dbFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dbFile->Append(DM_DB_NAME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool ready = false;
+ if (mDBConn && NS_SUCCEEDED(mDBConn->GetConnectionReady(&ready)) && ready)
+ CloseDB(mDBConn, mUpdateDownloadStatement, mGetIdsForURIStatement);
+ mDBConn = GetFileDBConnection(dbFile);
+ NS_ENSURE_TRUE(mDBConn, NS_ERROR_NOT_AVAILABLE);
+
+ rv = InitSQLFunctions(mDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool tableExists;
+ rv = mDBConn->TableExists(NS_LITERAL_CSTRING("moz_downloads"), &tableExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!tableExists) {
+ rv = CreateTable(mDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We're done with the initialization now and can skip the remaining
+ // upgrading logic.
+ return NS_OK;
+ }
+
+ // Checking the database schema now
+ int32_t schemaVersion;
+ rv = mDBConn->GetSchemaVersion(&schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Changing the database? Be sure to do these two things!
+ // 1) Increment DM_SCHEMA_VERSION
+ // 2) Implement the proper downgrade/upgrade code for the current version
+
+ switch (schemaVersion) {
+ // Upgrading
+ // Every time you increment the database schema, you need to implement
+ // the upgrading code from the previous version to the new one.
+ // Also, don't forget to make a unit test to test your upgrading code!
+ case 1: // Drop a column (iconURL) from the database (bug 385875)
+ {
+ // Safely wrap this in a transaction so we don't hose the whole DB
+ mozStorageTransaction safeTransaction(mDBConn, true);
+
+ // Create a temporary table that will store the existing records
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE moz_downloads_backup ("
+ "id INTEGER PRIMARY KEY, "
+ "name TEXT, "
+ "source TEXT, "
+ "target TEXT, "
+ "startTime INTEGER, "
+ "endTime INTEGER, "
+ "state INTEGER"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Insert into a temporary table
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_downloads_backup "
+ "SELECT id, name, source, target, startTime, endTime, state "
+ "FROM moz_downloads"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Drop the old table
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE moz_downloads"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now recreate it with this schema version
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_downloads ("
+ "id INTEGER PRIMARY KEY, "
+ "name TEXT, "
+ "source TEXT, "
+ "target TEXT, "
+ "startTime INTEGER, "
+ "endTime INTEGER, "
+ "state INTEGER"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Insert the data back into it
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_downloads "
+ "SELECT id, name, source, target, startTime, endTime, state "
+ "FROM moz_downloads_backup"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // And drop our temporary table
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE moz_downloads_backup"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 2;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 2: // Add referrer column to the database
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN referrer TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 3;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 3: // This version adds a column to the database (entityID)
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN entityID TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 4;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 4: // This version adds a column to the database (tempPath)
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN tempPath TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 5;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 5: // This version adds two columns for tracking transfer progress
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN currBytes INTEGER NOT NULL DEFAULT 0"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN maxBytes INTEGER NOT NULL DEFAULT -1"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 6;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 6: // This version adds three columns to DB (MIME type related info)
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN mimeType TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN preferredApplication TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN preferredAction INTEGER NOT NULL DEFAULT 0"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 7;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 7: // This version adds a column to remember to auto-resume downloads
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN autoResume INTEGER NOT NULL DEFAULT 0"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 8;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // Warning: schema versions >=8 must take into account that they can
+ // be operating on schemas from unknown, future versions that have
+ // been downgraded. Operations such as adding columns may fail,
+ // since the column may already exist.
+
+ case 8: // This version adds a column for GUIDs
+ {
+ bool exists;
+ rv = mDBConn->IndexExists(NS_LITERAL_CSTRING("moz_downloads_guid_uniqueindex"),
+ &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads ADD COLUMN guid TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE UNIQUE INDEX moz_downloads_guid_uniqueindex ON moz_downloads (guid)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads SET guid = GENERATE_GUID() WHERE guid ISNULL"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the database schema
+ schemaVersion = 9;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+
+ // Extra sanity checking for developers
+#ifndef DEBUG
+ MOZ_FALLTHROUGH;
+ case DM_SCHEMA_VERSION:
+#endif
+ break;
+
+ case 0:
+ {
+ NS_WARNING("Could not get download database's schema version!");
+
+ // The table may still be usable - someone may have just messed with the
+ // schema version, so let's just treat this like a downgrade and verify
+ // that the needed columns are there. If they aren't there, we'll drop
+ // the table anyway.
+ rv = mDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to downgrade check
+ MOZ_FALLTHROUGH;
+
+ // Downgrading
+ // If columns have been added to the table, we can still use the ones we
+ // understand safely. If columns have been deleted or alterd, we just
+ // drop the table and start from scratch. If you change how a column
+ // should be interpreted, make sure you also change its name so this
+ // check will catch it.
+ default:
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id, name, source, target, tempPath, startTime, endTime, state, "
+ "referrer, entityID, currBytes, maxBytes, mimeType, "
+ "preferredApplication, preferredAction, autoResume, guid "
+ "FROM moz_downloads"), getter_AddRefs(stmt));
+ if (NS_SUCCEEDED(rv)) {
+ // We have a database that contains all of the elements that make up
+ // the latest known schema. Reset the version to force an upgrade
+ // path if this downgraded database is used in a later version.
+ mDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
+ break;
+ }
+
+ // if the statement fails, that means all the columns were not there.
+ // First we backup the database
+ nsCOMPtr<mozIStorageService> storage =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(storage, NS_ERROR_NOT_AVAILABLE);
+ nsCOMPtr<nsIFile> backup;
+ rv = storage->BackupDatabaseFile(dbFile, DM_DB_CORRUPT_FILENAME, nullptr,
+ getter_AddRefs(backup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Then we dump it
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE moz_downloads"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CreateTable(mDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::CreateTable(mozIStorageConnection* aDBConn)
+{
+ nsresult rv = aDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_downloads ("
+ "id INTEGER PRIMARY KEY, "
+ "name TEXT, "
+ "source TEXT, "
+ "target TEXT, "
+ "tempPath TEXT, "
+ "startTime INTEGER, "
+ "endTime INTEGER, "
+ "state INTEGER, "
+ "referrer TEXT, "
+ "entityID TEXT, "
+ "currBytes INTEGER NOT NULL DEFAULT 0, "
+ "maxBytes INTEGER NOT NULL DEFAULT -1, "
+ "mimeType TEXT, "
+ "preferredApplication TEXT, "
+ "preferredAction INTEGER NOT NULL DEFAULT 0, "
+ "autoResume INTEGER NOT NULL DEFAULT 0, "
+ "guid TEXT"
+ ")"));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE UNIQUE INDEX moz_downloads_guid_uniqueindex "
+ "ON moz_downloads(guid)"));
+ return rv;
+}
+
+nsresult
+nsDownloadManager::RestoreDatabaseState()
+{
+ // Restore downloads that were in a scanning state. We can assume that they
+ // have been dealt with by the virus scanner
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads "
+ "SET state = :state "
+ "WHERE state = :state_cond"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_FINISHED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state_cond"), nsIDownloadManager::DOWNLOAD_SCANNING);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Convert supposedly-active downloads into downloads that should auto-resume
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads "
+ "SET autoResume = :autoResume "
+ "WHERE state = :notStarted "
+ "OR state = :queued "
+ "OR state = :downloading"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::AUTO_RESUME);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("notStarted"), nsIDownloadManager::DOWNLOAD_NOTSTARTED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("queued"), nsIDownloadManager::DOWNLOAD_QUEUED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("downloading"), nsIDownloadManager::DOWNLOAD_DOWNLOADING);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Switch any download that is supposed to automatically resume and is in a
+ // finished state to *not* automatically resume. See Bug 409179 for details.
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads "
+ "SET autoResume = :autoResume "
+ "WHERE state = :state "
+ "AND autoResume = :autoResume_cond"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::DONT_RESUME);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_FINISHED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume_cond"), nsDownload::AUTO_RESUME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::RestoreActiveDownloads()
+{
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id "
+ "FROM moz_downloads "
+ "WHERE (state = :state AND LENGTH(entityID) > 0) "
+ "OR autoResume != :autoResume"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_PAUSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::DONT_RESUME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult retVal = NS_OK;
+ bool hasResults;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResults)) && hasResults) {
+ RefPtr<nsDownload> dl;
+ // Keep trying to add even if we fail one, but make sure to return failure.
+ // Additionally, be careful to not call anything that tries to change the
+ // database because we're iterating over a live statement.
+ if (NS_FAILED(GetDownloadFromDB(stmt->AsInt32(0), getter_AddRefs(dl))) ||
+ NS_FAILED(AddToCurrentDownloads(dl)))
+ retVal = NS_ERROR_FAILURE;
+ }
+
+ // Try to resume only the downloads that should auto-resume
+ rv = ResumeAllDownloads(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return retVal;
+}
+
+int64_t
+nsDownloadManager::AddDownloadToDB(const nsAString &aName,
+ const nsACString &aSource,
+ const nsACString &aTarget,
+ const nsAString &aTempPath,
+ int64_t aStartTime,
+ int64_t aEndTime,
+ const nsACString &aMimeType,
+ const nsACString &aPreferredApp,
+ nsHandlerInfoAction aPreferredAction,
+ bool aPrivate,
+ nsACString& aNewGUID)
+{
+ mozIStorageConnection* dbConn = aPrivate ? mPrivateDBConn : mDBConn;
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_downloads "
+ "(name, source, target, tempPath, startTime, endTime, state, "
+ "mimeType, preferredApplication, preferredAction, guid) VALUES "
+ "(:name, :source, :target, :tempPath, :startTime, :endTime, :state, "
+ ":mimeType, :preferredApplication, :preferredAction, :guid)"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("source"), aSource);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("target"), aTarget);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("tempPath"), aTempPath);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), aStartTime);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), aEndTime);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_NOTSTARTED);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("mimeType"), aMimeType);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("preferredApplication"), aPreferredApp);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("preferredAction"), aPreferredAction);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ nsAutoCString guid;
+ rv = GenerateGUID(guid);
+ NS_ENSURE_SUCCESS(rv, 0);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ bool hasMore;
+ rv = stmt->ExecuteStep(&hasMore); // we want to keep our lock
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ int64_t id = 0;
+ rv = dbConn->GetLastInsertRowID(&id);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ aNewGUID = guid;
+
+ // lock on DB from statement will be released once we return
+ return id;
+}
+
+nsresult
+nsDownloadManager::InitDB()
+{
+ nsresult rv = InitPrivateDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitFileDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitStatements(mDBConn, getter_AddRefs(mUpdateDownloadStatement),
+ getter_AddRefs(mGetIdsForURIStatement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::InitStatements(mozIStorageConnection* aDBConn,
+ mozIStorageStatement** aUpdateStatement,
+ mozIStorageStatement** aGetIdsStatement)
+{
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads "
+ "SET tempPath = :tempPath, startTime = :startTime, endTime = :endTime, "
+ "state = :state, referrer = :referrer, entityID = :entityID, "
+ "currBytes = :currBytes, maxBytes = :maxBytes, autoResume = :autoResume "
+ "WHERE id = :id"), aUpdateStatement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT guid "
+ "FROM moz_downloads "
+ "WHERE source = :source"), aGetIdsStatement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::Init()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService)
+ return NS_ERROR_FAILURE;
+
+ rv = bundleService->CreateBundle(DOWNLOAD_MANAGER_BUNDLE,
+ getter_AddRefs(mBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#if !defined(MOZ_JSDOWNLOADS)
+ // When MOZ_JSDOWNLOADS is undefined, we still check the preference that can
+ // be used to enable the JavaScript API during the migration process.
+ mUseJSTransfer = Preferences::GetBool(PREF_BD_USEJSTRANSFER, false);
+#else
+ mUseJSTransfer = true;
+#endif
+
+ if (mUseJSTransfer)
+ return NS_OK;
+
+ // Clean up any old downloads.rdf files from before Firefox 3
+ {
+ nsCOMPtr<nsIFile> oldDownloadsFile;
+ bool fileExists;
+ if (NS_SUCCEEDED(NS_GetSpecialDirectory(NS_APP_DOWNLOADS_50_FILE,
+ getter_AddRefs(oldDownloadsFile))) &&
+ NS_SUCCEEDED(oldDownloadsFile->Exists(&fileExists)) &&
+ fileExists) {
+ (void)oldDownloadsFile->Remove(false);
+ }
+ }
+
+ mObserverService = mozilla::services::GetObserverService();
+ if (!mObserverService)
+ return NS_ERROR_FAILURE;
+
+ rv = InitDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef DOWNLOAD_SCANNER
+ mScanner = new nsDownloadScanner();
+ if (!mScanner)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = mScanner->Init();
+ if (NS_FAILED(rv)) {
+ delete mScanner;
+ mScanner = nullptr;
+ }
+#endif
+
+ // Do things *after* initializing various download manager properties such as
+ // restoring downloads to a consistent state
+ rv = RestoreDatabaseState();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = RestoreActiveDownloads();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to restore all active downloads");
+
+ nsCOMPtr<nsINavHistoryService> history =
+ do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
+
+ (void)mObserverService->NotifyObservers(
+ static_cast<nsIDownloadManager *>(this),
+ "download-manager-initialized",
+ nullptr);
+
+ // The following AddObserver calls must be the last lines in this function,
+ // because otherwise, this function may fail (and thus, this object would be not
+ // completely initialized), but the observerservice would still keep a reference
+ // to us and notify us about shutdown, which may cause crashes.
+ // failure to add an observer is not critical
+ (void)mObserverService->AddObserver(this, "quit-application", true);
+ (void)mObserverService->AddObserver(this, "quit-application-requested", true);
+ (void)mObserverService->AddObserver(this, "offline-requested", true);
+ (void)mObserverService->AddObserver(this, "sleep_notification", true);
+ (void)mObserverService->AddObserver(this, "wake_notification", true);
+ (void)mObserverService->AddObserver(this, "suspend_process_notification", true);
+ (void)mObserverService->AddObserver(this, "resume_process_notification", true);
+ (void)mObserverService->AddObserver(this, "profile-before-change", true);
+ (void)mObserverService->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, true);
+ (void)mObserverService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, true);
+ (void)mObserverService->AddObserver(this, "last-pb-context-exited", true);
+ (void)mObserverService->AddObserver(this, "last-pb-context-exiting", true);
+
+ if (history)
+ (void)history->AddObserver(this, true);
+
+ return NS_OK;
+}
+
+int32_t
+nsDownloadManager::GetRetentionBehavior()
+{
+ // We use 0 as the default, which is "remove when done"
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ int32_t val;
+ rv = pref->GetIntPref(PREF_BDM_RETENTION, &val);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ // Allow the Downloads Panel to change the retention behavior. We do this to
+ // allow proper migration to the new feature when using the same profile on
+ // multiple versions of the product (bug 697678). Implementation note: in
+ // order to allow observers to change the retention value, we have to pass an
+ // object in the aSubject parameter, we cannot use aData for that.
+ nsCOMPtr<nsISupportsPRInt32> retentionBehavior =
+ do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID);
+ retentionBehavior->SetData(val);
+ (void)mObserverService->NotifyObservers(retentionBehavior,
+ "download-manager-change-retention",
+ nullptr);
+ retentionBehavior->GetData(&val);
+
+ return val;
+}
+
+enum nsDownloadManager::QuitBehavior
+nsDownloadManager::GetQuitBehavior()
+{
+ // We use 0 as the default, which is "remember and resume the download"
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, QUIT_AND_RESUME);
+
+ int32_t val;
+ rv = pref->GetIntPref(PREF_BDM_QUITBEHAVIOR, &val);
+ NS_ENSURE_SUCCESS(rv, QUIT_AND_RESUME);
+
+ switch (val) {
+ case 1:
+ return QUIT_AND_PAUSE;
+ case 2:
+ return QUIT_AND_CANCEL;
+ default:
+ return QUIT_AND_RESUME;
+ }
+}
+
+// Using a globally-unique GUID, search all databases (both private and public).
+// A return value of NS_ERROR_NOT_AVAILABLE means no download with the given GUID
+// could be found, either private or public.
+
+nsresult
+nsDownloadManager::GetDownloadFromDB(const nsACString& aGUID, nsDownload **retVal)
+{
+ MOZ_ASSERT(!FindDownload(aGUID),
+ "If it is a current download, you should not call this method!");
+
+ NS_NAMED_LITERAL_CSTRING(query,
+ "SELECT id, state, startTime, source, target, tempPath, name, referrer, "
+ "entityID, currBytes, maxBytes, mimeType, preferredAction, "
+ "preferredApplication, autoResume, guid "
+ "FROM moz_downloads "
+ "WHERE guid = :guid");
+ // First, let's query the database and see if it even exists
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mDBConn->CreateStatement(query, getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetDownloadFromDB(mDBConn, stmt, retVal);
+
+ // If the download cannot be found in the public database, try again
+ // in the private one. Otherwise, return whatever successful result
+ // or failure obtained from the public database.
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ rv = mPrivateDBConn->CreateStatement(query, getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetDownloadFromDB(mPrivateDBConn, stmt, retVal);
+
+ // Only if it still cannot be found do we report the failure.
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ *retVal = nullptr;
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsDownloadManager::GetDownloadFromDB(uint32_t aID, nsDownload **retVal)
+{
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ MOZ_ASSERT(!FindDownload(aID),
+ "If it is a current download, you should not call this method!");
+
+ // First, let's query the database and see if it even exists
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id, state, startTime, source, target, tempPath, name, referrer, "
+ "entityID, currBytes, maxBytes, mimeType, preferredAction, "
+ "preferredApplication, autoResume, guid "
+ "FROM moz_downloads "
+ "WHERE id = :id"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetDownloadFromDB(mDBConn, stmt, retVal);
+}
+
+nsresult
+nsDownloadManager::GetDownloadFromDB(mozIStorageConnection* aDBConn,
+ mozIStorageStatement* stmt,
+ nsDownload **retVal)
+{
+ bool hasResults = false;
+ nsresult rv = stmt->ExecuteStep(&hasResults);
+ if (NS_FAILED(rv) || !hasResults)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // We have a download, so lets create it
+ RefPtr<nsDownload> dl = new nsDownload();
+ if (!dl)
+ return NS_ERROR_OUT_OF_MEMORY;
+ dl->mPrivate = aDBConn == mPrivateDBConn;
+
+ dl->mDownloadManager = this;
+
+ int32_t i = 0;
+ // Setting all properties of the download now
+ dl->mCancelable = nullptr;
+ dl->mID = stmt->AsInt64(i++);
+ dl->mDownloadState = stmt->AsInt32(i++);
+ dl->mStartTime = stmt->AsInt64(i++);
+
+ nsCString source;
+ stmt->GetUTF8String(i++, source);
+ rv = NS_NewURI(getter_AddRefs(dl->mSource), source);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString target;
+ stmt->GetUTF8String(i++, target);
+ rv = NS_NewURI(getter_AddRefs(dl->mTarget), target);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString tempPath;
+ stmt->GetString(i++, tempPath);
+ if (!tempPath.IsEmpty()) {
+ rv = NS_NewLocalFile(tempPath, true, getter_AddRefs(dl->mTempFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ stmt->GetString(i++, dl->mDisplayName);
+
+ nsCString referrer;
+ rv = stmt->GetUTF8String(i++, referrer);
+ if (NS_SUCCEEDED(rv) && !referrer.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(dl->mReferrer), referrer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = stmt->GetUTF8String(i++, dl->mEntityID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t currBytes = stmt->AsInt64(i++);
+ int64_t maxBytes = stmt->AsInt64(i++);
+ dl->SetProgressBytes(currBytes, maxBytes);
+
+ // Build mMIMEInfo only if the mimeType in DB is not empty
+ nsAutoCString mimeType;
+ rv = stmt->GetUTF8String(i++, mimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mimeType.IsEmpty()) {
+ nsCOMPtr<nsIMIMEService> mimeService =
+ do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
+ getter_AddRefs(dl->mMIMEInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsHandlerInfoAction action = stmt->AsInt32(i++);
+ rv = dl->mMIMEInfo->SetPreferredAction(action);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString persistentDescriptor;
+ rv = stmt->GetUTF8String(i++, persistentDescriptor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!persistentDescriptor.IsEmpty()) {
+ nsCOMPtr<nsILocalHandlerApp> handler =
+ do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> localExecutable;
+ rv = NS_NewNativeLocalFile(EmptyCString(), false,
+ getter_AddRefs(localExecutable));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localExecutable->SetPersistentDescriptor(persistentDescriptor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = handler->SetExecutable(localExecutable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dl->mMIMEInfo->SetPreferredApplicationHandler(handler);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ // Compensate for the i++s skipped in the true block
+ i += 2;
+ }
+
+ dl->mAutoResume =
+ static_cast<enum nsDownload::AutoResume>(stmt->AsInt32(i++));
+
+ rv = stmt->GetUTF8String(i++, dl->mGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Handle situations where we load a download from a database that has been
+ // used in an older version and not gone through the upgrade path (ie. it
+ // contains empty GUID entries).
+ if (dl->mGUID.IsEmpty()) {
+ rv = GenerateGUID(dl->mGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIStorageStatement> updateStmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads SET guid = :guid "
+ "WHERE id = :id"),
+ getter_AddRefs(updateStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updateStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), dl->mGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), dl->mID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updateStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Addrefing and returning
+ dl.forget(retVal);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::AddToCurrentDownloads(nsDownload *aDl)
+{
+ nsCOMArray<nsDownload>& currentDownloads =
+ aDl->mPrivate ? mCurrentPrivateDownloads : mCurrentDownloads;
+ if (!currentDownloads.AppendObject(aDl))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ aDl->mDownloadManager = this;
+ return NS_OK;
+}
+
+void
+nsDownloadManager::SendEvent(nsDownload *aDownload, const char *aTopic)
+{
+ (void)mObserverService->NotifyObservers(aDownload, aTopic, nullptr);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIDownloadManager
+
+NS_IMETHODIMP
+nsDownloadManager::GetActivePrivateDownloadCount(int32_t* aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ *aResult = mCurrentPrivateDownloads.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetActiveDownloadCount(int32_t *aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ *aResult = mCurrentDownloads.Count();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetActiveDownloads(nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return NS_NewArrayEnumerator(aResult, mCurrentDownloads);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetActivePrivateDownloads(nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return NS_NewArrayEnumerator(aResult, mCurrentPrivateDownloads);
+}
+
+/**
+ * For platforms where helper apps use the downloads directory (i.e. mobile),
+ * this should be kept in sync with nsExternalHelperAppService.cpp
+ */
+NS_IMETHODIMP
+nsDownloadManager::GetDefaultDownloadsDirectory(nsIFile **aResult)
+{
+ nsCOMPtr<nsIFile> downloadDir;
+
+ nsresult rv;
+ nsCOMPtr<nsIProperties> dirService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // OSX 10.4:
+ // Desktop
+ // OSX 10.5:
+ // User download directory
+ // Vista:
+ // Downloads
+ // XP/2K:
+ // My Documents/Downloads
+ // Linux:
+ // XDG user dir spec, with a fallback to Home/Downloads
+
+ nsXPIDLString folderName;
+ mBundle->GetStringFromName(u"downloadsFolder",
+ getter_Copies(folderName));
+
+#if defined (XP_MACOSX)
+ rv = dirService->Get(NS_OSX_DEFAULT_DOWNLOAD_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+#elif defined(XP_WIN)
+ rv = dirService->Get(NS_WIN_DEFAULT_DOWNLOAD_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check the os version
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService(NS_SYSTEMINFO_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t version;
+ NS_NAMED_LITERAL_STRING(osVersion, "version");
+ rv = infoService->GetPropertyAsInt32(osVersion, &version);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (version < 6) { // XP/2K
+ // First get "My Documents"
+ rv = dirService->Get(NS_WIN_PERSONAL_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = downloadDir->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This could be the first time we are creating the downloads folder in My
+ // Documents, so make sure it exists.
+ bool exists;
+ rv = downloadDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) {
+ rv = downloadDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+#elif defined(XP_UNIX)
+#if defined(MOZ_WIDGET_ANDROID)
+ // Android doesn't have a $HOME directory, and by default we only have
+ // write access to /data/data/org.mozilla.{$APP} and /sdcard
+ char* downloadDirPath = getenv("DOWNLOADS_DIRECTORY");
+ if (downloadDirPath) {
+ rv = NS_NewNativeLocalFile(nsDependentCString(downloadDirPath),
+ true, getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ rv = NS_ERROR_FAILURE;
+ }
+#else
+ rv = dirService->Get(NS_UNIX_DEFAULT_DOWNLOAD_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ // fallback to Home/Downloads
+ if (NS_FAILED(rv)) {
+ rv = dirService->Get(NS_UNIX_HOME_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = downloadDir->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#endif
+#else
+ rv = dirService->Get(NS_OS_HOME_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = downloadDir->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+
+ downloadDir.forget(aResult);
+
+ return NS_OK;
+}
+
+#define NS_BRANCH_DOWNLOAD "browser.download."
+#define NS_PREF_FOLDERLIST "folderList"
+#define NS_PREF_DIR "dir"
+
+NS_IMETHODIMP
+nsDownloadManager::GetUserDownloadsDirectory(nsIFile **aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProperties> dirService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefService> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefService->GetBranch(NS_BRANCH_DOWNLOAD,
+ getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t val;
+ rv = prefBranch->GetIntPref(NS_PREF_FOLDERLIST,
+ &val);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch(val) {
+ case 0: // Desktop
+ {
+ nsCOMPtr<nsIFile> downloadDir;
+ rv = dirService->Get(NS_OS_DESKTOP_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ downloadDir.forget(aResult);
+ return NS_OK;
+ }
+ break;
+ case 1: // Downloads
+ return GetDefaultDownloadsDirectory(aResult);
+ case 2: // Custom
+ {
+ nsCOMPtr<nsIFile> customDirectory;
+ prefBranch->GetComplexValue(NS_PREF_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(customDirectory));
+ if (customDirectory) {
+ bool exists = false;
+ (void)customDirectory->Exists(&exists);
+
+ if (!exists) {
+ rv = customDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_SUCCEEDED(rv)) {
+ customDirectory.forget(aResult);
+ return NS_OK;
+ }
+
+ // Create failed, so it still doesn't exist. Fall out and get the
+ // default downloads directory.
+ }
+
+ bool writable = false;
+ bool directory = false;
+ (void)customDirectory->IsWritable(&writable);
+ (void)customDirectory->IsDirectory(&directory);
+
+ if (exists && writable && directory) {
+ customDirectory.forget(aResult);
+ return NS_OK;
+ }
+ }
+ rv = GetDefaultDownloadsDirectory(aResult);
+ if (NS_SUCCEEDED(rv)) {
+ (void)prefBranch->SetComplexValue(NS_PREF_DIR,
+ NS_GET_IID(nsIFile),
+ *aResult);
+ }
+ return rv;
+ }
+ break;
+ }
+ return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::AddDownload(DownloadType aDownloadType,
+ nsIURI *aSource,
+ nsIURI *aTarget,
+ const nsAString& aDisplayName,
+ nsIMIMEInfo *aMIMEInfo,
+ PRTime aStartTime,
+ nsIFile *aTempFile,
+ nsICancelable *aCancelable,
+ bool aIsPrivate,
+ nsIDownload **aDownload)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_ENSURE_ARG_POINTER(aSource);
+ NS_ENSURE_ARG_POINTER(aTarget);
+ NS_ENSURE_ARG_POINTER(aDownload);
+
+ nsresult rv;
+
+ // target must be on the local filesystem
+ nsCOMPtr<nsIFileURL> targetFileURL = do_QueryInterface(aTarget, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> targetFile;
+ rv = targetFileURL->GetFile(getter_AddRefs(targetFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsDownload> dl = new nsDownload();
+ if (!dl)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // give our new nsIDownload some info so it's ready to go off into the world
+ dl->mTarget = aTarget;
+ dl->mSource = aSource;
+ dl->mTempFile = aTempFile;
+ dl->mPrivate = aIsPrivate;
+
+ dl->mDisplayName = aDisplayName;
+ if (dl->mDisplayName.IsEmpty())
+ targetFile->GetLeafName(dl->mDisplayName);
+
+ dl->mMIMEInfo = aMIMEInfo;
+ dl->SetStartTime(aStartTime == 0 ? PR_Now() : aStartTime);
+
+ // Creates a cycle that will be broken when the download finishes
+ dl->mCancelable = aCancelable;
+
+ // Adding to the DB
+ nsAutoCString source, target;
+ rv = aSource->GetSpec(source);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aTarget->GetSpec(target);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Track the temp file for exthandler downloads
+ nsAutoString tempPath;
+ if (aTempFile)
+ aTempFile->GetPath(tempPath);
+
+ // Break down MIMEInfo but don't panic if we can't get all the pieces - we
+ // can still download the file
+ nsAutoCString persistentDescriptor, mimeType;
+ nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
+ if (aMIMEInfo) {
+ (void)aMIMEInfo->GetType(mimeType);
+
+ nsCOMPtr<nsIHandlerApp> handlerApp;
+ (void)aMIMEInfo->GetPreferredApplicationHandler(getter_AddRefs(handlerApp));
+ nsCOMPtr<nsILocalHandlerApp> locHandlerApp = do_QueryInterface(handlerApp);
+
+ if (locHandlerApp) {
+ nsCOMPtr<nsIFile> executable;
+ (void)locHandlerApp->GetExecutable(getter_AddRefs(executable));
+ Unused << executable->GetPersistentDescriptor(persistentDescriptor);
+ }
+
+ (void)aMIMEInfo->GetPreferredAction(&action);
+ }
+
+ int64_t id = AddDownloadToDB(dl->mDisplayName, source, target, tempPath,
+ dl->mStartTime, dl->mLastUpdate,
+ mimeType, persistentDescriptor, action,
+ dl->mPrivate, dl->mGUID /* outparam */);
+ NS_ENSURE_TRUE(id, NS_ERROR_FAILURE);
+ dl->mID = id;
+
+ rv = AddToCurrentDownloads(dl);
+ (void)dl->SetState(nsIDownloadManager::DOWNLOAD_QUEUED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef DOWNLOAD_SCANNER
+ if (mScanner) {
+ bool scan = true;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void)prefs->GetBoolPref(PREF_BDM_SCANWHENDONE, &scan);
+ }
+ // We currently apply local security policy to downloads when we scan
+ // via windows all-in-one download security api. The CheckPolicy call
+ // below is a pre-emptive part of that process. So tie applying security
+ // zone policy settings when downloads are intiated to the same pref
+ // that triggers applying security zone policy settings after a download
+ // completes. (bug 504804)
+ if (scan) {
+ AVCheckPolicyState res = mScanner->CheckPolicy(aSource, aTarget);
+ if (res == AVPOLICY_BLOCKED) {
+ // This download will get deleted during a call to IAE's Save,
+ // so go ahead and mark it as blocked and avoid the download.
+ (void)CancelDownload(id);
+ (void)dl->SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY);
+ }
+ }
+ }
+#endif
+
+ // Check with parental controls to see if file downloads
+ // are allowed for this user. If not allowed, cancel the
+ // download and mark its state as being blocked.
+ nsCOMPtr<nsIParentalControlsService> pc =
+ do_CreateInstance(NS_PARENTALCONTROLSSERVICE_CONTRACTID);
+ if (pc) {
+ bool enabled = false;
+ (void)pc->GetBlockFileDownloadsEnabled(&enabled);
+ if (enabled) {
+ (void)CancelDownload(id);
+ (void)dl->SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL);
+ }
+
+ // Log the event if required by pc settings.
+ bool logEnabled = false;
+ (void)pc->GetLoggingEnabled(&logEnabled);
+ if (logEnabled) {
+ (void)pc->Log(nsIParentalControlsService::ePCLog_FileDownload,
+ enabled,
+ aSource,
+ nullptr);
+ }
+ }
+
+ dl.forget(aDownload);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetDownload(uint32_t aID, nsIDownload **aDownloadItem)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ nsDownload *itm = FindDownload(aID);
+
+ RefPtr<nsDownload> dl;
+ if (!itm) {
+ nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ itm = dl.get();
+ }
+
+ NS_ADDREF(*aDownloadItem = itm);
+
+ return NS_OK;
+}
+
+namespace {
+class AsyncResult : public Runnable
+{
+public:
+ AsyncResult(nsresult aStatus, nsIDownload* aResult,
+ nsIDownloadManagerResult* aCallback)
+ : mStatus(aStatus), mResult(aResult), mCallback(aCallback)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mCallback->HandleResult(mStatus, mResult);
+ return NS_OK;
+ }
+
+private:
+ nsresult mStatus;
+ nsCOMPtr<nsIDownload> mResult;
+ nsCOMPtr<nsIDownloadManagerResult> mCallback;
+};
+} // namespace
+
+NS_IMETHODIMP
+nsDownloadManager::GetDownloadByGUID(const nsACString& aGUID,
+ nsIDownloadManagerResult* aCallback)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ nsDownload *itm = FindDownload(aGUID);
+
+ nsresult rv = NS_OK;
+ RefPtr<nsDownload> dl;
+ if (!itm) {
+ rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
+ itm = dl.get();
+ }
+
+ RefPtr<AsyncResult> runnable = new AsyncResult(rv, itm, aCallback);
+ NS_DispatchToMainThread(runnable);
+ return NS_OK;
+}
+
+nsDownload *
+nsDownloadManager::FindDownload(uint32_t aID)
+{
+ // we shouldn't ever have many downloads, so we can loop over them
+ for (int32_t i = mCurrentDownloads.Count() - 1; i >= 0; --i) {
+ nsDownload *dl = mCurrentDownloads[i];
+ if (dl->mID == aID)
+ return dl;
+ }
+
+ return nullptr;
+}
+
+nsDownload *
+nsDownloadManager::FindDownload(const nsACString& aGUID)
+{
+ // we shouldn't ever have many downloads, so we can loop over them
+ for (int32_t i = mCurrentDownloads.Count() - 1; i >= 0; --i) {
+ nsDownload *dl = mCurrentDownloads[i];
+ if (dl->mGUID == aGUID)
+ return dl;
+ }
+
+ for (int32_t i = mCurrentPrivateDownloads.Count() - 1; i >= 0; --i) {
+ nsDownload *dl = mCurrentPrivateDownloads[i];
+ if (dl->mGUID == aGUID)
+ return dl;
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::CancelDownload(uint32_t aID)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ // We AddRef here so we don't lose access to member variables when we remove
+ RefPtr<nsDownload> dl = FindDownload(aID);
+
+ // if it's null, someone passed us a bad id.
+ if (!dl)
+ return NS_ERROR_FAILURE;
+
+ return dl->Cancel();
+}
+
+nsresult
+nsDownloadManager::RetryDownload(const nsACString& aGUID)
+{
+ RefPtr<nsDownload> dl;
+ nsresult rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RetryDownload(dl);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::RetryDownload(uint32_t aID)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ RefPtr<nsDownload> dl;
+ nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RetryDownload(dl);
+}
+
+nsresult
+nsDownloadManager::RetryDownload(nsDownload* dl)
+{
+ // if our download is not canceled or failed, we should fail
+ if (dl->mDownloadState != nsIDownloadManager::DOWNLOAD_FAILED &&
+ dl->mDownloadState != nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL &&
+ dl->mDownloadState != nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY &&
+ dl->mDownloadState != nsIDownloadManager::DOWNLOAD_DIRTY &&
+ dl->mDownloadState != nsIDownloadManager::DOWNLOAD_CANCELED)
+ return NS_ERROR_FAILURE;
+
+ // If the download has failed and is resumable then we first try resuming it
+ nsresult rv;
+ if (dl->mDownloadState == nsIDownloadManager::DOWNLOAD_FAILED && dl->IsResumable()) {
+ rv = dl->Resume();
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+
+ // reset time and download progress
+ dl->SetStartTime(PR_Now());
+ dl->SetProgressBytes(0, -1);
+
+ nsCOMPtr<nsIWebBrowserPersist> wbp =
+ do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = wbp->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddToCurrentDownloads(dl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dl->SetState(nsIDownloadManager::DOWNLOAD_QUEUED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Creates a cycle that will be broken when the download finishes
+ dl->mCancelable = wbp;
+ (void)wbp->SetProgressListener(dl);
+
+ // referrer policy can be anything since referrer is nullptr
+ rv = wbp->SavePrivacyAwareURI(dl->mSource, nullptr,
+ nullptr, mozilla::net::RP_Unset,
+ nullptr, nullptr,
+ dl->mTarget, dl->mPrivate);
+ if (NS_FAILED(rv)) {
+ dl->mCancelable = nullptr;
+ (void)wbp->SetProgressListener(nullptr);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+RemoveDownloadByGUID(const nsACString& aGUID, mozIStorageConnection* aDBConn)
+{
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_downloads "
+ "WHERE guid = :guid"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::RemoveDownload(const nsACString& aGUID)
+{
+ RefPtr<nsDownload> dl = FindDownload(aGUID);
+ MOZ_ASSERT(!dl, "Can't call RemoveDownload on a download in progress!");
+ if (dl)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dl->mPrivate) {
+ RemoveDownloadByGUID(aGUID, mPrivateDBConn);
+ } else {
+ RemoveDownloadByGUID(aGUID, mDBConn);
+ }
+
+ return NotifyDownloadRemoval(dl);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::RemoveDownload(uint32_t aID)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ RefPtr<nsDownload> dl = FindDownload(aID);
+ MOZ_ASSERT(!dl, "Can't call RemoveDownload on a download in progress!");
+ if (dl)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_downloads "
+ "WHERE id = :id"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aID); // unsigned; 64-bit to prevent overflow
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Notify the UI with the topic and download id
+ return NotifyDownloadRemoval(dl);
+}
+
+nsresult
+nsDownloadManager::NotifyDownloadRemoval(nsDownload* aRemoved)
+{
+ nsCOMPtr<nsISupportsPRUint32> id;
+ nsCOMPtr<nsISupportsCString> guid;
+ nsresult rv;
+
+ // Only send an integer ID notification if the download is public.
+ bool sendDeprecatedNotification = !(aRemoved && aRemoved->mPrivate);
+
+ if (sendDeprecatedNotification && aRemoved) {
+ id = do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t dlID;
+ rv = aRemoved->GetId(&dlID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = id->SetData(dlID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (sendDeprecatedNotification) {
+ mObserverService->NotifyObservers(id,
+ "download-manager-remove-download",
+ nullptr);
+ }
+
+ if (aRemoved) {
+ guid = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString guidStr;
+ rv = aRemoved->GetGuid(guidStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = guid->SetData(guidStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mObserverService->NotifyObservers(guid,
+ "download-manager-remove-download-guid",
+ nullptr);
+ return NS_OK;
+}
+
+static nsresult
+DoRemoveDownloadsByTimeframe(mozIStorageConnection* aDBConn,
+ int64_t aStartTime,
+ int64_t aEndTime)
+{
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_downloads "
+ "WHERE startTime >= :startTime "
+ "AND startTime <= :endTime "
+ "AND state NOT IN (:downloading, :paused, :queued)"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bind the times
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), aStartTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), aEndTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bind the active states
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("downloading"), nsIDownloadManager::DOWNLOAD_DOWNLOADING);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("paused"), nsIDownloadManager::DOWNLOAD_PAUSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("queued"), nsIDownloadManager::DOWNLOAD_QUEUED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Execute
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::RemoveDownloadsByTimeframe(int64_t aStartTime,
+ int64_t aEndTime)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ nsresult rv = DoRemoveDownloadsByTimeframe(mDBConn, aStartTime, aEndTime);
+ nsresult rv2 = DoRemoveDownloadsByTimeframe(mPrivateDBConn, aStartTime, aEndTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+
+ // Notify the UI with the topic and null subject to indicate "remove multiple"
+ return NotifyDownloadRemoval(nullptr);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::CleanUp()
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return CleanUp(mDBConn);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::CleanUpPrivate()
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return CleanUp(mPrivateDBConn);
+}
+
+nsresult
+nsDownloadManager::CleanUp(mozIStorageConnection* aDBConn)
+{
+ DownloadState states[] = { nsIDownloadManager::DOWNLOAD_FINISHED,
+ nsIDownloadManager::DOWNLOAD_FAILED,
+ nsIDownloadManager::DOWNLOAD_CANCELED,
+ nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL,
+ nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY,
+ nsIDownloadManager::DOWNLOAD_DIRTY };
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_downloads "
+ "WHERE state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ?"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < ArrayLength(states); ++i) {
+ rv = stmt->BindInt32ByIndex(i, states[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Notify the UI with the topic and null subject to indicate "remove multiple"
+ return NotifyDownloadRemoval(nullptr);
+}
+
+static nsresult
+DoGetCanCleanUp(mozIStorageConnection* aDBConn, bool *aResult)
+{
+ // This method should never return anything but NS_OK for the benefit of
+ // unwitting consumers.
+
+ *aResult = false;
+
+ DownloadState states[] = { nsIDownloadManager::DOWNLOAD_FINISHED,
+ nsIDownloadManager::DOWNLOAD_FAILED,
+ nsIDownloadManager::DOWNLOAD_CANCELED,
+ nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL,
+ nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY,
+ nsIDownloadManager::DOWNLOAD_DIRTY };
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT COUNT(*) "
+ "FROM moz_downloads "
+ "WHERE state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ?"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ for (uint32_t i = 0; i < ArrayLength(states); ++i) {
+ rv = stmt->BindInt32ByIndex(i, states[i]);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ }
+
+ bool moreResults; // We don't really care...
+ rv = stmt->ExecuteStep(&moreResults);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ int32_t count;
+ rv = stmt->GetInt32(0, &count);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ if (count > 0)
+ *aResult = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetCanCleanUp(bool *aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return DoGetCanCleanUp(mDBConn, aResult);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetCanCleanUpPrivate(bool *aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return DoGetCanCleanUp(mPrivateDBConn, aResult);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::PauseDownload(uint32_t aID)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ nsDownload *dl = FindDownload(aID);
+ if (!dl)
+ return NS_ERROR_FAILURE;
+
+ return dl->Pause();
+}
+
+NS_IMETHODIMP
+nsDownloadManager::ResumeDownload(uint32_t aID)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ nsDownload *dl = FindDownload(aID);
+ if (!dl)
+ return NS_ERROR_FAILURE;
+
+ return dl->Resume();
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetDBConnection(mozIStorageConnection **aDBConn)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_ADDREF(*aDBConn = mDBConn);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetPrivateDBConnection(mozIStorageConnection **aDBConn)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_ADDREF(*aDBConn = mPrivateDBConn);
+
+ return NS_OK;
+ }
+
+NS_IMETHODIMP
+nsDownloadManager::AddListener(nsIDownloadProgressListener *aListener)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ mListeners.AppendObject(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::AddPrivacyAwareListener(nsIDownloadProgressListener *aListener)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ mPrivacyAwareListeners.AppendObject(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::RemoveListener(nsIDownloadProgressListener *aListener)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ mListeners.RemoveObject(aListener);
+ mPrivacyAwareListeners.RemoveObject(aListener);
+ return NS_OK;
+}
+
+void
+nsDownloadManager::NotifyListenersOnDownloadStateChange(int16_t aOldState,
+ nsDownload *aDownload)
+{
+ for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
+ mPrivacyAwareListeners[i]->OnDownloadStateChange(aOldState, aDownload);
+ }
+
+ // Only privacy-aware listeners should receive notifications about private
+ // downloads, while non-privacy-aware listeners receive no sign they exist.
+ if (aDownload->mPrivate) {
+ return;
+ }
+
+ for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
+ mListeners[i]->OnDownloadStateChange(aOldState, aDownload);
+ }
+}
+
+void
+nsDownloadManager::NotifyListenersOnProgressChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ int64_t aCurSelfProgress,
+ int64_t aMaxSelfProgress,
+ int64_t aCurTotalProgress,
+ int64_t aMaxTotalProgress,
+ nsDownload *aDownload)
+{
+ for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
+ mPrivacyAwareListeners[i]->OnProgressChange(aProgress, aRequest, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress, aDownload);
+ }
+
+ // Only privacy-aware listeners should receive notifications about private
+ // downloads, while non-privacy-aware listeners receive no sign they exist.
+ if (aDownload->mPrivate) {
+ return;
+ }
+
+ for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
+ mListeners[i]->OnProgressChange(aProgress, aRequest, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress, aDownload);
+ }
+}
+
+void
+nsDownloadManager::NotifyListenersOnStateChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus,
+ nsDownload *aDownload)
+{
+ for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
+ mPrivacyAwareListeners[i]->OnStateChange(aProgress, aRequest, aStateFlags, aStatus,
+ aDownload);
+ }
+
+ // Only privacy-aware listeners should receive notifications about private
+ // downloads, while non-privacy-aware listeners receive no sign they exist.
+ if (aDownload->mPrivate) {
+ return;
+ }
+
+ for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
+ mListeners[i]->OnStateChange(aProgress, aRequest, aStateFlags, aStatus,
+ aDownload);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsINavHistoryObserver
+
+NS_IMETHODIMP
+nsDownloadManager::OnBeginUpdateBatch()
+{
+ // This method in not normally invoked when mUseJSTransfer is enabled, however
+ // we provide an extra check in case it is called manually by add-ons.
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ // We already have a transaction, so don't make another
+ if (mHistoryTransaction)
+ return NS_OK;
+
+ // Start a transaction that commits when deleted
+ mHistoryTransaction = new mozStorageTransaction(mDBConn, true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnEndUpdateBatch()
+{
+ // Get rid of the transaction and cause it to commit
+ mHistoryTransaction = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnVisit(nsIURI *aURI, int64_t aVisitID, PRTime aTime,
+ int64_t aSessionID, int64_t aReferringID,
+ uint32_t aTransitionType, const nsACString& aGUID,
+ bool aHidden, uint32_t aVisitCount, uint32_t aTyped)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnTitleChanged(nsIURI *aURI,
+ const nsAString &aPageTitle,
+ const nsACString &aGUID)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnFrecencyChanged(nsIURI* aURI,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnManyFrecenciesChanged()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnDeleteURI(nsIURI *aURI,
+ const nsACString& aGUID,
+ uint16_t aReason)
+{
+ // This method in not normally invoked when mUseJSTransfer is enabled, however
+ // we provide an extra check in case it is called manually by add-ons.
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ nsresult rv = RemoveDownloadsForURI(mGetIdsForURIStatement, aURI);
+ nsresult rv2 = RemoveDownloadsForURI(mGetPrivateIdsForURIStatement, aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnClearHistory()
+{
+ return CleanUp();
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnPageChanged(nsIURI *aURI,
+ uint32_t aChangedAttribute,
+ const nsAString& aNewValue,
+ const nsACString &aGUID)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnDeleteVisits(nsIURI *aURI, PRTime aVisitTime,
+ const nsACString& aGUID,
+ uint16_t aReason, uint32_t aTransitionType)
+{
+ // Don't bother removing downloads until the page is removed.
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+nsDownloadManager::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ // This method in not normally invoked when mUseJSTransfer is enabled, however
+ // we provide an extra check in case it is called manually by add-ons.
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ // We need to count the active public downloads that could be lost
+ // by quitting, and add any active private ones as well, since per-window
+ // private browsing may be active.
+ int32_t currDownloadCount = mCurrentDownloads.Count();
+
+ // If we don't need to cancel all the downloads on quit, only count the ones
+ // that aren't resumable.
+ if (GetQuitBehavior() != QUIT_AND_CANCEL) {
+ for (int32_t i = currDownloadCount - 1; i >= 0; --i) {
+ if (mCurrentDownloads[i]->IsResumable()) {
+ currDownloadCount--;
+ }
+ }
+
+ // We have a count of the public, non-resumable downloads. Now we need
+ // to add the total number of private downloads, since they are in danger
+ // of being lost.
+ currDownloadCount += mCurrentPrivateDownloads.Count();
+ }
+
+ nsresult rv;
+ if (strcmp(aTopic, "oncancel") == 0) {
+ nsCOMPtr<nsIDownload> dl = do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dl->Cancel();
+ } else if (strcmp(aTopic, "profile-before-change") == 0) {
+ CloseAllDBs();
+ } else if (strcmp(aTopic, "quit-application") == 0) {
+ // Try to pause all downloads and, if appropriate, mark them as auto-resume
+ // unless user has specified that downloads should be canceled
+ enum QuitBehavior behavior = GetQuitBehavior();
+ if (behavior != QUIT_AND_CANCEL)
+ (void)PauseAllDownloads(bool(behavior != QUIT_AND_PAUSE));
+
+ // Remove downloads to break cycles and cancel downloads
+ (void)RemoveAllDownloads();
+
+ // Now that active downloads have been canceled, remove all completed or
+ // aborted downloads if the user's retention policy specifies it.
+ if (GetRetentionBehavior() == 1)
+ CleanUp();
+ } else if (strcmp(aTopic, "quit-application-requested") == 0 &&
+ currDownloadCount) {
+ nsCOMPtr<nsISupportsPRBool> cancelDownloads =
+ do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+#ifndef XP_MACOSX
+ ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
+ u"quitCancelDownloadsAlertTitle",
+ u"quitCancelDownloadsAlertMsgMultiple",
+ u"quitCancelDownloadsAlertMsg",
+ u"dontQuitButtonWin");
+#else
+ ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
+ u"quitCancelDownloadsAlertTitle",
+ u"quitCancelDownloadsAlertMsgMacMultiple",
+ u"quitCancelDownloadsAlertMsgMac",
+ u"dontQuitButtonMac");
+#endif
+ } else if (strcmp(aTopic, "offline-requested") == 0 && currDownloadCount) {
+ nsCOMPtr<nsISupportsPRBool> cancelDownloads =
+ do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
+ u"offlineCancelDownloadsAlertTitle",
+ u"offlineCancelDownloadsAlertMsgMultiple",
+ u"offlineCancelDownloadsAlertMsg",
+ u"dontGoOfflineButton");
+ }
+ else if (strcmp(aTopic, NS_IOSERVICE_GOING_OFFLINE_TOPIC) == 0) {
+ // Pause all downloads, and mark them to auto-resume.
+ (void)PauseAllDownloads(true);
+ }
+ else if (strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) == 0 &&
+ nsDependentString(aData).EqualsLiteral(NS_IOSERVICE_ONLINE)) {
+ // We can now resume all downloads that are supposed to auto-resume.
+ (void)ResumeAllDownloads(false);
+ }
+ else if (strcmp(aTopic, "alertclickcallback") == 0) {
+ nsCOMPtr<nsIDownloadManagerUI> dmui =
+ do_GetService("@mozilla.org/download-manager-ui;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return dmui->Show(nullptr, nullptr, nsIDownloadManagerUI::REASON_USER_INTERACTED,
+ aData && NS_strcmp(aData, u"private") == 0);
+ } else if (strcmp(aTopic, "sleep_notification") == 0 ||
+ strcmp(aTopic, "suspend_process_notification") == 0) {
+ // Pause downloads if we're sleeping, and mark the downloads as auto-resume
+ (void)PauseAllDownloads(true);
+ } else if (strcmp(aTopic, "wake_notification") == 0 ||
+ strcmp(aTopic, "resume_process_notification") == 0) {
+ int32_t resumeOnWakeDelay = 10000;
+ nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (pref)
+ (void)pref->GetIntPref(PREF_BDM_RESUMEONWAKEDELAY, &resumeOnWakeDelay);
+
+ // Wait a little bit before trying to resume to avoid resuming when network
+ // connections haven't restarted yet
+ mResumeOnWakeTimer = new mozilla::timer;
+ if (resumeOnWakeDelay >= 0 && mResumeOnWakeTimer) {
+ (void)mResumeOnWakeTimer->InitWithFuncCallback(ResumeOnWakeCallback,
+ this, resumeOnWakeDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+ } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
+ // Upon leaving private browsing mode, cancel all private downloads,
+ // remove all trace of them, and then blow away the private database
+ // and recreate a blank one.
+ RemoveAllDownloads(mCurrentPrivateDownloads);
+ InitPrivateDB();
+ } else if (strcmp(aTopic, "last-pb-context-exiting") == 0) {
+ // If there are active private downloads, prompt the user to confirm leaving
+ // private browsing mode (thereby cancelling them). Otherwise, silently proceed.
+ if (!mCurrentPrivateDownloads.Count())
+ return NS_OK;
+
+ nsCOMPtr<nsISupportsPRBool> cancelDownloads = do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ConfirmCancelDownloads(mCurrentPrivateDownloads.Count(), cancelDownloads,
+ u"leavePrivateBrowsingCancelDownloadsAlertTitle",
+ u"leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2",
+ u"leavePrivateBrowsingWindowsCancelDownloadsAlertMsg2",
+ u"dontLeavePrivateBrowsingButton2");
+ }
+
+ return NS_OK;
+}
+
+void
+nsDownloadManager::ConfirmCancelDownloads(int32_t aCount,
+ nsISupportsPRBool *aCancelDownloads,
+ const char16_t *aTitle,
+ const char16_t *aCancelMessageMultiple,
+ const char16_t *aCancelMessageSingle,
+ const char16_t *aDontCancelButton)
+{
+ // If user has already dismissed quit request, then do nothing
+ bool quitRequestCancelled = false;
+ aCancelDownloads->GetData(&quitRequestCancelled);
+ if (quitRequestCancelled)
+ return;
+
+ nsXPIDLString title, message, quitButton, dontQuitButton;
+
+ mBundle->GetStringFromName(aTitle, getter_Copies(title));
+
+ nsAutoString countString;
+ countString.AppendInt(aCount);
+ const char16_t *strings[1] = { countString.get() };
+ if (aCount > 1) {
+ mBundle->FormatStringFromName(aCancelMessageMultiple, strings, 1,
+ getter_Copies(message));
+ mBundle->FormatStringFromName(u"cancelDownloadsOKTextMultiple",
+ strings, 1, getter_Copies(quitButton));
+ } else {
+ mBundle->GetStringFromName(aCancelMessageSingle, getter_Copies(message));
+ mBundle->GetStringFromName(u"cancelDownloadsOKText",
+ getter_Copies(quitButton));
+ }
+
+ mBundle->GetStringFromName(aDontCancelButton, getter_Copies(dontQuitButton));
+
+ // Get Download Manager window, to be parent of alert.
+ nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+ nsCOMPtr<mozIDOMWindowProxy> dmWindow;
+ if (wm) {
+ wm->GetMostRecentWindow(u"Download:Manager",
+ getter_AddRefs(dmWindow));
+ }
+
+ // Show alert.
+ nsCOMPtr<nsIPromptService> prompter(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
+ if (prompter) {
+ int32_t flags = (nsIPromptService::BUTTON_TITLE_IS_STRING * nsIPromptService::BUTTON_POS_0) + (nsIPromptService::BUTTON_TITLE_IS_STRING * nsIPromptService::BUTTON_POS_1);
+ bool nothing = false;
+ int32_t button;
+ prompter->ConfirmEx(dmWindow, title, message, flags, quitButton.get(), dontQuitButton.get(), nullptr, nullptr, ¬hing, &button);
+
+ aCancelDownloads->SetData(button == 1);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsDownload
+
+NS_IMPL_CLASSINFO(nsDownload, nullptr, 0, NS_DOWNLOAD_CID)
+NS_IMPL_ISUPPORTS_CI(
+ nsDownload
+ , nsIDownload
+ , nsITransfer
+ , nsIWebProgressListener
+ , nsIWebProgressListener2
+)
+
+nsDownload::nsDownload() : mDownloadState(nsIDownloadManager::DOWNLOAD_NOTSTARTED),
+ mID(0),
+ mPercentComplete(0),
+ mCurrBytes(0),
+ mMaxBytes(-1),
+ mStartTime(0),
+ mLastUpdate(PR_Now() - (uint32_t)gUpdateInterval),
+ mResumedAt(-1),
+ mSpeed(0),
+ mHasMultipleFiles(false),
+ mPrivate(false),
+ mAutoResume(DONT_RESUME)
+{
+}
+
+nsDownload::~nsDownload()
+{
+}
+
+NS_IMETHODIMP nsDownload::SetSha256Hash(const nsACString& aHash) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must call SetSha256Hash on main thread");
+ // This will be used later to query the application reputation service.
+ mHash = aHash;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDownload::SetSignatureInfo(nsIArray* aSignatureInfo) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must call SetSignatureInfo on main thread");
+ // This will be used later to query the application reputation service.
+ mSignatureInfo = aSignatureInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDownload::SetRedirects(nsIArray* aRedirects) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must call SetRedirects on main thread");
+ // This will be used later to query the application reputation service.
+ mRedirects = aRedirects;
+ return NS_OK;
+}
+
+#ifdef MOZ_ENABLE_GIO
+static void gio_set_metadata_done(GObject *source_obj, GAsyncResult *res, gpointer user_data)
+{
+ GError *err = nullptr;
+ g_file_set_attributes_finish(G_FILE(source_obj), res, nullptr, &err);
+ if (err) {
+#ifdef DEBUG
+ NS_DebugBreak(NS_DEBUG_WARNING, "Set file metadata failed: ", err->message, __FILE__, __LINE__);
+#endif
+ g_error_free(err);
+ }
+}
+#endif
+
+nsresult
+nsDownload::SetState(DownloadState aState)
+{
+ NS_ASSERTION(mDownloadState != aState,
+ "Trying to set the download state to what it already is set to!");
+
+ int16_t oldState = mDownloadState;
+ mDownloadState = aState;
+
+ // We don't want to lose access to our member variables
+ RefPtr<nsDownload> kungFuDeathGrip = this;
+
+ // When the state changed listener is dispatched, queries to the database and
+ // the download manager api should reflect what the nsIDownload object would
+ // return. So, if a download is done (finished, canceled, etc.), it should
+ // first be removed from the current downloads. We will also have to update
+ // the database *before* notifying listeners. At this point, you can safely
+ // dispatch to the observers as well.
+ switch (aState) {
+ case nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL:
+ case nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY:
+ case nsIDownloadManager::DOWNLOAD_DIRTY:
+ case nsIDownloadManager::DOWNLOAD_CANCELED:
+ case nsIDownloadManager::DOWNLOAD_FAILED:
+#ifdef ANDROID
+ // If we still have a temp file, remove it
+ bool tempExists;
+ if (mTempFile && NS_SUCCEEDED(mTempFile->Exists(&tempExists)) && tempExists) {
+ nsresult rv = mTempFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#endif
+
+ // Transfers are finished, so break the reference cycle
+ Finalize();
+ break;
+#ifdef DOWNLOAD_SCANNER
+ case nsIDownloadManager::DOWNLOAD_SCANNING:
+ {
+ nsresult rv = mDownloadManager->mScanner ? mDownloadManager->mScanner->ScanDownload(this) : NS_ERROR_NOT_INITIALIZED;
+ // If we failed, then fall through to 'download finished'
+ if (NS_SUCCEEDED(rv))
+ break;
+ mDownloadState = aState = nsIDownloadManager::DOWNLOAD_FINISHED;
+ }
+#endif
+ case nsIDownloadManager::DOWNLOAD_FINISHED:
+ {
+ nsresult rv = ExecuteDesiredAction();
+ if (NS_FAILED(rv)) {
+ // We've failed to execute the desired action. As a result, we should
+ // fail the download so the user can try again.
+ (void)FailDownload(rv, nullptr);
+ return rv;
+ }
+
+ // Now that we're done with handling the download, clean it up
+ Finalize();
+
+ nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ // Master pref to control this function.
+ bool showTaskbarAlert = true;
+ if (pref)
+ pref->GetBoolPref(PREF_BDM_SHOWALERTONCOMPLETE, &showTaskbarAlert);
+
+ if (showTaskbarAlert) {
+ int32_t alertInterval = 2000;
+ if (pref)
+ pref->GetIntPref(PREF_BDM_SHOWALERTINTERVAL, &alertInterval);
+
+ int64_t alertIntervalUSec = alertInterval * PR_USEC_PER_MSEC;
+ int64_t goat = PR_Now() - mStartTime;
+ showTaskbarAlert = goat > alertIntervalUSec;
+
+ int32_t size = mPrivate ?
+ mDownloadManager->mCurrentPrivateDownloads.Count() :
+ mDownloadManager->mCurrentDownloads.Count();
+ if (showTaskbarAlert && size == 0) {
+ nsCOMPtr<nsIAlertsService> alerts =
+ do_GetService("@mozilla.org/alerts-service;1");
+ if (alerts) {
+ nsXPIDLString title, message;
+
+ mDownloadManager->mBundle->GetStringFromName(
+ u"downloadsCompleteTitle",
+ getter_Copies(title));
+ mDownloadManager->mBundle->GetStringFromName(
+ u"downloadsCompleteMsg",
+ getter_Copies(message));
+
+ bool removeWhenDone =
+ mDownloadManager->GetRetentionBehavior() == 0;
+
+ // If downloads are automatically removed per the user's
+ // retention policy, there's no reason to make the text clickable
+ // because if it is, they'll click open the download manager and
+ // the items they downloaded will have been removed.
+ alerts->ShowAlertNotification(
+ NS_LITERAL_STRING(DOWNLOAD_MANAGER_ALERT_ICON), title,
+ message, !removeWhenDone,
+ mPrivate ? NS_LITERAL_STRING("private") : NS_LITERAL_STRING("non-private"),
+ mDownloadManager, EmptyString(), NS_LITERAL_STRING("auto"),
+ EmptyString(), EmptyString(), nullptr, mPrivate,
+ false /* requireInteraction */);
+ }
+ }
+ }
+
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK)
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mTarget);
+ nsCOMPtr<nsIFile> file;
+ nsAutoString path;
+
+ if (fileURL &&
+ NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
+ file &&
+ NS_SUCCEEDED(file->GetPath(path))) {
+
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_ANDROID)
+ // On Windows and Gtk, add the download to the system's "recent documents"
+ // list, with a pref to disable.
+ {
+ bool addToRecentDocs = true;
+ if (pref)
+ pref->GetBoolPref(PREF_BDM_ADDTORECENTDOCS, &addToRecentDocs);
+#ifdef MOZ_WIDGET_ANDROID
+ if (addToRecentDocs) {
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ nsAutoCString contentType;
+ GetMIMEInfo(getter_AddRefs(mimeInfo));
+
+ if (mimeInfo)
+ mimeInfo->GetMIMEType(contentType);
+
+ if (jni::IsFennec()) {
+ java::DownloadsIntegration::ScanMedia(path, NS_ConvertUTF8toUTF16(contentType));
+ }
+ }
+#else
+ if (addToRecentDocs && !mPrivate) {
+#ifdef XP_WIN
+ ::SHAddToRecentDocs(SHARD_PATHW, path.get());
+#elif defined(MOZ_WIDGET_GTK)
+ GtkRecentManager* manager = gtk_recent_manager_get_default();
+
+ gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),
+ nullptr, nullptr);
+ if (uri) {
+ gtk_recent_manager_add_item(manager, uri);
+ g_free(uri);
+ }
+#endif
+ }
+#endif
+#ifdef MOZ_ENABLE_GIO
+ // Use GIO to store the source URI for later display in the file manager.
+ GFile* gio_file = g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());
+ nsCString source_uri;
+ rv = mSource->GetSpec(source_uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ GFileInfo *file_info = g_file_info_new();
+ g_file_info_set_attribute_string(file_info, "metadata::download-uri", source_uri.get());
+ g_file_set_attributes_async(gio_file,
+ file_info,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ nullptr, gio_set_metadata_done, nullptr);
+ g_object_unref(file_info);
+ g_object_unref(gio_file);
+#endif
+ }
+#endif
+
+#ifdef XP_MACOSX
+ // On OS X, make the downloads stack bounce.
+ CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
+ NS_ConvertUTF16toUTF8(path).get(),
+ kCFStringEncodingUTF8);
+ CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
+ ::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
+ observedObject, nullptr, TRUE);
+ ::CFRelease(observedObject);
+#endif
+ }
+
+#ifdef XP_WIN
+ // Adjust file attributes so that by default, new files are indexed
+ // by desktop search services. Skip off those that land in the temp
+ // folder.
+ nsCOMPtr<nsIFile> tempDir, fileDir;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ (void)file->GetParent(getter_AddRefs(fileDir));
+
+ bool isTemp = false;
+ if (fileDir)
+ (void)fileDir->Equals(tempDir, &isTemp);
+
+ nsCOMPtr<nsILocalFileWin> localFileWin(do_QueryInterface(file));
+ if (!isTemp && localFileWin)
+ (void)localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED);
+#endif
+
+#endif
+ // Now remove the download if the user's retention policy is "Remove when Done"
+ if (mDownloadManager->GetRetentionBehavior() == 0)
+ mDownloadManager->RemoveDownload(mGUID);
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Before notifying the listener, we must update the database so that calls
+ // to it work out properly.
+ nsresult rv = UpdateDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDownloadManager->NotifyListenersOnDownloadStateChange(oldState, this);
+
+ switch (mDownloadState) {
+ case nsIDownloadManager::DOWNLOAD_DOWNLOADING:
+ // Only send the dl-start event to downloads that are actually starting.
+ if (oldState == nsIDownloadManager::DOWNLOAD_QUEUED) {
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-start");
+ }
+ break;
+ case nsIDownloadManager::DOWNLOAD_FAILED:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-failed");
+ break;
+ case nsIDownloadManager::DOWNLOAD_SCANNING:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-scanning");
+ break;
+ case nsIDownloadManager::DOWNLOAD_FINISHED:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-done");
+ break;
+ case nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL:
+ case nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-blocked");
+ break;
+ case nsIDownloadManager::DOWNLOAD_DIRTY:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-dirty");
+ break;
+ case nsIDownloadManager::DOWNLOAD_CANCELED:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-cancel");
+ break;
+ default:
+ break;
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIWebProgressListener2
+
+NS_IMETHODIMP
+nsDownload::OnProgressChange64(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int64_t aCurSelfProgress,
+ int64_t aMaxSelfProgress,
+ int64_t aCurTotalProgress,
+ int64_t aMaxTotalProgress)
+{
+ if (!mRequest)
+ mRequest = aRequest; // used for pause/resume
+
+ if (mDownloadState == nsIDownloadManager::DOWNLOAD_QUEUED) {
+ // Obtain the referrer
+ nsresult rv;
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ nsCOMPtr<nsIURI> referrer = mReferrer;
+ if (channel)
+ (void)NS_GetReferrerFromChannel(channel, getter_AddRefs(mReferrer));
+
+ // Restore the original referrer if the new one isn't useful
+ if (!mReferrer)
+ mReferrer = referrer;
+
+ // If we have a MIME info, we know that exthandler has already added this to
+ // the history, but if we do not, we'll have to add it ourselves.
+ if (!mMIMEInfo && !mPrivate) {
+ nsCOMPtr<nsIDownloadHistory> dh =
+ do_GetService(NS_DOWNLOADHISTORY_CONTRACTID);
+ if (dh)
+ (void)dh->AddDownload(mSource, mReferrer, mStartTime, mTarget);
+ }
+
+ // Fetch the entityID, but if we can't get it, don't panic (non-resumable)
+ nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(aRequest));
+ if (resumableChannel)
+ (void)resumableChannel->GetEntityID(mEntityID);
+
+ // Before we update the state and dispatch state notifications, we want to
+ // ensure that we have the correct state for this download with regards to
+ // its percent completion and size.
+ SetProgressBytes(0, aMaxTotalProgress);
+
+ // Update the state and the database
+ rv = SetState(nsIDownloadManager::DOWNLOAD_DOWNLOADING);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // filter notifications since they come in so frequently
+ PRTime now = PR_Now();
+ PRIntervalTime delta = now - mLastUpdate;
+ if (delta < gUpdateInterval)
+ return NS_OK;
+
+ mLastUpdate = now;
+
+ // Calculate the speed using the elapsed delta time and bytes downloaded
+ // during that time for more accuracy.
+ double elapsedSecs = double(delta) / PR_USEC_PER_SEC;
+ if (elapsedSecs > 0) {
+ double speed = double(aCurTotalProgress - mCurrBytes) / elapsedSecs;
+ if (mCurrBytes == 0) {
+ mSpeed = speed;
+ } else {
+ // Calculate 'smoothed average' of 10 readings.
+ mSpeed = mSpeed * 0.9 + speed * 0.1;
+ }
+ }
+
+ SetProgressBytes(aCurTotalProgress, aMaxTotalProgress);
+
+ // Report to the listener our real sizes
+ int64_t currBytes, maxBytes;
+ (void)GetAmountTransferred(&currBytes);
+ (void)GetSize(&maxBytes);
+ mDownloadManager->NotifyListenersOnProgressChange(
+ aWebProgress, aRequest, currBytes, maxBytes, currBytes, maxBytes, this);
+
+ // If the maximums are different, then there must be more than one file
+ if (aMaxSelfProgress != aMaxTotalProgress)
+ mHasMultipleFiles = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::OnRefreshAttempted(nsIWebProgress *aWebProgress,
+ nsIURI *aUri,
+ int32_t aDelay,
+ bool aSameUri,
+ bool *allowRefresh)
+{
+ *allowRefresh = true;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIWebProgressListener
+
+NS_IMETHODIMP
+nsDownload::OnProgressChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ return OnProgressChange64(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress);
+}
+
+NS_IMETHODIMP
+nsDownload::OnLocationChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, nsIURI *aLocation,
+ uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::OnStatusChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, nsresult aStatus,
+ const char16_t *aMessage)
+{
+ if (NS_FAILED(aStatus))
+ return FailDownload(aStatus, aMessage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::OnStateChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Must call OnStateChange in main thread");
+
+ // We don't want to lose access to our member variables
+ RefPtr<nsDownload> kungFuDeathGrip = this;
+
+ // Check if we're starting a request; the NETWORK flag is necessary to not
+ // pick up the START of *each* file but only for the whole request
+ if ((aStateFlags & STATE_START) && (aStateFlags & STATE_IS_NETWORK)) {
+ nsresult rv;
+ nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t status;
+ rv = channel->GetResponseStatus(&status);
+ // HTTP 450 - Blocked by parental control proxies
+ if (NS_SUCCEEDED(rv) && status == 450) {
+ // Cancel using the provided object
+ (void)Cancel();
+
+ // Fail the download
+ (void)SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL);
+ }
+ }
+ } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK) &&
+ IsFinishable()) {
+ // We got both STOP and NETWORK so that means the whole request is done
+ // (and not just a single file if there are multiple files)
+ if (NS_SUCCEEDED(aStatus)) {
+ // We can't completely trust the bytes we've added up because we might be
+ // missing on some/all of the progress updates (especially from cache).
+ // Our best bet is the file itself, but if for some reason it's gone or
+ // if we have multiple files, the next best is what we've calculated.
+ int64_t fileSize;
+ nsCOMPtr<nsIFile> file;
+ // We need a nsIFile clone to deal with file size caching issues. :(
+ nsCOMPtr<nsIFile> clone;
+ if (!mHasMultipleFiles &&
+ NS_SUCCEEDED(GetTargetFile(getter_AddRefs(file))) &&
+ NS_SUCCEEDED(file->Clone(getter_AddRefs(clone))) &&
+ NS_SUCCEEDED(clone->GetFileSize(&fileSize)) && fileSize > 0) {
+ mCurrBytes = mMaxBytes = fileSize;
+
+ // If we resumed, keep the fact that we did and fix size calculations
+ if (WasResumed())
+ mResumedAt = 0;
+ } else if (mMaxBytes == -1) {
+ mMaxBytes = mCurrBytes;
+ } else {
+ mCurrBytes = mMaxBytes;
+ }
+
+ mPercentComplete = 100;
+ mLastUpdate = PR_Now();
+
+#ifdef DOWNLOAD_SCANNER
+ bool scan = true;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ (void)prefs->GetBoolPref(PREF_BDM_SCANWHENDONE, &scan);
+
+ if (scan)
+ (void)SetState(nsIDownloadManager::DOWNLOAD_SCANNING);
+ else
+ (void)SetState(nsIDownloadManager::DOWNLOAD_FINISHED);
+#else
+ (void)SetState(nsIDownloadManager::DOWNLOAD_FINISHED);
+#endif
+ } else {
+ // We failed for some unknown reason -- fail with a generic message
+ (void)FailDownload(aStatus, nullptr);
+ }
+ }
+
+ mDownloadManager->NotifyListenersOnStateChange(aWebProgress, aRequest,
+ aStateFlags, aStatus, this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, uint32_t aState)
+{
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIDownload
+
+NS_IMETHODIMP
+nsDownload::Init(nsIURI *aSource,
+ nsIURI *aTarget,
+ const nsAString& aDisplayName,
+ nsIMIMEInfo *aMIMEInfo,
+ PRTime aStartTime,
+ nsIFile *aTempFile,
+ nsICancelable *aCancelable,
+ bool aIsPrivate)
+{
+ NS_WARNING("Huh...how did we get here?!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetState(int16_t *aState)
+{
+ *aState = mDownloadState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetDisplayName(nsAString &aDisplayName)
+{
+ aDisplayName = mDisplayName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetCancelable(nsICancelable **aCancelable)
+{
+ *aCancelable = mCancelable;
+ NS_IF_ADDREF(*aCancelable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetTarget(nsIURI **aTarget)
+{
+ *aTarget = mTarget;
+ NS_IF_ADDREF(*aTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetSource(nsIURI **aSource)
+{
+ *aSource = mSource;
+ NS_IF_ADDREF(*aSource);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetStartTime(int64_t *aStartTime)
+{
+ *aStartTime = mStartTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetPercentComplete(int32_t *aPercentComplete)
+{
+ *aPercentComplete = mPercentComplete;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetAmountTransferred(int64_t *aAmountTransferred)
+{
+ *aAmountTransferred = mCurrBytes + (WasResumed() ? mResumedAt : 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetSize(int64_t *aSize)
+{
+ *aSize = mMaxBytes + (WasResumed() && mMaxBytes != -1 ? mResumedAt : 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetMIMEInfo(nsIMIMEInfo **aMIMEInfo)
+{
+ *aMIMEInfo = mMIMEInfo;
+ NS_IF_ADDREF(*aMIMEInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetTargetFile(nsIFile **aTargetFile)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mTarget, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ file.forget(aTargetFile);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDownload::GetSpeed(double *aSpeed)
+{
+ *aSpeed = mSpeed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetId(uint32_t *aId)
+{
+ if (mPrivate) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aId = mID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetGuid(nsACString &aGUID)
+{
+ aGUID = mGUID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetReferrer(nsIURI **referrer)
+{
+ NS_IF_ADDREF(*referrer = mReferrer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetResumable(bool *resumable)
+{
+ *resumable = IsResumable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetIsPrivate(bool *isPrivate)
+{
+ *isPrivate = mPrivate;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsDownload Helper Functions
+
+void
+nsDownload::Finalize()
+{
+ // We're stopping, so break the cycle we created at download start
+ mCancelable = nullptr;
+
+ // Reset values that aren't needed anymore, so the DB can be updated as well
+ mEntityID.Truncate();
+ mTempFile = nullptr;
+
+ // Remove ourself from the active downloads
+ nsCOMArray<nsDownload>& currentDownloads = mPrivate ?
+ mDownloadManager->mCurrentPrivateDownloads :
+ mDownloadManager->mCurrentDownloads;
+ (void)currentDownloads.RemoveObject(this);
+
+ // Make sure we do not automatically resume
+ mAutoResume = DONT_RESUME;
+}
+
+nsresult
+nsDownload::ExecuteDesiredAction()
+{
+ // nsExternalHelperAppHandler is the only caller of AddDownload that sets a
+ // tempfile parameter. In this case, execute the desired action according to
+ // the saved mime info.
+ if (!mTempFile) {
+ return NS_OK;
+ }
+
+ // We need to bail if for some reason the temp file got removed
+ bool fileExists;
+ if (NS_FAILED(mTempFile->Exists(&fileExists)) || !fileExists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ // Assume an unknown action is save to disk
+ nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
+ if (mMIMEInfo) {
+ nsresult rv = mMIMEInfo->GetPreferredAction(&action);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsresult rv = NS_OK;
+ switch (action) {
+ case nsIMIMEInfo::saveToDisk:
+ // Move the file to the proper location
+ rv = MoveTempToTarget();
+ if (NS_SUCCEEDED(rv)) {
+ rv = FixTargetPermissions();
+ }
+ break;
+ case nsIMIMEInfo::useHelperApp:
+ case nsIMIMEInfo::useSystemDefault:
+ // For these cases we have to move the file to the target location and
+ // open with the appropriate application
+ rv = OpenWithApplication();
+ break;
+ default:
+ break;
+ }
+
+ return rv;
+}
+
+nsresult
+nsDownload::FixTargetPermissions()
+{
+ nsCOMPtr<nsIFile> target;
+ nsresult rv = GetTargetFile(getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set perms according to umask.
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService("@mozilla.org/system-info;1");
+ uint32_t gUserUmask = 0;
+ rv = infoService->GetPropertyAsUint32(NS_LITERAL_STRING("umask"),
+ &gUserUmask);
+ if (NS_SUCCEEDED(rv)) {
+ (void)target->SetPermissions(0666 & ~gUserUmask);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsDownload::MoveTempToTarget()
+{
+ nsCOMPtr<nsIFile> target;
+ nsresult rv = GetTargetFile(getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // MoveTo will fail if the file already exists, but we've already obtained
+ // confirmation from the user that this is OK, so remove it if it exists.
+ bool fileExists;
+ if (NS_SUCCEEDED(target->Exists(&fileExists)) && fileExists) {
+ rv = target->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Extract the new leaf name from the file location
+ nsAutoString fileName;
+ rv = target->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> dir;
+ rv = target->GetParent(getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mTempFile->MoveTo(dir, fileName);
+ return rv;
+}
+
+nsresult
+nsDownload::OpenWithApplication()
+{
+ // First move the temporary file to the target location
+ nsCOMPtr<nsIFile> target;
+ nsresult rv = GetTargetFile(getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Move the temporary file to the target location
+ rv = MoveTempToTarget();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool deleteTempFileOnExit;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (!prefs || NS_FAILED(prefs->GetBoolPref(PREF_BH_DELETETEMPFILEONEXIT,
+ &deleteTempFileOnExit))) {
+ // No prefservice or no pref set; use default value
+#if !defined(XP_MACOSX)
+ // Mac users have been very verbal about temp files being deleted on
+ // app exit - they don't like it - but we'll continue to do this on
+ // other platforms for now.
+ deleteTempFileOnExit = true;
+#else
+ deleteTempFileOnExit = false;
+#endif
+ }
+
+ // Always schedule files to be deleted at the end of the private browsing
+ // mode, regardless of the value of the pref.
+ if (deleteTempFileOnExit || mPrivate) {
+
+ // Make the tmp file readonly so users won't lose changes.
+ target->SetPermissions(0400);
+
+ // Use the ExternalHelperAppService to push the temporary file to the list
+ // of files to be deleted on exit.
+ nsCOMPtr<nsPIExternalAppLauncher> appLauncher(do_GetService
+ (NS_EXTERNALHELPERAPPSERVICE_CONTRACTID));
+
+ // Even if we are unable to get this service we return the result
+ // of LaunchWithFile() which makes more sense.
+ if (appLauncher) {
+ if (mPrivate) {
+ (void)appLauncher->DeleteTemporaryPrivateFileWhenPossible(target);
+ } else {
+ (void)appLauncher->DeleteTemporaryFileOnExit(target);
+ }
+ }
+ }
+
+ return mMIMEInfo->LaunchWithFile(target);
+}
+
+void
+nsDownload::SetStartTime(int64_t aStartTime)
+{
+ mStartTime = aStartTime;
+ mLastUpdate = aStartTime;
+}
+
+void
+nsDownload::SetProgressBytes(int64_t aCurrBytes, int64_t aMaxBytes)
+{
+ mCurrBytes = aCurrBytes;
+ mMaxBytes = aMaxBytes;
+
+ // Get the real bytes that include resume position
+ int64_t currBytes, maxBytes;
+ (void)GetAmountTransferred(&currBytes);
+ (void)GetSize(&maxBytes);
+
+ if (currBytes == maxBytes)
+ mPercentComplete = 100;
+ else if (maxBytes <= 0)
+ mPercentComplete = -1;
+ else
+ mPercentComplete = (int32_t)((double)currBytes / maxBytes * 100 + .5);
+}
+
+NS_IMETHODIMP
+nsDownload::Pause()
+{
+ if (!IsResumable())
+ return NS_ERROR_UNEXPECTED;
+
+ nsresult rv = CancelTransfer();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return SetState(nsIDownloadManager::DOWNLOAD_PAUSED);
+}
+
+nsresult
+nsDownload::CancelTransfer()
+{
+ nsresult rv = NS_OK;
+ if (mCancelable) {
+ rv = mCancelable->Cancel(NS_BINDING_ABORTED);
+ // we're done with this, so break the cycle
+ mCancelable = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDownload::Cancel()
+{
+ // Don't cancel if download is already finished
+ if (IsFinished())
+ return NS_OK;
+
+ // Have the download cancel its connection
+ (void)CancelTransfer();
+
+ // Dump the temp file because we know we don't need the file anymore. The
+ // underlying transfer creating the file doesn't delete the file because it
+ // can't distinguish between a pause that cancels the transfer or a real
+ // cancel.
+ if (mTempFile) {
+ bool exists;
+ mTempFile->Exists(&exists);
+ if (exists)
+ mTempFile->Remove(false);
+ }
+
+ nsCOMPtr<nsIFile> file;
+ if (NS_SUCCEEDED(GetTargetFile(getter_AddRefs(file))))
+ {
+ bool exists;
+ file->Exists(&exists);
+ if (exists)
+ file->Remove(false);
+ }
+
+ nsresult rv = SetState(nsIDownloadManager::DOWNLOAD_CANCELED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::Resume()
+{
+ if (!IsPaused() || !IsResumable())
+ return NS_ERROR_UNEXPECTED;
+
+ nsresult rv;
+ nsCOMPtr<nsIWebBrowserPersist> wbp =
+ do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = wbp->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE |
+ nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a new channel for the source URI
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(wbp));
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ mSource,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aLoadGroup
+ ir);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
+ if (pbChannel) {
+ pbChannel->SetPrivate(mPrivate);
+ }
+
+ // Make sure we can get a file, either the temporary or the real target, for
+ // both purposes of file size and a target to write to
+ nsCOMPtr<nsIFile> targetLocalFile(mTempFile);
+ if (!targetLocalFile) {
+ rv = GetTargetFile(getter_AddRefs(targetLocalFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Get the file size to be used as an offset, but if anything goes wrong
+ // along the way, we'll silently restart at 0.
+ int64_t fileSize;
+ // We need a nsIFile clone to deal with file size caching issues. :(
+ nsCOMPtr<nsIFile> clone;
+ if (NS_FAILED(targetLocalFile->Clone(getter_AddRefs(clone))) ||
+ NS_FAILED(clone->GetFileSize(&fileSize)))
+ fileSize = 0;
+
+ // Set the channel to resume at the right position along with the entityID
+ nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(channel));
+ if (!resumableChannel)
+ return NS_ERROR_UNEXPECTED;
+ rv = resumableChannel->ResumeAt(fileSize, mEntityID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we know the max size, we know what it should be when resuming
+ int64_t maxBytes;
+ GetSize(&maxBytes);
+ SetProgressBytes(0, maxBytes != -1 ? maxBytes - fileSize : -1);
+ // Track where we resumed because progress notifications restart at 0
+ mResumedAt = fileSize;
+
+ // Set the referrer
+ if (mReferrer) {
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ rv = httpChannel->SetReferrer(mReferrer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Creates a cycle that will be broken when the download finishes
+ mCancelable = wbp;
+ (void)wbp->SetProgressListener(this);
+
+ // Save the channel using nsIWBP
+ rv = wbp->SaveChannel(channel, targetLocalFile);
+ if (NS_FAILED(rv)) {
+ mCancelable = nullptr;
+ (void)wbp->SetProgressListener(nullptr);
+ return rv;
+ }
+
+ return SetState(nsIDownloadManager::DOWNLOAD_DOWNLOADING);
+}
+
+NS_IMETHODIMP
+nsDownload::Remove()
+{
+ return mDownloadManager->RemoveDownload(mGUID);
+}
+
+NS_IMETHODIMP
+nsDownload::Retry()
+{
+ return mDownloadManager->RetryDownload(mGUID);
+}
+
+bool
+nsDownload::IsPaused()
+{
+ return mDownloadState == nsIDownloadManager::DOWNLOAD_PAUSED;
+}
+
+bool
+nsDownload::IsResumable()
+{
+ return !mEntityID.IsEmpty();
+}
+
+bool
+nsDownload::WasResumed()
+{
+ return mResumedAt != -1;
+}
+
+bool
+nsDownload::ShouldAutoResume()
+{
+ return mAutoResume == AUTO_RESUME;
+}
+
+bool
+nsDownload::IsFinishable()
+{
+ return mDownloadState == nsIDownloadManager::DOWNLOAD_NOTSTARTED ||
+ mDownloadState == nsIDownloadManager::DOWNLOAD_QUEUED ||
+ mDownloadState == nsIDownloadManager::DOWNLOAD_DOWNLOADING;
+}
+
+bool
+nsDownload::IsFinished()
+{
+ return mDownloadState == nsIDownloadManager::DOWNLOAD_FINISHED;
+}
+
+nsresult
+nsDownload::UpdateDB()
+{
+ NS_ASSERTION(mID, "Download ID is stored as zero. This is bad!");
+ NS_ASSERTION(mDownloadManager, "Egads! We have no download manager!");
+
+ mozIStorageStatement *stmt = mPrivate ?
+ mDownloadManager->mUpdatePrivateDownloadStatement : mDownloadManager->mUpdateDownloadStatement;
+
+ nsAutoString tempPath;
+ if (mTempFile)
+ (void)mTempFile->GetPath(tempPath);
+ nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("tempPath"), tempPath);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), mStartTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), mLastUpdate);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), mDownloadState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mReferrer) {
+ nsAutoCString referrer;
+ rv = mReferrer->GetSpec(referrer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("referrer"), referrer);
+ } else {
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("referrer"));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("entityID"), mEntityID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t currBytes;
+ (void)GetAmountTransferred(&currBytes);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("currBytes"), currBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t maxBytes;
+ (void)GetSize(&maxBytes);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("maxBytes"), maxBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), mAutoResume);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return stmt->Execute();
+}
+
+nsresult
+nsDownload::FailDownload(nsresult aStatus, const char16_t *aMessage)
+{
+ // Grab the bundle before potentially losing our member variables
+ nsCOMPtr<nsIStringBundle> bundle = mDownloadManager->mBundle;
+
+ (void)SetState(nsIDownloadManager::DOWNLOAD_FAILED);
+
+ // Get title for alert.
+ nsXPIDLString title;
+ nsresult rv = bundle->GetStringFromName(
+ u"downloadErrorAlertTitle", getter_Copies(title));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get a generic message if we weren't supplied one
+ nsXPIDLString message;
+ message = aMessage;
+ if (message.IsEmpty()) {
+ rv = bundle->GetStringFromName(
+ u"downloadErrorGeneric", getter_Copies(message));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Get Download Manager window to be parent of alert
+ nsCOMPtr<nsIWindowMediator> wm =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIDOMWindowProxy> dmWindow;
+ rv = wm->GetMostRecentWindow(u"Download:Manager",
+ getter_AddRefs(dmWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Show alert
+ nsCOMPtr<nsIPromptService> prompter =
+ do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prompter->Alert(dmWindow, title, message);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/statusfilter/nsBrowserStatusFilter.cpp.orig
@@ -0,0 +1,391 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsBrowserStatusFilter.h"
+#include "nsIChannel.h"
+#include "nsIServiceManager.h"
+#include "nsString.h"
+
+// XXX
+// XXX DO NOT TOUCH THIS CODE UNLESS YOU KNOW WHAT YOU'RE DOING !!!
+// XXX
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter <public>
+//-----------------------------------------------------------------------------
+
+nsBrowserStatusFilter::nsBrowserStatusFilter()
+ : mCurProgress(0)
+ , mMaxProgress(0)
+ , mStatusIsDirty(true)
+ , mCurrentPercentage(0)
+ , mTotalRequests(0)
+ , mFinishedRequests(0)
+ , mUseRealProgressFlag(false)
+ , mDelayedStatus(false)
+ , mDelayedProgress(false)
+{
+}
+
+nsBrowserStatusFilter::~nsBrowserStatusFilter()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsBrowserStatusFilter,
+ nsIWebProgress,
+ nsIWebProgressListener,
+ nsIWebProgressListener2,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter::nsIWebProgress
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::AddProgressListener(nsIWebProgressListener *aListener,
+ uint32_t aNotifyMask)
+{
+ mListener = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::RemoveProgressListener(nsIWebProgressListener *aListener)
+{
+ if (aListener == mListener)
+ mListener = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::GetDOMWindow(mozIDOMWindowProxy **aResult)
+{
+ NS_NOTREACHED("nsBrowserStatusFilter::GetDOMWindow");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::GetDOMWindowID(uint64_t *aResult)
+{
+ *aResult = 0;
+ NS_NOTREACHED("nsBrowserStatusFilter::GetDOMWindowID");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::GetIsTopLevel(bool *aIsTopLevel)
+{
+ *aIsTopLevel = false;
+ NS_NOTREACHED("nsBrowserStatusFilter::GetIsTopLevel");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::GetIsLoadingDocument(bool *aIsLoadingDocument)
+{
+ NS_NOTREACHED("nsBrowserStatusFilter::GetIsLoadingDocument");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::GetLoadType(uint32_t *aLoadType)
+{
+ *aLoadType = 0;
+ NS_NOTREACHED("nsBrowserStatusFilter::GetLoadType");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter::nsIWebProgressListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnStateChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ if (!mListener)
+ return NS_OK;
+
+ if (aStateFlags & STATE_START) {
+ if (aStateFlags & STATE_IS_NETWORK) {
+ ResetMembers();
+ }
+ if (aStateFlags & STATE_IS_REQUEST) {
+ ++mTotalRequests;
+
+ // if the total requests exceeds 1, then we'll base our progress
+ // notifications on the percentage of completed requests.
+ // otherwise, progress for the single request will be reported.
+ mUseRealProgressFlag = (mTotalRequests == 1);
+ }
+ }
+ else if (aStateFlags & STATE_STOP) {
+ if (aStateFlags & STATE_IS_REQUEST) {
+ ++mFinishedRequests;
+ // Note: Do not return from here. This is necessary so that the
+ // STATE_STOP can still be relayed to the listener if needed
+ // (bug 209330)
+ if (!mUseRealProgressFlag && mTotalRequests)
+ OnProgressChange(nullptr, nullptr, 0, 0,
+ mFinishedRequests, mTotalRequests);
+ }
+ }
+ else if (aStateFlags & STATE_TRANSFERRING) {
+ if (aStateFlags & STATE_IS_REQUEST) {
+ if (!mUseRealProgressFlag && mTotalRequests)
+ return OnProgressChange(nullptr, nullptr, 0, 0,
+ mFinishedRequests, mTotalRequests);
+ }
+
+ // no need to forward this state change
+ return NS_OK;
+ } else {
+ // no need to forward this state change
+ return NS_OK;
+ }
+
+ // If we're here, we have either STATE_START or STATE_STOP. The
+ // listener only cares about these in certain conditions.
+ bool isLoadingDocument = false;
+ if ((aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK ||
+ (aStateFlags & nsIWebProgressListener::STATE_IS_REQUEST &&
+ mFinishedRequests == mTotalRequests &&
+ (aWebProgress->GetIsLoadingDocument(&isLoadingDocument),
+ !isLoadingDocument)))) {
+ if (mTimer && (aStateFlags & nsIWebProgressListener::STATE_STOP)) {
+ mTimer->Cancel();
+ ProcessTimeout();
+ }
+
+ return mListener->OnStateChange(aWebProgress, aRequest, aStateFlags,
+ aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnProgressChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ if (!mListener)
+ return NS_OK;
+
+ if (!mUseRealProgressFlag && aRequest)
+ return NS_OK;
+
+ //
+ // limit frequency of calls to OnProgressChange
+ //
+
+ mCurProgress = (int64_t)aCurTotalProgress;
+ mMaxProgress = (int64_t)aMaxTotalProgress;
+
+ if (mDelayedProgress)
+ return NS_OK;
+
+ if (!mDelayedStatus) {
+ MaybeSendProgress();
+ StartDelayTimer();
+ }
+
+ mDelayedProgress = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnLocationChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ nsIURI *aLocation,
+ uint32_t aFlags)
+{
+ if (!mListener)
+ return NS_OK;
+
+ return mListener->OnLocationChange(aWebProgress, aRequest, aLocation,
+ aFlags);
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnStatusChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ nsresult aStatus,
+ const char16_t *aMessage)
+{
+ if (!mListener)
+ return NS_OK;
+
+ //
+ // limit frequency of calls to OnStatusChange
+ //
+ if (mStatusIsDirty || !mCurrentStatusMsg.Equals(aMessage)) {
+ mStatusIsDirty = true;
+ mStatusMsg = aMessage;
+ }
+
+ if (mDelayedStatus)
+ return NS_OK;
+
+ if (!mDelayedProgress) {
+ MaybeSendStatus();
+ StartDelayTimer();
+ }
+
+ mDelayedStatus = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t aState)
+{
+ if (!mListener)
+ return NS_OK;
+
+ return mListener->OnSecurityChange(aWebProgress, aRequest, aState);
+}
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter::nsIWebProgressListener2
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnProgressChange64(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int64_t aCurSelfProgress,
+ int64_t aMaxSelfProgress,
+ int64_t aCurTotalProgress,
+ int64_t aMaxTotalProgress)
+{
+ // XXX truncates 64-bit to 32-bit
+ return OnProgressChange(aWebProgress, aRequest,
+ (int32_t)aCurSelfProgress,
+ (int32_t)aMaxSelfProgress,
+ (int32_t)aCurTotalProgress,
+ (int32_t)aMaxTotalProgress);
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnRefreshAttempted(nsIWebProgress *aWebProgress,
+ nsIURI *aUri,
+ int32_t aDelay,
+ bool aSameUri,
+ bool *allowRefresh)
+{
+ nsCOMPtr<nsIWebProgressListener2> listener =
+ do_QueryInterface(mListener);
+ if (!listener) {
+ *allowRefresh = true;
+ return NS_OK;
+ }
+
+ return listener->OnRefreshAttempted(aWebProgress, aUri, aDelay, aSameUri,
+ allowRefresh);
+}
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter <private>
+//-----------------------------------------------------------------------------
+
+void
+nsBrowserStatusFilter::ResetMembers()
+{
+ mTotalRequests = 0;
+ mFinishedRequests = 0;
+ mUseRealProgressFlag = false;
+ mMaxProgress = 0;
+ mCurProgress = 0;
+ mCurrentPercentage = 0;
+ mStatusIsDirty = true;
+}
+
+void
+nsBrowserStatusFilter::MaybeSendProgress()
+{
+ if (mCurProgress > mMaxProgress || mCurProgress <= 0)
+ return;
+
+ // check our percentage
+ int32_t percentage = (int32_t) double(mCurProgress) * 100 / mMaxProgress;
+
+ // The progress meter only updates for increases greater than 3 percent
+ if (percentage > (mCurrentPercentage + 3)) {
+ mCurrentPercentage = percentage;
+ // XXX truncates 64-bit to 32-bit
+ mListener->OnProgressChange(nullptr, nullptr, 0, 0,
+ (int32_t)mCurProgress,
+ (int32_t)mMaxProgress);
+ }
+}
+
+void
+nsBrowserStatusFilter::MaybeSendStatus()
+{
+ if (mStatusIsDirty) {
+ mListener->OnStatusChange(nullptr, nullptr, NS_OK, mStatusMsg.get());
+ mCurrentStatusMsg = mStatusMsg;
+ mStatusIsDirty = false;
+ }
+}
+
+nsresult
+nsBrowserStatusFilter::StartDelayTimer()
+{
+ NS_ASSERTION(!DelayInEffect(), "delay should not be in effect");
+
+ mTimer = new Mozilla::Timer;
+ if (!mTimer)
+ return NS_ERROR_FAILURE;
+
+ return mTimer->InitWithNamedFuncCallback(
+ TimeoutHandler, this, 160, nsITimer::TYPE_ONE_SHOT,
+ "nsBrowserStatusFilter::TimeoutHandler");
+}
+
+void
+nsBrowserStatusFilter::ProcessTimeout()
+{
+ mTimer = nullptr;
+
+ if (!mListener)
+ return;
+
+ if (mDelayedStatus) {
+ mDelayedStatus = false;
+ MaybeSendStatus();
+ }
+
+ if (mDelayedProgress) {
+ mDelayedProgress = false;
+ MaybeSendProgress();
+ }
+}
+
+void
+nsBrowserStatusFilter::TimeoutHandler(nsITimer *aTimer, void *aClosure)
+{
+ nsBrowserStatusFilter *self = reinterpret_cast<nsBrowserStatusFilter *>(aClosure);
+ if (!self) {
+ NS_ERROR("no self");
+ return;
+ }
+
+ self->ProcessTimeout();
+}