зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
366383f783
Коммит
c977a538e6
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче