Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/widget/windows/tests/gtest/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 35 kB image not shown  

Quelle  TestJumpListBuilder.cpp   Sprache: C

 
/* 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 <objectarray.h>
#include <shobjidl.h>
#include <windows.h>
#include <string.h>
#include <propvarutil.h>
#include <propkey.h>

#ifdef __MINGW32__
// MinGW-w64 headers are missing PropVariantToString.
#  include <propsys.h>
PSSTDAPI PropVariantToString(REFPROPVARIANT propvar, PWSTR psz, UINT cch);
#endif

#include "gtest/gtest.h"
#include "gmock/gmock.h"

#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/WindowsJumpListShortcutDescriptionBinding.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "JumpListBuilder.h"

using namespace mozilla;
using namespace testing;
using mozilla::dom::AutoJSAPI;
using mozilla::dom::Promise;
using mozilla::dom::PromiseNativeHandler;
using mozilla::dom::ToJSValue;
using mozilla::dom::WindowsJumpListShortcutDescription;
using mozilla::widget::JumpListBackend;
using mozilla::widget::JumpListBuilder;

/**
 * GMock matcher that ensures that two LPCWSTRs match.
 */

MATCHER_P(LPCWSTREq, value, "The equivalent of StrEq for LPCWSTRs") {
  return (wcscmp(arg, value)) == 0;
}

/**
 * GMock matcher that ensures that a IObjectArray* contains nsIShellLinkW's
 * that match an equivalent set of nsTArray<WindowsJumpListShortcutDescriptions>
 */

MATCHER_P(ShellLinksEq, descs,
          "Comparing generated IShellLinkW with "
          "WindowsJumpListShortcutDescription definitions") {
  uint32_t count = 0;
  HRESULT hr = arg->GetCount(&count);
  if (FAILED(hr) || count != descs->Length()) {
    return false;
  }

  for (uint32_t i = 0; i < descs->Length(); ++i) {
    RefPtr<IShellLinkW> link;
    if (FAILED(arg->GetAt(i, IID_IShellLinkW,
                          static_cast<void**>(getter_AddRefs(link))))) {
      return false;
    }

    if (!link) {
      return false;
    }

    const WindowsJumpListShortcutDescription& desc = descs->ElementAt(i);

    // We'll now compare each member of the WindowsJumpListShortcutDescription
    // with what is stored in the IShellLink.

    // WindowsJumpListShortcutDescription.title
    IPropertyStore* propStore = nullptr;
    hr = link->QueryInterface(IID_IPropertyStore, (LPVOID*)&propStore);
    if (FAILED(hr)) {
      return false;
    }

    PROPVARIANT pv;
    hr = propStore->GetValue(PKEY_Title, &pv);
    if (FAILED(hr)) {
      return false;
    }

    wchar_t title[PKEYSTR_MAX];
    hr = PropVariantToString(pv, title, PKEYSTR_MAX);
    if (FAILED(hr)) {
      return false;
    }

    if (!desc.mTitle.Equals(title)) {
      return false;
    }

    // WindowsJumpListShortcutDescription.path
    wchar_t pathBuf[MAX_PATH];
    hr = link->GetPath(pathBuf, MAX_PATH, nullptr, SLGP_SHORTPATH);
    if (FAILED(hr)) {
      return false;
    }

    if (!desc.mPath.Equals(pathBuf)) {
      return false;
    }

    // WindowsJumpListShortcutDescription.arguments (optional)
    wchar_t argsBuf[MAX_PATH];
    hr = link->GetArguments(argsBuf, MAX_PATH);
    if (FAILED(hr)) {
      return false;
    }

    if (desc.mArguments.WasPassed()) {
      if (!desc.mArguments.Value().Equals(argsBuf)) {
        return false;
      }
    } else {
      // Otherwise, the arguments should be empty.
      if (wcsnlen(argsBuf, MAX_PATH) != 0) {
        return false;
      }
    }

    // WindowsJumpListShortcutDescription.description
    wchar_t descBuf[MAX_PATH];
    hr = link->GetDescription(descBuf, MAX_PATH);
    if (FAILED(hr)) {
      return false;
    }

    if (!desc.mDescription.Equals(descBuf)) {
      return false;
    }

    // WindowsJumpListShortcutDescription.iconPath and
    // WindowsJumpListShortcutDescription.fallbackIconIndex
    int iconIdx = 0;
    wchar_t iconPathBuf[MAX_PATH];
    hr = link->GetIconLocation(iconPathBuf, MAX_PATH, &iconIdx);
    if (FAILED(hr)) {
      return false;
    }

    if (desc.mIconPath.WasPassed() && !desc.mIconPath.Value().IsEmpty()) {
      // If the WindowsJumpListShortcutDescription supplied an iconPath,
      // then it should match iconPathBuf and have an icon index of 0.
      if (!desc.mIconPath.Value().Equals(iconPathBuf) || iconIdx != 0) {
        return false;
      }
    } else {
      // Otherwise, the iconPathBuf should equal the
      // WindowsJumpListShortcutDescription path, and the iconIdx should match
      // the fallbackIconIndex.
      if (!desc.mPath.Equals(iconPathBuf) ||
          desc.mFallbackIconIndex != iconIdx) {
        return false;
      }
    }
  }

  return true;
}

