summaryrefslogtreecommitdiffstats
path: root/toolkit/components/aboutthirdparty/AboutThirdParty_TestMethods.cpp
blob: b52ba49eac8450bde8fdfb79bc8541ea8dd2dbfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "AboutThirdParty.h"

#include <windows.h>
#include <shlobj.h>
#include <shobjidl.h>

namespace {

class FileDialogEventsForTesting final : public IFileDialogEvents {
  mozilla::Atomic<uint32_t> mRefCnt;
  const nsString mTargetName;
  RefPtr<IShellItem> mTargetDir;

  ~FileDialogEventsForTesting() = default;

 public:
  FileDialogEventsForTesting(const nsAString& aTargetName,
                             IShellItem* aTargetDir)
      : mRefCnt(0),
        mTargetName(PromiseFlatString(aTargetName)),
        mTargetDir(aTargetDir) {}

  // IUnknown

  STDMETHODIMP QueryInterface(REFIID aRefIID, void** aResult) {
    if (!aResult) {
      return E_INVALIDARG;
    }

    if (aRefIID == IID_IFileDialogEvents) {
      RefPtr ref(static_cast<IFileDialogEvents*>(this));
      ref.forget(aResult);
      return S_OK;
    }

    return E_NOINTERFACE;
  }

  STDMETHODIMP_(ULONG) AddRef() { return ++mRefCnt; }

  STDMETHODIMP_(ULONG) Release() {
    ULONG result = --mRefCnt;
    if (!result) {
      delete this;
    }
    return result;
  }

  // IFileDialogEvents

  STDMETHODIMP OnFileOk(IFileDialog*) { return E_NOTIMPL; }
  STDMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return E_NOTIMPL; }
  STDMETHODIMP OnShareViolation(IFileDialog*, IShellItem*,
                                FDE_SHAREVIOLATION_RESPONSE*) {
    return E_NOTIMPL;
  }
  STDMETHODIMP OnTypeChange(IFileDialog*) { return E_NOTIMPL; }
  STDMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) {
    return E_NOTIMPL;
  }
  STDMETHODIMP OnFolderChange(IFileDialog*) { return E_NOTIMPL; }
  STDMETHODIMP OnSelectionChange(IFileDialog* aDialog) {
    if (::GetModuleHandleW(mTargetName.get())) {
      aDialog->Close(S_OK);
    } else {
      // This sends a notification which is processed asynchronously.  Calling
      // SetFolder from OnSelectionChange gives the main thread some cycles to
      // process other window messages, while calling SetFolder from
      // OnFolderChange causes freeze.  Thus we can safely wait until a common
      // dialog loads a shell extension without blocking UI.
      aDialog->SetFolder(mTargetDir);
    }

    return E_NOTIMPL;
  }
};

}  // anonymous namespace

namespace mozilla {

NS_IMETHODIMP AboutThirdParty::OpenAndCloseFileDialogForTesting(
    const nsAString& aModuleName, const nsAString& aInitialDir,
    const nsAString& aFilter) {
  // Notify the shell of a new icon handler which should have been registered
  // by the test script.
  ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);

  RefPtr<IFileOpenDialog> dialog;
  if (FAILED(::CoCreateInstance(CLSID_FileOpenDialog, nullptr,
                                CLSCTX_INPROC_SERVER, IID_IFileOpenDialog,
                                getter_AddRefs(dialog)))) {
    return NS_ERROR_UNEXPECTED;
  }

  const nsString& filter = PromiseFlatString(aFilter);
  COMDLG_FILTERSPEC fileFilter = {L"Test Target", filter.get()};
  if (FAILED(dialog->SetFileTypes(1, &fileFilter))) {
    return NS_ERROR_UNEXPECTED;
  }

  RefPtr<IShellItem> folder;
  if (FAILED(::SHCreateItemFromParsingName(PromiseFlatString(aInitialDir).get(),
                                           nullptr, IID_IShellItem,
                                           getter_AddRefs(folder)))) {
    return NS_ERROR_UNEXPECTED;
  }

  // Need to send a first notification outside FileDialogEventsForTesting.
  if (FAILED(dialog->SetFolder(folder))) {
    return NS_ERROR_UNEXPECTED;
  }

  RefPtr events(new FileDialogEventsForTesting(aModuleName, folder));

  DWORD cookie;
  if (FAILED(dialog->Advise(events, &cookie))) {
    return NS_ERROR_UNEXPECTED;
  }

  if (FAILED(dialog->Show(nullptr))) {
    return NS_ERROR_UNEXPECTED;
  }

  if (FAILED(dialog->Unadvise(cookie))) {
    return NS_ERROR_UNEXPECTED;
  }

  return NS_OK;
}

}  // namespace mozilla