Bug 1530190. Call SHGetFileInfo off the main thread in nsIconChannel on Windows. r=tnikkel

There are two reasons for this. One is performance, it can be slow. The second is that is can spin the event loop which can re-enter into things like layout.

This patch uses the image decoder thread pool for that. But this is unsuitable (reason in next patch), the next patch changes it to use the img io thread used for network to pass data to imglib off main thread.

Differential Revision: https://phabricator.services.mozilla.com/D28419
This commit is contained in:
Andrew Osmond 2019-04-22 17:30:40 -05:00
Родитель 366383f783
Коммит c977a538e6
3 изменённых файлов: 190 добавлений и 50 удалений

Просмотреть файл

@ -8,4 +8,8 @@ SOURCES += [
'nsIconChannel.cpp',
]
LOCAL_INCLUDES += [
'/image',
]
FINAL_LIBRARY = 'xul'

Просмотреть файл

@ -17,7 +17,6 @@
#include "nsMemory.h"
#include "nsIStringStream.h"
#include "nsIURL.h"
#include "nsIOutputStream.h"
#include "nsIPipe.h"
#include "nsNetCID.h"
#include "nsIFile.h"
@ -29,6 +28,11 @@
#include "nsContentSecurityManager.h"
#include "nsContentUtils.h"
#include "nsNetUtil.h"
#include "nsThreadUtils.h"
#include "Decoder.h"
#include "IDecodingTask.h"
#include "DecodePool.h"
// we need windows.h to read out registry information...
#include <windows.h>
@ -38,6 +42,7 @@
#include <wchar.h>
using namespace mozilla;
using namespace mozilla::image;
struct ICONFILEHEADER {
uint16_t ifhReserved;
@ -61,6 +66,43 @@ static SHSTOCKICONID GetStockIconIDForName(const nsACString& aStockName) {
return aStockName.EqualsLiteral("uac-shield") ? SIID_SHIELD : SIID_INVALID;
}
class nsIconChannel::IconAsyncOpenTask final : public IDecodingTask {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IconAsyncOpenTask, override)
IconAsyncOpenTask(nsIconChannel* aChannel, nsIEventTarget* aTarget,
nsCOMPtr<nsIFile>&& aLocalFile, nsAutoString& aPath,
UINT aInfoFlags)
: mChannel(aChannel),
mTarget(aTarget),
mLocalFile(std::move(aLocalFile)),
mPath(aPath),
mInfoFlags(aInfoFlags) {}
void Run() override;
bool ShouldPreferSyncRun() const override { return false; }
TaskPriority Priority() const override { return TaskPriority::eLow; }
private:
~IconAsyncOpenTask() override = default;
RefPtr<nsIconChannel> mChannel;
nsCOMPtr<nsIEventTarget> mTarget;
nsCOMPtr<nsIFile> mLocalFile;
nsAutoString mPath;
UINT mInfoFlags;
};
void nsIconChannel::IconAsyncOpenTask::Run() {
HICON hIcon = nullptr;
nsresult rv =
mChannel->GetHIconFromFile(mLocalFile, mPath, mInfoFlags, &hIcon);
nsCOMPtr<nsIRunnable> task = NewRunnableMethod<HICON, nsresult>(
"nsIconChannel::FinishAsyncOpen", mChannel,
&nsIconChannel::FinishAsyncOpen, hIcon, rv);
mTarget->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
}
// nsIconChannel methods
nsIconChannel::nsIconChannel() {}
@ -162,7 +204,13 @@ nsIconChannel::Open(nsIInputStream** aStream) {
nsContentSecurityManager::doContentSecurityCheck(this, listener);
NS_ENSURE_SUCCESS(rv, rv);
return MakeInputStream(aStream, false);
HICON hIcon = nullptr;
rv = GetHIcon(/* aNonBlocking */ false, &hIcon);
if (NS_FAILED(rv)) {
return rv;
}
return MakeInputStream(aStream, /* aNonBlocking */ false, hIcon);
}
nsresult nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile,
@ -191,6 +239,39 @@ nsresult nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile,
return file->Clone(aLocalFile);
}
void nsIconChannel::OnAsyncError(nsresult aStatus) {
OnStartRequest(this);
OnStopRequest(this, aStatus);
}
void nsIconChannel::FinishAsyncOpen(HICON aIcon, nsresult aStatus) {
MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(aStatus)) {
OnAsyncError(aStatus);
return;
}
nsCOMPtr<nsIInputStream> inStream;
nsresult rv = MakeInputStream(getter_AddRefs(inStream),
/* aNonBlocking */ true, aIcon);
if (NS_FAILED(rv)) {
OnAsyncError(rv);
return;
}
rv = mPump->Init(inStream, 0, 0, false, mListenerTarget);
if (NS_FAILED(rv)) {
OnAsyncError(rv);
return;
}
rv = mPump->AsyncRead(this, nullptr);
if (NS_FAILED(rv)) {
OnAsyncError(rv);
}
}
NS_IMETHODIMP
nsIconChannel::AsyncOpen(nsIStreamListener* aListener) {
nsCOMPtr<nsIStreamListener> listener = aListener;
@ -210,34 +291,41 @@ nsIconChannel::AsyncOpen(nsIStreamListener* aListener) {
"security flags in loadInfo but doContentSecurityCheck() not called");
nsCOMPtr<nsIInputStream> inStream;
rv = MakeInputStream(getter_AddRefs(inStream), true);
rv = EnsurePipeCreated(/* aIconSize */ 0, /* aNonBlocking */ true);
if (NS_FAILED(rv)) {
mCallbacks = nullptr;
return rv;
}
// Init our streampump
nsCOMPtr<nsIEventTarget> target = nsContentUtils::GetEventTargetByLoadInfo(
mListenerTarget = nsContentUtils::GetEventTargetByLoadInfo(
mLoadInfo, mozilla::TaskCategory::Other);
rv = mPump->Init(inStream, 0, 0, false, target);
if (!mListenerTarget) {
mListenerTarget = do_GetMainThread();
}
// If we pass aNonBlocking as true, GetHIcon will always have dispatched
// upon success.
HICON hIcon = nullptr;
rv = GetHIcon(/* aNonBlocking */ true, &hIcon);
if (NS_FAILED(rv)) {
mCallbacks = nullptr;
mInputStream = nullptr;
mOutputStream = nullptr;
mListenerTarget = nullptr;
return rv;
}
rv = mPump->AsyncRead(this, nullptr);
if (NS_SUCCEEDED(rv)) {
// Store our real listener
mListener = aListener;
// Add ourself to the load group, if available
if (mLoadGroup) {
mLoadGroup->AddRequest(this, nullptr);
}
} else {
mCallbacks = nullptr;
// We shouldn't have the icon yet if it is non-blocking.
MOZ_ASSERT(!hIcon);
// Add ourself to the load group, if available
if (mLoadGroup) {
mLoadGroup->AddRequest(this, nullptr);
}
return rv;
// Store our real listener
mListener = aListener;
return NS_OK;
}
static DWORD GetSpecialFolderIcon(nsIFile* aFile, int aFolder,
@ -276,7 +364,7 @@ static UINT GetSizeInfoFlag(uint32_t aDesiredImageSize) {
return (UINT)(aDesiredImageSize > 16 ? SHGFI_SHELLICONSIZE : SHGFI_SMALLICON);
}
nsresult nsIconChannel::GetHIconFromFile(HICON* hIcon) {
nsresult nsIconChannel::GetHIconFromFile(bool aNonBlocking, HICON* hIcon) {
nsCString contentType;
nsCString fileExt;
nsCOMPtr<nsIFile> localFile; // file we want an icon for
@ -287,7 +375,6 @@ nsresult nsIconChannel::GetHIconFromFile(HICON* hIcon) {
// if the file exists, we are going to use it's real attributes...
// otherwise we only want to use it for it's extension...
SHFILEINFOW sfi;
UINT infoFlags = SHGFI_ICON;
bool fileExists = false;
@ -334,13 +421,28 @@ nsresult nsIconChannel::GetHIconFromFile(HICON* hIcon) {
filePath = NS_LITERAL_STRING(".") + NS_ConvertUTF8toUTF16(defFileExt);
}
if (aNonBlocking) {
RefPtr<IconAsyncOpenTask> task = new IconAsyncOpenTask(
this, mListenerTarget, std::move(localFile), filePath, infoFlags);
DecodePool::Singleton()->AsyncRun(task);
return NS_OK;
}
return GetHIconFromFile(localFile, filePath, infoFlags, hIcon);
}
nsresult nsIconChannel::GetHIconFromFile(nsIFile* aLocalFile,
const nsAutoString& aPath,
UINT aInfoFlags, HICON* hIcon) {
SHFILEINFOW sfi;
// Is this the "Desktop" folder?
DWORD shellResult =
GetSpecialFolderIcon(localFile, CSIDL_DESKTOP, &sfi, infoFlags);
GetSpecialFolderIcon(aLocalFile, CSIDL_DESKTOP, &sfi, aInfoFlags);
if (!shellResult) {
// Is this the "My Documents" folder?
shellResult =
GetSpecialFolderIcon(localFile, CSIDL_PERSONAL, &sfi, infoFlags);
GetSpecialFolderIcon(aLocalFile, CSIDL_PERSONAL, &sfi, aInfoFlags);
}
// There are other "Special Folders" and Namespace entities that we
@ -351,20 +453,20 @@ nsresult nsIconChannel::GetHIconFromFile(HICON* hIcon) {
// Not a special folder, or something else failed above.
if (!shellResult) {
shellResult = ::SHGetFileInfoW(filePath.get(), FILE_ATTRIBUTE_ARCHIVE, &sfi,
sizeof(sfi), infoFlags);
shellResult = ::SHGetFileInfoW(aPath.get(), FILE_ATTRIBUTE_ARCHIVE, &sfi,
sizeof(sfi), aInfoFlags);
}
if (shellResult && sfi.hIcon) {
*hIcon = sfi.hIcon;
} else {
rv = NS_ERROR_NOT_AVAILABLE;
if (!shellResult || !sfi.hIcon) {
return NS_ERROR_NOT_AVAILABLE;
}
return rv;
*hIcon = sfi.hIcon;
return NS_OK;
}
nsresult nsIconChannel::GetStockHIcon(nsIMozIconURI* aIconURI, HICON* hIcon) {
nsresult nsIconChannel::GetStockHIcon(nsIMozIconURI* aIconURI,
bool aNonBlocking, HICON* hIcon) {
nsresult rv = NS_OK;
uint32_t desiredImageSize;
@ -384,6 +486,14 @@ nsresult nsIconChannel::GetStockHIcon(nsIMozIconURI* aIconURI, HICON* hIcon) {
sii.cbSize = sizeof(sii);
HRESULT hr = SHGetStockIconInfo(stockIconID, infoFlags, &sii);
if (aNonBlocking) {
nsCOMPtr<nsIRunnable> task = NewRunnableMethod<HICON, nsresult>(
"nsIconChannel::FinishAsyncOpen", this, &nsIconChannel::FinishAsyncOpen,
sii.hIcon, rv);
mListenerTarget->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
return NS_OK;
}
if (SUCCEEDED(hr)) {
*hIcon = sii.hIcon;
} else {
@ -454,32 +564,43 @@ static BITMAPINFO* CreateBitmapInfo(BITMAPINFOHEADER* aHeader,
return bmi;
}
nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval,
bool aNonBlocking) {
nsresult nsIconChannel::EnsurePipeCreated(uint32_t aIconSize,
bool aNonBlocking) {
if (mInputStream || mOutputStream) {
return NS_OK;
}
return NS_NewPipe(getter_AddRefs(mInputStream), getter_AddRefs(mOutputStream),
aIconSize, aIconSize > 0 ? aIconSize : UINT32_MAX,
aNonBlocking);
}
nsresult nsIconChannel::GetHIcon(bool aNonBlocking, HICON* aIcon) {
// Check whether the icon requested's a file icon or a stock icon
nsresult rv = NS_ERROR_NOT_AVAILABLE;
// GetDIBits does not exist on windows mobile.
HICON hIcon = nullptr;
nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(mUrl, &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString stockIcon;
iconURI->GetStockIcon(stockIcon);
if (!stockIcon.IsEmpty()) {
rv = GetStockHIcon(iconURI, &hIcon);
} else {
rv = GetHIconFromFile(&hIcon);
return GetStockHIcon(iconURI, aNonBlocking, aIcon);
}
NS_ENSURE_SUCCESS(rv, rv);
return GetHIconFromFile(aNonBlocking, aIcon);
}
if (hIcon) {
nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval,
bool aNonBlocking, HICON aIcon) {
nsresult rv = NS_ERROR_FAILURE;
if (aIcon) {
// we got a handle to an icon. Now we want to get a bitmap for the icon
// using GetIconInfo....
ICONINFO iconInfo;
if (GetIconInfo(hIcon, &iconInfo)) {
if (GetIconInfo(aIcon, &iconInfo)) {
// we got the bitmaps, first find out their size
HDC hDC = CreateCompatibleDC(nullptr); // get a device context for
// the screen.
@ -560,16 +681,12 @@ nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval,
GetDIBits(hDC, iconInfo.hbmMask, 0, maskHeader.biHeight,
whereTo, maskInfo, DIB_RGB_COLORS)) {
// Now, create a pipe and stuff our data into it
nsCOMPtr<nsIInputStream> inStream;
nsCOMPtr<nsIOutputStream> outStream;
rv = NS_NewPipe(getter_AddRefs(inStream),
getter_AddRefs(outStream), iconSize, iconSize,
aNonBlocking);
rv = EnsurePipeCreated(iconSize, aNonBlocking);
if (NS_SUCCEEDED(rv)) {
uint32_t written;
rv = outStream->Write(buffer.get(), iconSize, &written);
rv = mOutputStream->Write(buffer.get(), iconSize, &written);
if (NS_SUCCEEDED(rv)) {
NS_ADDREF(*_retval = inStream);
NS_ADDREF(*_retval = mInputStream);
}
}
@ -584,13 +701,16 @@ nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval,
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
} // if we got icon info
DestroyIcon(hIcon);
DestroyIcon(aIcon);
} // if we got an hIcon
// If we didn't make a stream, then fail.
if (!*_retval && NS_SUCCEEDED(rv)) {
rv = NS_ERROR_NOT_AVAILABLE;
}
mInputStream = nullptr;
mOutputStream = nullptr;
return rv;
}
@ -728,6 +848,7 @@ nsIconChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
// Drop notification callbacks to prevent cycles.
mCallbacks = nullptr;
mListenerTarget = nullptr;
return NS_OK;
}

Просмотреть файл

@ -18,6 +18,7 @@
#include "nsIInterfaceRequestorUtils.h"
#include "nsIURI.h"
#include "nsIInputStreamPump.h"
#include "nsIOutputStream.h"
#include "nsIStreamListener.h"
#include "nsIIconURI.h"
@ -40,6 +41,12 @@ class nsIconChannel final : public nsIChannel, public nsIStreamListener {
nsresult Init(nsIURI* uri);
protected:
class IconAsyncOpenTask;
void OnAsyncError(nsresult aStatus);
void FinishAsyncOpen(HICON aIcon, nsresult aStatus);
nsresult EnsurePipeCreated(uint32_t aIconSize, bool aNonBlocking);
nsCOMPtr<nsIURI> mUrl;
nsCOMPtr<nsIURI> mOriginalURI;
nsCOMPtr<nsILoadGroup> mLoadGroup;
@ -48,18 +55,26 @@ class nsIconChannel final : public nsIChannel, public nsIStreamListener {
nsCOMPtr<nsILoadInfo> mLoadInfo;
nsCOMPtr<nsIInputStreamPump> mPump;
nsCOMPtr<nsIInputStream> mInputStream;
nsCOMPtr<nsIOutputStream> mOutputStream;
nsCOMPtr<nsIStreamListener> mListener;
nsCOMPtr<nsIEventTarget> mListenerTarget;
nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile,
uint32_t* aDesiredImageSize,
nsCString& aContentType,
nsCString& aFileExtension);
nsresult GetHIconFromFile(HICON* hIcon);
nsresult MakeInputStream(nsIInputStream** _retval, bool nonBlocking);
nsresult GetHIcon(bool aNonBlocking, HICON* hIcon);
nsresult GetHIconFromFile(bool aNonBlocking, HICON* hIcon);
nsresult GetHIconFromFile(nsIFile* aLocalFile, const nsAutoString& aPath,
UINT aInfoFlags, HICON* hIcon);
nsresult MakeInputStream(nsIInputStream** _retval, bool aNonBlocking,
HICON aIcon);
// Functions specific to Vista and above
protected:
nsresult GetStockHIcon(nsIMozIconURI* aIconURI, HICON* hIcon);
nsresult GetStockHIcon(nsIMozIconURI* aIconURI, bool aNonBlocking,
HICON* hIcon);
};
#endif // mozilla_image_encoders_icon_win_nsIconChannel_h