/**
 * This is a helper class that allows our tests to wait for a native DOM Promise
 * to resolve, and get the JS::Value that the Promise resolves with. This is
 * expected to run on the main thread.
 */

class WaitForResolver : public PromiseNativeHandler {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WaitForResolver, override)

  NS_IMETHODIMP QueryInterface(REFNSIID aIID, void** aInstancePtr) override {
    nsresult rv = NS_ERROR_UNEXPECTED;
    NS_INTERFACE_TABLE0(WaitForResolver)

    return rv;
  }

  WaitForResolver() : mIsDone(false) {}

  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
                        ErrorResult& aError) override {
    mResult = aValue;
    mIsDone = true;
  }

  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
                        ErrorResult& aError) override {
    ASSERT_TRUE(false);  // Should never reach here.
  }

  /**
   * Spins a nested event loop and blocks until the Promise has resolved.
   */

  void SpinUntilResolved() {
    SpinEventLoopUntil("WaitForResolver::SpinUntilResolved"_ns,
                       [&]() { return mIsDone; });
  }

  /**
   * Spins a nested event loop and blocks until the Promise has resolved,
   * after which the JS::Value that the Promise resolves with is returned via
   * the aRetval outparam.
   *
   * @param {JS::MutableHandle<JS::Value>} aRetval
   *   The outparam for the JS::Value that the Promise resolves with.
   */

  void SpinUntilResolvedWithResult(JS::MutableHandle<JS::Value> aRetval) {
    SpinEventLoopUntil("WaitForResolver::SpinUntilResolved"_ns,
                       [&]() { return mIsDone; });
    aRetval.set(mResult);
  }

 private:
  virtual ~WaitForResolver() = default;

  JS::Heap<JS::Value> mResult;
  bool mIsDone;
};

/**
 * An implementation of JumpListBackend that is instrumented using the GMock
 * framework to record calls. Unlike the NativeJumpListBackend, this backend
 * is expected to be instantiated on the main thread and passed as an argument
 * to the JumpListBuilder's worker thread. Testers should wait for the methods
 * that call these functions to resolve their Promises before checking the
 * recorded values.
 */

class TestingJumpListBackend : public JumpListBackend {
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JumpListBackend, override)

  TestingJumpListBackend() : mMonitor("TestingJumpListBackend::mMonitor") {}

  virtual bool IsAvailable() override { return true; }

  MOCK_METHOD(HRESULT, SetAppID, (LPCWSTR));
  MOCK_METHOD(HRESULT, BeginList, (UINT*, REFIID, void**));
  MOCK_METHOD(HRESULT, AddUserTasks, (IObjectArray*));
  MOCK_METHOD(HRESULT, AppendCategory, (LPCWSTR, IObjectArray*));
  MOCK_METHOD(HRESULT, CommitList, ());
  MOCK_METHOD(HRESULT, AbortList, ());
  MOCK_METHOD(HRESULT, DeleteList, (LPCWSTR));

  virtual HRESULT AppendKnownCategory(KNOWNDESTCATEGORY category) override {
    return 0;
  }

  // In one case (construction), an operation occurs off of the main thread that
  // we must wait for without an associated Promise.
  Monitor& GetMonitor() { return mMonitor; }

 protected:
  virtual ~TestingJumpListBackend() override {};

 private:
  Monitor mMonitor;
};

/**
 * A helper function that creates some fake WindowsJumpListShortcutDescription
 * objects as well as JS::Value representations of those objects. These are
 * returned to the caller through outparams.
 *
 * @param {JSContext*} aCx
 *   The current JSContext in the execution environment.
 * @param {uint32_t} howMany
 *   The number of WindowsJumpListShortcutDescriptions to generate.
 * @param {boolean} longDescription
 *   True if the description should be greater than MAX_PATH (260 characters).
 * @param {nsTArray<WindowsJumpListShortcutDescription>&} aArray
 *   The outparam for the array of generated
 * WindowsJumpListShortcutDescriptions.
 * @param {JS::Handle<JSObject*>} aJSArrayObj
 *   The outparam for the array of JS::Value's representing the generated
 *   WindowsJumpListShortcutDescriptions.
 */

