diff -r 000000000000 -r 6474c204b198 widget/windows/winrt/nsMetroFilePicker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widget/windows/winrt/nsMetroFilePicker.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,484 @@ +/* -*- 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 "nsMetroFilePicker.h" +#include "nsComponentManagerUtils.h" +#include "nsNetUtil.h" +#include "nsAutoPtr.h" +#include "MetroUtils.h" + +#include +#include + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Storage; +using namespace ABI::Windows::Storage::Pickers; +using namespace ABI::Windows::Storage::Streams; +using namespace ABI::Windows::UI; +using namespace ABI::Windows::UI::ViewManagement; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace mozilla::widget::winrt; + +/////////////////////////////////////////////////////////////////////////////// +// nsIFilePicker + +nsMetroFilePicker::nsMetroFilePicker() +{ +} + +nsMetroFilePicker::~nsMetroFilePicker() +{ +} + +NS_IMPL_ISUPPORTS(nsMetroFilePicker, nsIFilePicker) + +NS_IMETHODIMP +nsMetroFilePicker::Init(nsIDOMWindow *parent, const nsAString& title, int16_t mode) +{ + mMode = mode; + HRESULT hr; + switch(mMode) { + case nsIFilePicker::modeOpen: + case nsIFilePicker::modeOpenMultiple: + hr = ActivateGenericInstance(RuntimeClass_Windows_Storage_Pickers_FileOpenPicker, mFileOpenPicker); + AssertRetHRESULT(hr, NS_ERROR_UNEXPECTED); + return NS_OK; + case nsIFilePicker::modeSave: + hr = ActivateGenericInstance(RuntimeClass_Windows_Storage_Pickers_FileSavePicker, mFileSavePicker); + AssertRetHRESULT(hr, NS_ERROR_UNEXPECTED); + return NS_OK; + default: + return NS_ERROR_NOT_IMPLEMENTED; + } +} + +NS_IMETHODIMP +nsMetroFilePicker::Show(int16_t *aReturnVal) +{ + // Metro file picker only offers an async variant which calls back to the + // UI thread, which is the main thread. We therefore can't call it + // synchronously from the main thread. + return NS_ERROR_NOT_IMPLEMENTED; +} + +HRESULT nsMetroFilePicker::OnPickSingleFile(IAsyncOperation* aFile, + AsyncStatus aStatus) +{ + if (aStatus != ABI::Windows::Foundation::AsyncStatus::Completed) { + if (mCallback) + mCallback->Done(nsIFilePicker::returnCancel); + return S_OK; + } + + HRESULT hr; + ComPtr file; + hr = aFile->GetResults(file.GetAddressOf()); + // When the user cancels hr == S_OK and file is nullptr + if (FAILED(hr) || !file) { + if (mCallback) + mCallback->Done(nsIFilePicker::returnCancel); + return S_OK; + } + ComPtr storageItem; + hr = file.As(&storageItem); + if (FAILED(hr)) { + if (mCallback) + mCallback->Done(nsIFilePicker::returnCancel); + return S_OK; + } + + HSTRING path; + if (FAILED(storageItem->get_Path(&path))) { + if (mCallback) + mCallback->Done(nsIFilePicker::returnCancel); + return S_OK; + } + WindowsDuplicateString(path, mFilePath.GetAddressOf()); + WindowsDeleteString(path); + + if (mCallback) { + mCallback->Done(nsIFilePicker::returnOK); + } + return S_OK; +} + +HRESULT nsMetroFilePicker::OnPickMultipleFiles(IAsyncOperation*>* aFileList, + AsyncStatus aStatus) +{ + if (aStatus != ABI::Windows::Foundation::AsyncStatus::Completed) { + if (mCallback) + mCallback->Done(nsIFilePicker::returnCancel); + return S_OK; + } + + HRESULT hr; + ComPtr> view; + hr = aFileList->GetResults(view.GetAddressOf()); + if (FAILED(hr)) { + if (mCallback) + mCallback->Done(nsIFilePicker::returnCancel); + return S_OK; + } + + unsigned int length; + view->get_Size(&length); + for (unsigned int idx = 0; idx < length; idx++) { + ComPtr file; + hr = view->GetAt(idx, file.GetAddressOf()); + if (FAILED(hr)) { + continue; + } + + ComPtr storageItem; + hr = file.As(&storageItem); + if (FAILED(hr)) { + continue; + } + + HSTRING path; + if (SUCCEEDED(storageItem->get_Path(&path))) { + nsCOMPtr file = + do_CreateInstance("@mozilla.org/file/local;1"); + unsigned int tmp; + if (NS_SUCCEEDED(file->InitWithPath( + nsAutoString(WindowsGetStringRawBuffer(path, &tmp))))) { + mFiles.AppendObject(file); + } + } + WindowsDeleteString(path); + } + + if (mCallback) { + mCallback->Done(nsIFilePicker::returnOK); + } + return S_OK; +} + +NS_IMETHODIMP +nsMetroFilePicker::Open(nsIFilePickerShownCallback *aCallback) +{ + HRESULT hr; + // Capture a reference to the callback which we'll also pass into the + // closure to ensure it's not freed. + mCallback = aCallback; + + // The filepicker cannot open when in snapped view, try to unsnapp + // before showing the filepicker. + ApplicationViewState viewState; + MetroUtils::GetViewState(viewState); + if (viewState == ApplicationViewState::ApplicationViewState_Snapped) { + bool unsnapped = SUCCEEDED(MetroUtils::TryUnsnap()); + NS_ENSURE_TRUE(unsnapped, NS_ERROR_FAILURE); + } + + switch(mMode) { + case nsIFilePicker::modeOpen: { + NS_ENSURE_ARG_POINTER(mFileOpenPicker); + + // Initiate the file picker operation + ComPtr> asyncOperation; + hr = mFileOpenPicker->PickSingleFileAsync(asyncOperation.GetAddressOf()); + AssertRetHRESULT(hr, NS_ERROR_FAILURE); + + // Subscribe to the completed event + ComPtr> + completedHandler(Callback>( + this, &nsMetroFilePicker::OnPickSingleFile)); + hr = asyncOperation->put_Completed(completedHandler.Get()); + AssertRetHRESULT(hr, NS_ERROR_UNEXPECTED); + break; + } + + case nsIFilePicker::modeOpenMultiple: { + NS_ENSURE_ARG_POINTER(mFileOpenPicker); + + typedef IVectorView StorageTemplate; + typedef IAsyncOperation AsyncCallbackTemplate; + typedef IAsyncOperationCompletedHandler HandlerTemplate; + + // Initiate the file picker operation + ComPtr asyncOperation; + hr = mFileOpenPicker->PickMultipleFilesAsync(asyncOperation.GetAddressOf()); + AssertRetHRESULT(hr, NS_ERROR_FAILURE); + + // Subscribe to the completed event + ComPtr completedHandler(Callback( + this, &nsMetroFilePicker::OnPickMultipleFiles)); + hr = asyncOperation->put_Completed(completedHandler.Get()); + AssertRetHRESULT(hr, NS_ERROR_UNEXPECTED); + break; + } + + case nsIFilePicker::modeSave: { + NS_ENSURE_ARG_POINTER(mFileSavePicker); + + // Set the default file name + mFileSavePicker->put_SuggestedFileName(HStringReference(mDefaultFilename.BeginReading()).Get()); + + // Set the default file extension + if (mDefaultExtension.Length() > 0) { + nsAutoString defaultFileExtension(mDefaultExtension); + + // Touch up the extansion format platform hands us. + if (defaultFileExtension[0] == L'*') { + defaultFileExtension.Cut(0, 1); + } else if (defaultFileExtension[0] != L'.') { + defaultFileExtension.Insert(L".", 0); + } + + // Sometimes the default file extension is not passed in correctly, + // so we purposfully ignore failures here. + HString ext; + ext.Set(defaultFileExtension.BeginReading()); + hr = mFileSavePicker->put_DefaultFileExtension(ext.Get()); + NS_ASSERTION(SUCCEEDED(hr), "put_DefaultFileExtension failed, bad format for extension?"); + + // Due to a bug in the WinRT file picker, the first file extension in the + // list is always used when saving a file. So we explicitly make sure the + // default extension is the first one in the list here. + if (mFirstTitle.Get()) { + ComPtr*>> map; + mFileSavePicker->get_FileTypeChoices(map.GetAddressOf()); + if (map) { + boolean found = false; + unsigned int index; + map->HasKey(mFirstTitle.Get(), &found); + if (found) { + ComPtr> list; + if (SUCCEEDED(map->Lookup(mFirstTitle.Get(), list.GetAddressOf()))) { + HString ext; + ext.Set(defaultFileExtension.get()); + found = false; + list->IndexOf(HStringReference(defaultFileExtension.get()).Get(), &index, &found); + if (found) { + list->RemoveAt(index); + list->InsertAt(0, HStringReference(defaultFileExtension.get()).Get()); + } + } + } + } + } + } + + // Dispatch the async show operation + ComPtr> asyncOperation; + hr = mFileSavePicker->PickSaveFileAsync(asyncOperation.GetAddressOf()); + AssertRetHRESULT(hr, NS_ERROR_FAILURE); + + // Subscribe to the completed event + ComPtr> + completedHandler(Callback>( + this, &nsMetroFilePicker::OnPickSingleFile)); + hr = asyncOperation->put_Completed(completedHandler.Get()); + AssertRetHRESULT(hr, NS_ERROR_UNEXPECTED); + break; + } + + case modeGetFolder: + return NS_ERROR_NOT_IMPLEMENTED; + + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + return NS_OK; +} + +NS_IMETHODIMP +nsMetroFilePicker::GetFile(nsIFile **aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + *aFile = nullptr; + + if (WindowsIsStringEmpty(mFilePath.Get())) + return NS_OK; + + nsCOMPtr file(do_CreateInstance("@mozilla.org/file/local;1")); + NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); + unsigned int length; + file->InitWithPath(nsAutoString(mFilePath.GetRawBuffer(&length))); + NS_ADDREF(*aFile = file); + return NS_OK; +} + +NS_IMETHODIMP +nsMetroFilePicker::GetFileURL(nsIURI **aFileURL) +{ + *aFileURL = nullptr; + nsCOMPtr file; + nsresult rv = GetFile(getter_AddRefs(file)); + if (!file) + return rv; + + return NS_NewFileURI(aFileURL, file); +} + +NS_IMETHODIMP +nsMetroFilePicker::GetFiles(nsISimpleEnumerator **aFiles) +{ + NS_ENSURE_ARG_POINTER(aFiles); + return NS_NewArrayEnumerator(aFiles, mFiles); +} + +// Set the filter index +NS_IMETHODIMP +nsMetroFilePicker::GetFilterIndex(int32_t *aFilterIndex) +{ + // No associated concept with a Metro file picker + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMetroFilePicker::SetFilterIndex(int32_t aFilterIndex) +{ + // No associated concept with a Metro file picker + return NS_ERROR_NOT_IMPLEMENTED; +} + +// AFACT, it's up to use to supply the implementation of a vector list. +class MozHStringVector : public RuntimeClass> { + InspectableClass(L"MozHStringVector", TrustLevel::BaseTrust) + ~MozHStringVector() { + Clear(); + } + + // See IVector_impl in windows.foundation.collections.h +public: + STDMETHOD(GetAt)(unsigned aIndex, HSTRING* aString) { + if (aIndex >= mList.Length()) { + return E_INVALIDARG; + } + return WindowsDuplicateString(mList[aIndex], aString); + } + + STDMETHOD(get_Size)(unsigned int* aLength) { + if (!aLength) { + return E_INVALIDARG; + } + *aLength = mList.Length(); + return S_OK; + } + + STDMETHOD(Append)(HSTRING aString) { + HSTRING str; + if (FAILED(WindowsDuplicateString(aString, &str))) { + return E_INVALIDARG; + } + mList.AppendElement(str); + return S_OK; + } + + STDMETHOD(Clear)() { + int length = mList.Length(); + for (int idx = 0; idx < length; idx++) + WindowsDeleteString(mList[idx]); + mList.Clear(); + return S_OK; + } + + // interfaces picker code doesn't seem to need + STDMETHOD(GetView)(IVectorView **aView) { return E_NOTIMPL; } + STDMETHOD(IndexOf)(HSTRING aValue, unsigned *aIndex, boolean *found) { return E_NOTIMPL; } + STDMETHOD(SetAt)(unsigned aIndex, HSTRING aString) { return E_NOTIMPL; } + STDMETHOD(InsertAt)(unsigned aIndex, HSTRING aString) { return E_NOTIMPL; } + STDMETHOD(RemoveAt)(unsigned aIndex) { return E_NOTIMPL; } + STDMETHOD(RemoveAtEnd)() { return E_NOTIMPL; } + +private: + nsTArray mList; +}; + +nsresult +nsMetroFilePicker::ParseFiltersIntoVector(ComPtr>& aVector, + const nsAString& aFilter, + bool aAllowAll) +{ + const char16_t *beg = aFilter.BeginReading(); + const char16_t *end = aFilter.EndReading(); + for (const char16_t *cur = beg, *fileTypeStart = beg; cur <= end; ++cur) { + // Start of a a filetype, example: *.png + if (cur == end || char16_t(' ') == *cur) { + int32_t startPos = fileTypeStart - beg; + int32_t endPos = cur - fileTypeStart - (cur == end ? 0 : 1); + const nsAString& fileType = Substring(aFilter, + startPos, + endPos); + // There is no way to say show all files in Metro save file picker, so + // just use .data if * or *.* is specified. + if (fileType.IsEmpty() || + fileType.Equals(L"*") || + fileType.Equals(L"*.*")) { + HString str; + if (aAllowAll) { + str.Set(L"*"); + aVector->Append(str.Get()); + } else { + str.Set(L".data"); + aVector->Append(str.Get()); + } + } else { + nsAutoString filter(fileType); + if (filter[0] == L'*') { + filter.Cut(0, 1); + } else if (filter[0] != L'.') { + filter.Insert(L".", 0); + } + HString str; + str.Set(filter.BeginReading()); + aVector->Append(str.Get()); + } + + fileTypeStart = cur + 1; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMetroFilePicker::AppendFilter(const nsAString& aTitle, + const nsAString& aFilter) +{ + HRESULT hr; + switch(mMode) { + case nsIFilePicker::modeOpen: + case nsIFilePicker::modeOpenMultiple: { + NS_ENSURE_ARG_POINTER(mFileOpenPicker); + ComPtr> list; + mFileOpenPicker->get_FileTypeFilter(list.GetAddressOf()); + nsresult rv = ParseFiltersIntoVector(list, aFilter, true); + NS_ENSURE_SUCCESS(rv, rv); + } + + case nsIFilePicker::modeSave: { + NS_ENSURE_ARG_POINTER(mFileSavePicker); + + ComPtr*>> map; + hr = mFileSavePicker->get_FileTypeChoices(map.GetAddressOf()); + AssertRetHRESULT(hr, NS_ERROR_FAILURE); + + HString key; + key.Set(aTitle.BeginReading()); + + ComPtr> saveTypes; + saveTypes = Make(); + nsresult rv = ParseFiltersIntoVector(saveTypes, aFilter, false); + NS_ENSURE_SUCCESS(rv, rv); + + if (WindowsIsStringEmpty(mFirstTitle.Get())) { + mFirstTitle.Set(key.Get()); + } + + boolean replaced; + map->Insert(key.Get(), saveTypes.Get(), &replaced); + } + break; + + default: + return NS_ERROR_FAILURE; + } + return NS_OK; +} +