void GenerateWindowsJumpListShortcutDescriptions(
    JSContext* aCx, uint32_t howMany, bool longDescription,
    nsTArray<WindowsJumpListShortcutDescription>& aArray,
    JS::Handle<JSObject*> aJSArrayObj) {
  for (uint32_t i = 0; i < howMany; ++i) {
    WindowsJumpListShortcutDescription desc;
    nsAutoString title(u"Test Task #");
    title.AppendInt(i);
    desc.mTitle = title;

    nsAutoString path(u"C:\\Some\\Test\\Path.exe");
    desc.mPath = path;
    nsAutoString description;

    if (longDescription) {
      description.AppendPrintf(
          "For item #%i, this is a very very very very VERY VERY very very "
          "very very very very very very very very very very VERY VERY very "
          "very very very very very very very very very very very VERY VERY "
          "very very very very very very very very very very very very VERY "
          "VERY very very very very very very very very very very very very "
          "VERY VERY very very very very very very very very very very very "
          "very VERY VERY very very very very very very very very long test "
          "description for an item",
          i);
    } else {
      description.AppendPrintf("This is a test description for an item #%i", i);
    }

    desc.mDescription = description;
    desc.mFallbackIconIndex = 0;

    if (!(i % 2)) {
      nsAutoString arguments(u"-arg1 -arg2 -arg3");
      desc.mArguments.Construct(arguments);
      nsAutoString iconPath(u"C:\\Some\\icon.png");
      desc.mIconPath.Construct(iconPath);
    }

    aArray.AppendElement(desc);
    JS::Rooted<JS::Value> descJSValue(aCx);
    ASSERT_TRUE(ToJSValue(aCx, desc, &descJSValue));

    MOZ_ALWAYS_TRUE(JS_SetElement(aCx, aJSArrayObj, i, descJSValue));
  }
}

/**
 * Tests construction and that the application ID is properly passed to the
 * backend.
 */

TEST(JumpListBuilder, Construction)
{
  RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
      new StrictMock<TestingJumpListBackend>();
  ASSERT_TRUE(testBackend);

  nsAutoString aumid(u"TestApplicationID");
  LPCWSTR passedID = aumid.get();
  // Construction of our class (or any class of that matter) does not return a
  // Promise that we can wait on to ensure that the background thread got the
  // right information. We therefore use a monitor on the testing backend as
  // well as an EXPECT_CALL to block execution of the test until the background
  // work has completed.
  Monitor& mon = testBackend->GetMonitor();
  MonitorAutoLock lock(mon);
  EXPECT_CALL(*testBackend, SetAppID(LPCWSTREq(passedID))).WillOnce([&mon] {
    MonitorAutoLock lock(mon);
    mon.Notify();
    return S_OK;
  });

  nsCOMPtr<nsIJumpListBuilder> builder =
      new JumpListBuilder(aumid, testBackend);
  ASSERT_TRUE(builder);

  // This is the amount of time that we will wait for the background thread to
  // respond before considering it a timeout failure.
  const int kWaitTimeoutMs = 5000;

  ASSERT_TRUE(mon.Wait(TimeDuration::FromMilliseconds(kWaitTimeoutMs)) !=
              CVStatus::Timeout);
}

/**
 * Tests calling CheckForRemovals and receiving a series of removed jump list
 * entries. Calling CheckForRemovals should call the following methods on the
 * backend, in order:
 *
 * - SetAppID
 * - AbortList
 * - BeginList
 * - AbortList
 */

TEST(JumpListBuilder, CheckForRemovals)
{
  RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
      new StrictMock<TestingJumpListBackend>();
  nsAutoString aumid(u"TestApplicationID");
  // We set up this expectation here because SetAppID will be called soon
  // after construction of the JumpListBuilder via the background thread.
  EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);

  nsCOMPtr<nsIJumpListBuilder> builder =
      new JumpListBuilder(aumid, testBackend);
  ASSERT_TRUE(builder);

  EXPECT_CALL(*testBackend, AbortList()).Times(2);

  // Let's prepare BeginList to return a two entry collection of IShellLinks.
  // The first IShellLink will have the URL be https://example.com, the second
  // will have the URL be https://mozilla.org.
  EXPECT_CALL(*testBackend, BeginList)
      .WillOnce([](UINT* pcMinSlots, REFIID riid, void** ppv) {
        RefPtr<IObjectCollection> collection;
        HRESULT hr = CoCreateInstance(
            CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER,
            IID_IObjectCollection, getter_AddRefs(collection));
        MOZ_RELEASE_ASSERT(SUCCEEDED(hr));

        RefPtr<IShellLinkW> link;
        hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
                              IID_IShellLinkW, getter_AddRefs(link));
        MOZ_RELEASE_ASSERT(SUCCEEDED(hr));

        nsAutoString firstLinkHref(u"https://example.com"_ns);
        link->SetArguments(firstLinkHref.get());

        nsAutoString appPath(u"C:\\Tmp\\firefox.exe"_ns);
        link->SetIconLocation(appPath.get(), 0);

        collection->AddObject(link);

        // Let's re-use the same IShellLink, but change the URL to add our
        // second entry. The values of the IShellLink are ultimately copied
        // over to the items being added to the collection.
        nsAutoString secondLinkHref(u"https://mozilla.org"_ns);
        link->SetArguments(secondLinkHref.get());
        collection->AddObject(link);

        RefPtr<IObjectArray> pArray;
        hr = collection->QueryInterface(IID_IObjectArray,
                                        getter_AddRefs(pArray));
        MOZ_RELEASE_ASSERT(SUCCEEDED(hr));

        *ppv = static_cast<IObjectArray*>(pArray);
        (static_cast<IUnknown*>(*ppv))->AddRef();

        // This is the default value to return, according to
        // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-icustomdestinationlist-beginlist
        *pcMinSlots = 10;

        return S_OK;
      });

  AutoJSAPI jsapi;
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
  JSContext* cx = jsapi.cx();
  RefPtr<Promise> promise;
  nsresult rv = builder->CheckForRemovals(cx, getter_AddRefs(promise));
  ASSERT_TRUE(NS_SUCCEEDED(rv));
  ASSERT_TRUE(promise);

  RefPtr<WaitForResolver> resolver = new WaitForResolver();
  promise->AppendNativeHandler(resolver);
  JS::Rooted<JS::Value> result(cx);
  resolver->SpinUntilResolvedWithResult(&result);

  ASSERT_TRUE(result.isObject());
  JS::Rooted<JSObject*> obj(cx, result.toObjectOrNull());

  bool isArray;
  ASSERT_TRUE(JS::IsArrayObject(cx, obj, &isArray));
  ASSERT_TRUE(isArray);

  // We should expect to see 2 URL strings returned in the array.
  uint32_t length = 0;
  ASSERT_TRUE(JS::GetArrayLength(cx, obj, &length));
  ASSERT_EQ(length, 2U);

  // The first one should be https://example.com
  JS::Rooted<JS::Value> firstURLValue(cx);
  ASSERT_TRUE(JS_GetElement(cx, obj, 0, &firstURLValue));
  JS::Rooted<JSString*> firstURLJSString(cx, firstURLValue.toString());
  nsAutoJSString firstURLAutoString;
  ASSERT_TRUE(firstURLAutoString.init(cx, firstURLJSString));

  ASSERT_TRUE(firstURLAutoString.EqualsLiteral("https://example.com"));

  // The second one should be https://mozilla.org
  JS::Rooted<JS::Value> secondURLValue(cx);
  ASSERT_TRUE(JS_GetElement(cx, obj, 1, &secondURLValue));
  JS::Rooted<JSString*> secondURLJSString(cx, secondURLValue.toString());
  nsAutoJSString secondURLAutoString;
  ASSERT_TRUE(secondURLAutoString.init(cx, secondURLJSString));

  ASSERT_TRUE(secondURLAutoString.EqualsLiteral("https://mozilla.org"));
}

/**
 * Tests calling CheckForRemovals and receiving a jump list entry with a very
 * long URL doesn't result in JumpListBuilder truncating the URL before handing
 * it back to the caller. Expects the following calls in order:
 *
 * - SetAppID
 * - AbortList
 * - BeginList
 * - AbortList
 */

TEST(JumpListBuilder, CheckForRemovalsLongURL)
{
  RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
      new StrictMock<TestingJumpListBackend>();
  nsAutoString aumid(u"TestApplicationID");
  // We set up this expectation here because SetAppID will be called soon
  // after construction of the JumpListBuilder via the background thread.
  EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);

  nsCOMPtr<nsIJumpListBuilder> builder =
      new JumpListBuilder(aumid, testBackend);
  ASSERT_TRUE(builder);

  EXPECT_CALL(*testBackend, AbortList()).Times(2);

  constexpr static const nsLiteralString veryLongHref(
      u"https://example.verylongurl.test/first/second/third/fourth/fifth/"
      "sixth/seventh/eighth/ninth/tenth/eleventh/twelfth/thirteenth/"
      "fourteenth/fifteenth-path-item/some/more/junk/after/that/more/more/"
      "more/more/more/more/more/more/more/more/more/more/more/more/more/more/"
      "more/more/more/more/more/more/more/more/more/more/more"_ns);
  // This test ensures that URLs longer than MAX_PATH do not get truncated by
  // JumpListBuilder or one of its utilities, so we must ensure that the static
  // URL we just defined is actually longer than MAX_PATH.
  static_assert(veryLongHref.Length() > MAX_PATH);

  // Let's prepare BeginList to return a one entry collection of IShellLinks.
  // The IShellLink will have the URL be very long - over MAX_PATH characters.
  EXPECT_CALL(*testBackend, BeginList)
      .WillOnce([](UINT* pcMinSlots, REFIID riid, void** ppv) {
        RefPtr<IObjectCollection> collection;
        HRESULT hr = CoCreateInstance(
            CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER,
            IID_IObjectCollection, getter_AddRefs(collection));
        MOZ_RELEASE_ASSERT(SUCCEEDED(hr));

        RefPtr<IShellLinkW> link;
        hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
                              IID_IShellLinkW, getter_AddRefs(link));
        MOZ_RELEASE_ASSERT(SUCCEEDED(hr));

        link->SetArguments(veryLongHref.get());

        nsAutoString appPath(u"C:\\Tmp\\firefox.exe"_ns);
        link->SetIconLocation(appPath.get(), 0);

        collection->AddObject(link);

        RefPtr<IObjectArray> pArray;
        hr = collection->QueryInterface(IID_IObjectArray,
                                        getter_AddRefs(pArray));
        MOZ_RELEASE_ASSERT(SUCCEEDED(hr));

        *ppv = static_cast<IObjectArray*>(pArray);
        (static_cast<IUnknown*>(*ppv))->AddRef();

        // This is the default value to return, according to
        // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-icustomdestinationlist-beginlist
        *pcMinSlots = 10;

        return S_OK;
      });

  AutoJSAPI jsapi;
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
  JSContext* cx = jsapi.cx();
  RefPtr<Promise> promise;
  nsresult rv = builder->CheckForRemovals(cx, getter_AddRefs(promise));
  ASSERT_TRUE(NS_SUCCEEDED(rv));
  ASSERT_TRUE(promise);

  RefPtr<WaitForResolver> resolver = new WaitForResolver();
  promise->AppendNativeHandler(resolver);
  JS::Rooted<JS::Value> result(cx);
  resolver->SpinUntilResolvedWithResult(&result);

  ASSERT_TRUE(result.isObject());
  JS::Rooted<JSObject*> obj(cx, result.toObjectOrNull());

  bool isArray;
  ASSERT_TRUE(JS::IsArrayObject(cx, obj, &isArray));
  ASSERT_TRUE(isArray);

  // We should expect to see 1 URL string returned in the array.
  uint32_t length = 0;
  ASSERT_TRUE(JS::GetArrayLength(cx, obj, &length));
  ASSERT_EQ(length, 1U);

  // The URL should match veryLongHref
  JS::Rooted<JS::Value> returnedURLValue(cx);
  ASSERT_TRUE(JS_GetElement(cx, obj, 0, &returnedURLValue));
  JS::Rooted<JSString*> returnedURLValueJSString(cx,
                                                 returnedURLValue.toString());
  nsAutoJSString returnedURLValueAutoString;
  ASSERT_TRUE(returnedURLValueAutoString.init(cx, returnedURLValueJSString));

  ASSERT_TRUE(returnedURLValueAutoString.Equals(veryLongHref));
}

/**
 * Tests calling PopulateJumpList with empty arguments, which should call the
 * following methods on the backend, in order:
 *
 * - SetAppID
 * - AbortList
 * - BeginList
 * - CommitList
 *
 * This should result in an empty jump list for the user.
 */

TEST(JumpListBuilder, PopulateJumpListEmpty)
{
  RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
      new StrictMock<TestingJumpListBackend>();
  nsAutoString aumid(u"TestApplicationID");
  // We set up this expectation here because SetAppID will be called soon
  // after construction of the JumpListBuilder via the background thread.
  EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);

  nsCOMPtr<nsIJumpListBuilder> builder =
      new JumpListBuilder(aumid, testBackend);
  ASSERT_TRUE(builder);

  AutoJSAPI jsapi;
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
  JSContext* cx = jsapi.cx();
  RefPtr<Promise> promise;

  JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> taskDescsJSVal(cx, JS::ObjectValue(*taskDescsObj));

  nsAutoString customTitle(u"");

  JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj));

  EXPECT_CALL(*testBackend, AbortList()).Times(1);
  EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
  EXPECT_CALL(*testBackend, CommitList()).Times(1);
  EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);

  nsresult rv =
      builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal,
                                cx, getter_AddRefs(promise));
  ASSERT_TRUE(NS_SUCCEEDED(rv));
  ASSERT_TRUE(promise);

  RefPtr<WaitForResolver> resolver = new WaitForResolver();
  promise->AppendNativeHandler(resolver);
  JS::Rooted<JS::Value> result(cx);
  resolver->SpinUntilResolved();
}

/**
 * Tests calling PopulateJumpList with only tasks, and no custom items.
 * This should call the following methods on the backend, in order:
 *
 * - SetAppID
 * - AbortList
 * - BeginList
 * - AddUserTasks
 * - CommitList
 *
 * This should result in a jump list with just tasks shown to the user, and
 * no custom section.
 */

TEST(JumpListBuilder, PopulateJumpListOnlyTasks)
{
  RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
      new StrictMock<TestingJumpListBackend>();
  nsAutoString aumid(u"TestApplicationID");
  // We set up this expectation here because SetAppID will be called soon
  // after construction of the JumpListBuilder via the background thread.
  EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);

  nsCOMPtr<nsIJumpListBuilder> builder =
      new JumpListBuilder(aumid, testBackend);
  ASSERT_TRUE(builder);

  AutoJSAPI jsapi;
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
  JSContext* cx = jsapi.cx();
  RefPtr<Promise> promise;

  JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> taskDescsJSVal(cx, JS::ObjectValue(*taskDescsObj));
  nsTArray<WindowsJumpListShortcutDescription> taskDescs;
  GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, taskDescs,
                                              taskDescsObj);

  nsAutoString customTitle(u"");
  JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj));

  EXPECT_CALL(*testBackend, AbortList()).Times(1);
  EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
  EXPECT_CALL(*testBackend, AddUserTasks(ShellLinksEq(&taskDescs))).Times(1);

  EXPECT_CALL(*testBackend, AppendCategory(_, _)).Times(0);
  EXPECT_CALL(*testBackend, CommitList()).Times(1);
  EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);

  nsresult rv =
      builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal,
                                cx, getter_AddRefs(promise));
  ASSERT_TRUE(NS_SUCCEEDED(rv));
  ASSERT_TRUE(promise);

  RefPtr<WaitForResolver> resolver = new WaitForResolver();
  promise->AppendNativeHandler(resolver);
  JS::Rooted<JS::Value> result(cx);
  resolver->SpinUntilResolved();
}

/**
 * Tests calling PopulateJumpList with only custom items, and no tasks.
 * This should call the following methods on the backend, in order:
 *
 * - SetAppID
 * - AbortList
 * - BeginList
 * - AppendCategory
 * - CommitList
 *
 * This should result in a jump list with just custom items shown to the user,
 * and no tasks.
 */

TEST(JumpListBuilder, PopulateJumpListOnlyCustomItems)
{
  RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
      new StrictMock<TestingJumpListBackend>();
  nsAutoString aumid(u"TestApplicationID");
  // We set up this expectation here because SetAppID will be called soon
  // after construction of the JumpListBuilder via the background thread.
  EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);

  nsCOMPtr<nsIJumpListBuilder> builder =
      new JumpListBuilder(aumid, testBackend);
  ASSERT_TRUE(builder);

  AutoJSAPI jsapi;
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
  JSContext* cx = jsapi.cx();
  RefPtr<Promise> promise;

  JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> taskDescsJSVal(cx);
  taskDescsJSVal.setObject(*taskDescsObj);

  nsAutoString customTitle(u"My custom title");

  JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj));
  nsTArray<WindowsJumpListShortcutDescription> descs;
  GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, descs,
                                              customDescsObj);

  EXPECT_CALL(*testBackend, AbortList()).Times(1);
  EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
  EXPECT_CALL(*testBackend, AddUserTasks(_)).Times(0);

  EXPECT_CALL(*testBackend, AppendCategory(LPCWSTREq(customTitle.get()),
                                           ShellLinksEq(&descs)))
      .Times(1);
  EXPECT_CALL(*testBackend, CommitList()).Times(1);
  EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);

  nsresult rv =
      builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal,
                                cx, getter_AddRefs(promise));
  ASSERT_TRUE(NS_SUCCEEDED(rv));
  ASSERT_TRUE(promise);

  RefPtr<WaitForResolver> resolver = new WaitForResolver();
  promise->AppendNativeHandler(resolver);
  JS::Rooted<JS::Value> result(cx);
  resolver->SpinUntilResolved();
}

/**
 * Tests calling PopulateJumpList with tasks and custom items.
 * This should call the following methods on the backend, in order:
 *
 * - SetAppID
 * - AbortList
 * - BeginList
 * - AddUserTasks
 * - AppendCategory
 * - CommitList
 *
 * This should result in a jump list with both built-in tasks as well as
 * custom tasks with a custom label.
 */

TEST(JumpListBuilder, PopulateJumpList)
{
  RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
      new StrictMock<TestingJumpListBackend>();
  nsAutoString aumid(u"TestApplicationID");
  // We set up this expectation here because SetAppID will be called soon
  // after construction of the JumpListBuilder via the background thread.
  EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);

  nsCOMPtr<nsIJumpListBuilder> builder =
      new JumpListBuilder(aumid, testBackend);
  ASSERT_TRUE(builder);

  AutoJSAPI jsapi;
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
  JSContext* cx = jsapi.cx();
  RefPtr<Promise> promise;

  JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> taskDescsJSVal(cx, JS::ObjectValue(*taskDescsObj));
  nsTArray<WindowsJumpListShortcutDescription> taskDescs;
  GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, taskDescs,
                                              taskDescsObj);

  nsAutoString customTitle(u"My custom title");

  JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj));
  nsTArray<WindowsJumpListShortcutDescription> customDescs;
  GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, customDescs,
                                              customDescsObj);

  EXPECT_CALL(*testBackend, AbortList()).Times(1);
  EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
  EXPECT_CALL(*testBackend, AddUserTasks(ShellLinksEq(&taskDescs))).Times(1);

  EXPECT_CALL(*testBackend, AppendCategory(LPCWSTREq(customTitle.get()),
                                           ShellLinksEq(&customDescs)))
      .Times(1);
  EXPECT_CALL(*testBackend, CommitList()).Times(1);
  EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);

  nsresult rv =
      builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal,
                                cx, getter_AddRefs(promise));
  ASSERT_TRUE(NS_SUCCEEDED(rv));
  ASSERT_TRUE(promise);

  RefPtr<WaitForResolver> resolver = new WaitForResolver();
  promise->AppendNativeHandler(resolver);
  JS::Rooted<JS::Value> result(cx);
  resolver->SpinUntilResolved();
}

/**
 * Tests calling PopulateJumpList with tasks and custom items, but makes it so
 * that AppendCategory returns E_ACCESSDENIED, which can occur if Windows is
 * configured to not show recently opened items. The PopulateJumpList Promise
 * should still resolve.
 *
 * - SetAppID
 * - AbortList
 * - BeginList
 * - AddUserTasks
 * - AppendCategory
 * - CommitList
 *
 * This should result in a jump list with just built-in tasks.
 */

TEST(JumpListBuilder, PopulateJumpListNoOpenedItems)
{
  RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
      new StrictMock<TestingJumpListBackend>();
  nsAutoString aumid(u"TestApplicationID");
  // We set up this expectation here because SetAppID will be called soon
  // after construction of the JumpListBuilder via the background thread.
  EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);

  nsCOMPtr<nsIJumpListBuilder> builder =
      new JumpListBuilder(aumid, testBackend);
  ASSERT_TRUE(builder);

  AutoJSAPI jsapi;
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
  JSContext* cx = jsapi.cx();
  RefPtr<Promise> promise;

  JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> taskDescsJSVal(cx, JS::ObjectValue(*taskDescsObj));
  nsTArray<WindowsJumpListShortcutDescription> taskDescs;
  GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, taskDescs,
                                              taskDescsObj);

  nsAutoString customTitle(u"My custom title");

  JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj));
  nsTArray<WindowsJumpListShortcutDescription> customDescs;
  GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, customDescs,
                                              customDescsObj);

  EXPECT_CALL(*testBackend, AbortList()).Times(1);
  EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
  EXPECT_CALL(*testBackend, AddUserTasks(ShellLinksEq(&taskDescs))).Times(1);

  EXPECT_CALL(*testBackend, AppendCategory(LPCWSTREq(customTitle.get()),
                                           ShellLinksEq(&customDescs)))
      .WillOnce([] { return E_ACCESSDENIED; });

  EXPECT_CALL(*testBackend, CommitList()).Times(1);
  EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);

  nsresult rv =
      builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal,
                                cx, getter_AddRefs(promise));
  ASSERT_TRUE(NS_SUCCEEDED(rv));
  ASSERT_TRUE(promise);

  RefPtr<WaitForResolver> resolver = new WaitForResolver();
  promise->AppendNativeHandler(resolver);
  JS::Rooted<JS::Value> result(cx);
  resolver->SpinUntilResolved();
}

/**
 * Tests calling ClearJumpList calls the following:
 *
 * - SetAppID
 * - DeleteList (passing the aumid)
 *
 * This results in an empty jump list for the user.
 */

TEST(JumpListBuilder, ClearJumpList)
{
  RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
      new StrictMock<TestingJumpListBackend>();
  nsAutoString aumid(u"TestApplicationID");
  // We set up this expectation here because SetAppID will be called soon
  // after construction of the JumpListBuilder via the background thread.
  EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);

  nsCOMPtr<nsIJumpListBuilder> builder =
      new JumpListBuilder(aumid, testBackend);
  ASSERT_TRUE(builder);

  AutoJSAPI jsapi;
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
  JSContext* cx = jsapi.cx();
  RefPtr<Promise> promise;

  EXPECT_CALL(*testBackend, AbortList()).Times(0);
  EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(0);
  EXPECT_CALL(*testBackend, AddUserTasks(_)).Times(0);

  EXPECT_CALL(*testBackend, AppendCategory(_, _)).Times(0);
  EXPECT_CALL(*testBackend, CommitList()).Times(0);
  EXPECT_CALL(*testBackend, DeleteList(LPCWSTREq(aumid.get()))).Times(1);

  nsresult rv = builder->ClearJumpList(cx, getter_AddRefs(promise));
  ASSERT_TRUE(NS_SUCCEEDED(rv));
  ASSERT_TRUE(promise);

  RefPtr<WaitForResolver> resolver = new WaitForResolver();
  promise->AppendNativeHandler(resolver);
  JS::Rooted<JS::Value> result(cx);
  resolver->SpinUntilResolved();
}

/**
 * Test that a WindowsJumpListShortcutDescription with a description
 * longer than MAX_PATH gets truncated to MAX_PATH. This is because a
 * description longer than MAX_PATH will cause CommitList to fail.
 */

TEST(JumpListBuilder, TruncateDescription)
{
  RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
      new StrictMock<TestingJumpListBackend>();
  nsAutoString aumid(u"TestApplicationID");
  // We set up this expectation here because SetAppID will be called soon
  // after construction of the JumpListBuilder via the background thread.
  EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);

  nsCOMPtr<nsIJumpListBuilder> builder =
      new JumpListBuilder(aumid, testBackend);
  ASSERT_TRUE(builder);

  AutoJSAPI jsapi;
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
  JSContext* cx = jsapi.cx();
  RefPtr<Promise> promise;

  JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> taskDescsJSVal(cx, JS::ObjectValue(*taskDescsObj));
  nsTArray<WindowsJumpListShortcutDescription> taskDescs;
  GenerateWindowsJumpListShortcutDescriptions(cx, 2, true, taskDescs,
                                              taskDescsObj);

  nsAutoString customTitle(u"My custom title");

  JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0));
  JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj));
  nsTArray<WindowsJumpListShortcutDescription> customDescs;
  GenerateWindowsJumpListShortcutDescriptions(cx, 2, true, customDescs,
                                              customDescsObj);

  // We expect the long descriptions to be truncated to 260 characters, so
  // we'll truncate the descriptions here ourselves.
  for (auto& taskDesc : taskDescs) {
    taskDesc.mDescription.SetLength(MAX_PATH - 1);
  }
  for (auto& customDesc : customDescs) {
    customDesc.mDescription.SetLength(MAX_PATH - 1);
  }

  EXPECT_CALL(*testBackend, AbortList()).Times(1);
  EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
  EXPECT_CALL(*testBackend, AddUserTasks(ShellLinksEq(&taskDescs))).Times(1);

  EXPECT_CALL(*testBackend, AppendCategory(LPCWSTREq(customTitle.get()),
                                           ShellLinksEq(&customDescs)))
      .Times(1);
  EXPECT_CALL(*testBackend, CommitList()).Times(1);
  EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);

  nsresult rv =
      builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal,
                                cx, getter_AddRefs(promise));
  ASSERT_TRUE(NS_SUCCEEDED(rv));
  ASSERT_TRUE(promise);

  RefPtr<WaitForResolver> resolver = new WaitForResolver();
  promise->AppendNativeHandler(resolver);
  JS::Rooted<JS::Value> result(cx);
  resolver->SpinUntilResolved();
}

Messung V0.5
C=91 H=96 G=93

¤ Dauer der Verarbeitung: 0.15 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.