Global Metrics

path: .metrics.loc.sloc
old: 100.0
new: 622.0

path: .metrics.loc.lloc
old: 0.0
new: 218.0

path: .metrics.loc.ploc
old: 16.0
new: 520.0

path: .metrics.loc.cloc
old: 77.0
new: 14.0

path: .metrics.loc.blank
old: 7.0
new: 88.0

path: .metrics.halstead.length
old: 48.0
new: 2478.0

path: .metrics.halstead.vocabulary
old: 28.0
new: 342.0

path: .metrics.halstead.N2
old: 29.0
new: 1019.0

path: .metrics.halstead.bugs
old: 0.026959829530255077
new: 3.1324110985299867

path: .metrics.halstead.estimated_program_length
old: 115.65156546374811
new: 2742.6324883504067

path: .metrics.halstead.time
old: 40.40965006946971
new: 50608.9711047455

path: .metrics.halstead.effort
old: 727.3737012504548
new: 910961.479885419

path: .metrics.halstead.volume
old: 230.75303625876495
new: 20859.438531887252

path: .metrics.halstead.level
old: 0.31724137931034485
new: 0.02289826627412496

path: .metrics.halstead.n2
old: 23.0
new: 315.0

path: .metrics.halstead.N1
old: 19.0
new: 1459.0

path: .metrics.halstead.n1
old: 5.0
new: 27.0

path: .metrics.halstead.difficulty
old: 3.152173913043478
new: 43.67142857142857

path: .metrics.halstead.purity_ratio
old: 2.4094076138280855
new: 1.106792771731399

path: .metrics.cyclomatic.average
old: 1.0
new: 4.391304347826087

path: .metrics.cyclomatic.sum
old: 3.0
new: 101.0

path: .metrics.mi.mi_visual_studio
old: 39.421773814689445
new: 0.0

path: .metrics.mi.mi_original
old: 67.41123322311896
new: -8.160550925687886

path: .metrics.mi.mi_sei
old: 70.74552570694394
new: -65.673551502288

path: .metrics.nom.closures
old: 0.0
new: 5.0

path: .metrics.nom.functions
old: 0.0
new: 20.0

path: .metrics.nom.total
old: 0.0
new: 25.0

path: .metrics.nexits.average
old: null
new: 2.76

path: .metrics.nexits.sum
old: 0.0
new: 69.0

path: .metrics.nargs.sum
old: 0.0
new: 24.0

path: .metrics.nargs.average
old: null
new: 0.96

path: .metrics.cognitive.average
old: null
new: 4.32

path: .metrics.cognitive.sum
old: 0.0
new: 108.0

Spaces Data

Minimal test - lines (28, 622)

path: .spaces[0].metrics.nexits.average
old: null
new: 2.76

path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 69.0

path: .spaces[0].metrics.mi.mi_sei
old: null
new: -66.86494909171078

path: .spaces[0].metrics.mi.mi_original
old: null
new: -7.125296441161609

path: .spaces[0].metrics.mi.mi_visual_studio
old: null
new: 0.0

path: .spaces[0].metrics.loc.blank
old: 0.0
new: 85.0

path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 218.0

path: .spaces[0].metrics.loc.sloc
old: 1.0
new: 595.0

path: .spaces[0].metrics.loc.ploc
old: 1.0
new: 502.0

path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 8.0

path: .spaces[0].metrics.cognitive.average
old: null
new: 4.32

path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 108.0

path: .spaces[0].metrics.nom.total
old: 0.0
new: 25.0

path: .spaces[0].metrics.nom.closures
old: 0.0
new: 5.0

path: .spaces[0].metrics.nom.functions
old: 0.0
new: 20.0

path: .spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 100.0

path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 4.545454545454546

path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 24.0

path: .spaces[0].metrics.nargs.average
old: null
new: 0.96

path: .spaces[0].metrics.halstead.length
old: 1.0
new: 2460.0

path: .spaces[0].metrics.halstead.purity_ratio
old: null
new: 1.0439165615594477

path: .spaces[0].metrics.halstead.volume
old: 0.0
new: 20516.031007096175

path: .spaces[0].metrics.halstead.n2
old: 1.0
new: 297.0

path: .spaces[0].metrics.halstead.level
old: null
new: 0.02197802197802198

path: .spaces[0].metrics.halstead.N1
old: 0.0
new: 1459.0

path: .spaces[0].metrics.halstead.difficulty
old: 0.0
new: 45.5

path: .spaces[0].metrics.halstead.N2
old: 1.0
new: 1001.0

path: .spaces[0].metrics.halstead.vocabulary
old: 1.0
new: 324.0

path: .spaces[0].metrics.halstead.bugs
old: 0.0
new: 3.1838204961878684

path: .spaces[0].metrics.halstead.time
old: 0.0
new: 51859.96726793755

path: .spaces[0].metrics.halstead.n1
old: 0.0
new: 27.0

path: .spaces[0].metrics.halstead.estimated_program_length
old: null
new: 2568.0347414362413

path: .spaces[0].metrics.halstead.effort
old: 0.0
new: 933479.410822876

Code

namespace mozilla {
namespace widget {

typedef ABI::Windows::Foundation::ITypedEventHandler<
    ABI::Windows::UI::Notifications::ToastNotification*, IInspectable*>
    ToastActivationHandler;
typedef ABI::Windows::Foundation::ITypedEventHandler<
    ABI::Windows::UI::Notifications::ToastNotification*,
    ABI::Windows::UI::Notifications::ToastDismissedEventArgs*>
    ToastDismissedHandler;
typedef ABI::Windows::Foundation::ITypedEventHandler<
    ABI::Windows::UI::Notifications::ToastNotification*,
    ABI::Windows::UI::Notifications::ToastFailedEventArgs*>
    ToastFailedHandler;

using namespace ABI::Windows::Data::Xml::Dom;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::UI::Notifications;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace mozilla;

NS_IMPL_ISUPPORTS(ToastNotificationHandler, nsIAlertNotificationImageListener)

static bool SetNodeValueString(const nsString& aString, IXmlNode* node,
                               IXmlDocument* xml) {
  ComPtr inputText;
  if (NS_WARN_IF(FAILED(xml->CreateTextNode(
          HStringReference(static_cast(aString.get())).Get(),
          &inputText)))) {
    return false;
  }
  ComPtr inputTextNode;
  if (NS_WARN_IF(FAILED(inputText.As(&inputTextNode)))) {
    return false;
  }
  ComPtr appendedChild;
  if (NS_WARN_IF(
          FAILED(node->AppendChild(inputTextNode.Get(), &appendedChild)))) {
    return false;
  }
  return true;
}

static bool SetAttribute(IXmlElement* element, const HSTRING name,
                         const nsAString& value) {
  HSTRING valueStr = HStringReference(static_cast(
                                          PromiseFlatString(value).get()))
                         .Get();
  if (NS_WARN_IF(FAILED(element->SetAttribute(name, valueStr)))) {
    return false;
  }
  return true;
}

static bool AddActionNode(IXmlDocument* toastXml, IXmlNode* actionsNode,
                          const nsAString& actionTitle,
                          const nsAString& actionArgs) {
  ComPtr action;
  HRESULT hr =
      toastXml->CreateElement(HStringReference(L"action").Get(), &action);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  if (NS_WARN_IF(!SetAttribute(action.Get(), HStringReference(L"content").Get(),
                               actionTitle))) {
    return false;
  }

  if (NS_WARN_IF(!SetAttribute(
          action.Get(), HStringReference(L"arguments").Get(), actionArgs))) {
    return false;
  }
  if (NS_WARN_IF(!SetAttribute(action.Get(),
                               HStringReference(L"placement").Get(),
                               u"contextmenu"_ns))) {
    return false;
  }

  // Add  to 
  ComPtr actionNode;
  hr = action.As(&actionNode);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  ComPtr appendedChild;
  hr = actionsNode->AppendChild(actionNode.Get(), &appendedChild);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  return true;
}

static ComPtr
GetToastNotificationManagerStatics() {
  ComPtr toastNotificationManagerStatics;
  if (NS_WARN_IF(FAILED(GetActivationFactory(
          HStringReference(
              RuntimeClass_Windows_UI_Notifications_ToastNotificationManager)
              .Get(),
          &toastNotificationManagerStatics)))) {
    return nullptr;
  }

  return toastNotificationManagerStatics;
}

ToastNotificationHandler::~ToastNotificationHandler() {
  if (mImageRequest) {
    mImageRequest->Cancel(NS_BINDING_ABORTED);
    mImageRequest = nullptr;
  }

  if (mHasImage) {
    DebugOnly rv = mImageFile->Remove(false);
    NS_ASSERTION(NS_SUCCEEDED(rv), "Cannot remove temporary image file");
  }

  UnregisterHandler();
}

void ToastNotificationHandler::UnregisterHandler() {
  if (mNotification && mNotifier) {
    mNotification->remove_Dismissed(mDismissedToken);
    mNotification->remove_Activated(mActivatedToken);
    mNotification->remove_Failed(mFailedToken);
    mNotifier->Hide(mNotification.Get());
  }

  mNotification = nullptr;
  mNotifier = nullptr;

  SendFinished();
}

ComPtr ToastNotificationHandler::InitializeXmlForTemplate(
    ToastTemplateType templateType) {
  ComPtr toastNotificationManagerStatics =
      GetToastNotificationManagerStatics();

  ComPtr toastXml;
  toastNotificationManagerStatics->GetTemplateContent(templateType, &toastXml);

  return toastXml;
}

nsresult ToastNotificationHandler::InitAlertAsync(
    nsIAlertNotification* aAlert) {
  return aAlert->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr,
                           getter_AddRefs(mImageRequest));
}

bool ToastNotificationHandler::ShowAlert() {
  if (!mBackend->IsActiveHandler(mName, this)) {
    return true;
  }

  ToastTemplateType toastTemplate;
  if (mHostPort.IsEmpty()) {
    toastTemplate =
        mHasImage ? ToastTemplateType::ToastTemplateType_ToastImageAndText03
                  : ToastTemplateType::ToastTemplateType_ToastText03;
  } else {
    toastTemplate =
        mHasImage ? ToastTemplateType::ToastTemplateType_ToastImageAndText04
                  : ToastTemplateType::ToastTemplateType_ToastText04;
  }

  ComPtr toastXml = InitializeXmlForTemplate(toastTemplate);
  if (!toastXml) {
    return false;
  }

  HRESULT hr;

  if (mHasImage) {
    ComPtr toastImageElements;
    hr = toastXml->GetElementsByTagName(HStringReference(L"image").Get(),
                                        &toastImageElements);
    if (NS_WARN_IF(FAILED(hr))) {
      return false;
    }
    ComPtr imageNode;
    hr = toastImageElements->Item(0, &imageNode);
    if (NS_WARN_IF(FAILED(hr))) {
      return false;
    }
    ComPtr image;
    hr = imageNode.As(&image);
    if (NS_WARN_IF(FAILED(hr))) {
      return false;
    }
    if (NS_WARN_IF(!SetAttribute(image.Get(), HStringReference(L"src").Get(),
                                 mImageUri))) {
      return false;
    }
  }

  ComPtr toastTextElements;
  hr = toastXml->GetElementsByTagName(HStringReference(L"text").Get(),
                                      &toastTextElements);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  ComPtr titleTextNodeRoot;
  hr = toastTextElements->Item(0, &titleTextNodeRoot);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }
  ComPtr msgTextNodeRoot;
  hr = toastTextElements->Item(1, &msgTextNodeRoot);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  if (NS_WARN_IF(!SetNodeValueString(mTitle, titleTextNodeRoot.Get(),
                                     toastXml.Get()))) {
    return false;
  }
  if (NS_WARN_IF(
          !SetNodeValueString(mMsg, msgTextNodeRoot.Get(), toastXml.Get()))) {
    return false;
  }

  ComPtr toastElements;
  hr = toastXml->GetElementsByTagName(HStringReference(L"toast").Get(),
                                      &toastElements);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  ComPtr toastNodeRoot;
  hr = toastElements->Item(0, &toastNodeRoot);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  ComPtr actions;
  hr = toastXml->CreateElement(HStringReference(L"actions").Get(), &actions);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  ComPtr actionsNode;
  hr = actions.As(&actionsNode);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  nsCOMPtr sbs =
      do_GetService(NS_STRINGBUNDLE_CONTRACTID);
  if (NS_WARN_IF(!sbs)) {
    return false;
  }

  nsCOMPtr bundle;
  sbs->CreateBundle("chrome://alerts/locale/alert.properties",
                    getter_AddRefs(bundle));
  if (NS_WARN_IF(!bundle)) {
    return false;
  }

  if (!mHostPort.IsEmpty()) {
    AutoTArray formatStrings = {mHostPort};

    ComPtr urlTextNodeRoot;
    hr = toastTextElements->Item(2, &urlTextNodeRoot);
    if (NS_WARN_IF(FAILED(hr))) {
      return false;
    }

    nsAutoString urlReference;
    bundle->FormatStringFromName("source.label", formatStrings, urlReference);

    if (NS_WARN_IF(!SetNodeValueString(urlReference, urlTextNodeRoot.Get(),
                                       toastXml.Get()))) {
      return false;
    }

    if (IsWin10AnniversaryUpdateOrLater()) {
      ComPtr placementText;
      hr = urlTextNodeRoot.As(&placementText);
      if (SUCCEEDED(hr)) {
        // placement is supported on Windows 10 Anniversary Update or later
        SetAttribute(placementText.Get(), HStringReference(L"placement").Get(),
                     u"attribution"_ns);
      }
    }

    nsAutoString disableButtonTitle;
    bundle->FormatStringFromName("webActions.disableForOrigin.label",
                                 formatStrings, disableButtonTitle);

    AddActionNode(toastXml.Get(), actionsNode.Get(), disableButtonTitle,
                  u"snooze"_ns);
  }

  nsAutoString settingsButtonTitle;
  bundle->GetStringFromName("webActions.settings.label", settingsButtonTitle);
  AddActionNode(toastXml.Get(), actionsNode.Get(), settingsButtonTitle,
                u"settings"_ns);

  ComPtr appendedChild;
  hr = toastNodeRoot->AppendChild(actionsNode.Get(), &appendedChild);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  return CreateWindowsNotificationFromXml(toastXml.Get());
}

bool ToastNotificationHandler::CreateWindowsNotificationFromXml(
    IXmlDocument* aXml) {
  ComPtr factory;
  HRESULT hr = GetActivationFactory(
      HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotification)
          .Get(),
      &factory);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  hr = factory->CreateToastNotification(aXml, &mNotification);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  RefPtr self = this;

  hr = mNotification->add_Activated(
      Callback([self](IToastNotification* aNotification,
                                              IInspectable* aInspectable) {
        return self->OnActivate(aNotification, aInspectable);
      }).Get(),
      &mActivatedToken);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  hr = mNotification->add_Dismissed(
      Callback([self](IToastNotification* aNotification,
                                             IToastDismissedEventArgs* aArgs) {
        return self->OnDismiss(aNotification, aArgs);
      }).Get(),
      &mDismissedToken);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  hr = mNotification->add_Failed(
      Callback([self](IToastNotification* aNotification,
                                          IToastFailedEventArgs* aArgs) {
        return self->OnFail(aNotification, aArgs);
      }).Get(),
      &mFailedToken);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  ComPtr toastNotificationManagerStatics =
      GetToastNotificationManagerStatics();
  if (NS_WARN_IF(!toastNotificationManagerStatics)) {
    return false;
  }

  nsAutoString uid;
  if (NS_WARN_IF(!WinTaskbar::GetAppUserModelID(uid))) {
    return false;
  }

  HSTRING uidStr =
      HStringReference(static_cast(uid.get())).Get();
  hr = toastNotificationManagerStatics->CreateToastNotifierWithId(uidStr,
                                                                  &mNotifier);
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  hr = mNotifier->Show(mNotification.Get());
  if (NS_WARN_IF(FAILED(hr))) {
    return false;
  }

  if (mAlertListener) {
    mAlertListener->Observe(nullptr, "alertshow", mCookie.get());
  }

  return true;
}

void ToastNotificationHandler::SendFinished() {
  if (!mSentFinished && mAlertListener) {
    mAlertListener->Observe(nullptr, "alertfinished", mCookie.get());
  }

  mSentFinished = true;
}

HRESULT
ToastNotificationHandler::OnActivate(IToastNotification* notification,
                                     IInspectable* inspectable) {
  if (mAlertListener) {
    nsAutoString argString;
    if (inspectable) {
      ComPtr eventArgs;
      HRESULT hr = inspectable->QueryInterface(
          __uuidof(IToastActivatedEventArgs), (void**)&eventArgs);
      if (SUCCEEDED(hr)) {
        HSTRING arguments;
        hr = eventArgs->get_Arguments(&arguments);
        if (SUCCEEDED(hr)) {
          uint32_t len = 0;
          const wchar_t* buffer = WindowsGetStringRawBuffer(arguments, &len);
          if (buffer) {
            argString.Assign(buffer, len);
          }
        }
      }
    }

    if (argString.EqualsLiteral("settings")) {
      mAlertListener->Observe(nullptr, "alertsettingscallback", mCookie.get());
    } else if (argString.EqualsLiteral("snooze")) {
      mAlertListener->Observe(nullptr, "alertdisablecallback", mCookie.get());
    } else if (mClickable) {
      // When clicking toast, focus moves to another process, but we want to set
      // focus on Firefox process.
      nsCOMPtr winMediator(
          do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
      if (winMediator) {
        nsCOMPtr navWin;
        winMediator->GetMostRecentWindow(u"navigator:browser",
                                         getter_AddRefs(navWin));
        if (navWin) {
          nsCOMPtr widget =
              WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin));
          if (widget) {
            SetForegroundWindow(
                static_cast(widget->GetNativeData(NS_NATIVE_WINDOW)));
          }
        }
      }
      mAlertListener->Observe(nullptr, "alertclickcallback", mCookie.get());
    }
  }
  mBackend->RemoveHandler(mName, this);
  return S_OK;
}

HRESULT
ToastNotificationHandler::OnDismiss(IToastNotification* notification,
                                    IToastDismissedEventArgs* aArgs) {
  SendFinished();
  mBackend->RemoveHandler(mName, this);
  return S_OK;
}

HRESULT
ToastNotificationHandler::OnFail(IToastNotification* notification,
                                 IToastFailedEventArgs* aArgs) {
  SendFinished();
  mBackend->RemoveHandler(mName, this);
  return S_OK;
}

nsresult ToastNotificationHandler::TryShowAlert() {
  if (NS_WARN_IF(!ShowAlert())) {
    mBackend->RemoveHandler(mName, this);
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

NS_IMETHODIMP
ToastNotificationHandler::OnImageMissing(nsISupports*) {
  return TryShowAlert();
}

NS_IMETHODIMP
ToastNotificationHandler::OnImageReady(nsISupports*, imgIRequest* aRequest) {
  nsresult rv = AsyncSaveImage(aRequest);
  if (NS_FAILED(rv)) {
    return TryShowAlert();
  }
  return rv;
}

nsresult ToastNotificationHandler::AsyncSaveImage(imgIRequest* aRequest) {
  nsresult rv =
      NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mImageFile));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = mImageFile->Append(u"notificationimages"_ns);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = mImageFile->Create(nsIFile::DIRECTORY_TYPE, 0500);
  if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
    return rv;
  }

  nsCOMPtr idGen =
      do_GetService("@mozilla.org/uuid-generator;1", &rv);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsID uuid;
  rv = idGen->GenerateUUIDInPlace(&uuid);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  char uuidChars[NSID_LENGTH];
  uuid.ToProvidedString(uuidChars);
  // Remove the brackets at the beginning and ending of the generated UUID.
  nsAutoCString uuidStr(Substring(uuidChars + 1, uuidChars + NSID_LENGTH - 2));
  uuidStr.AppendLiteral(".bmp");
  mImageFile->AppendNative(uuidStr);

  nsCOMPtr imgContainer;
  rv = aRequest->GetImage(getter_AddRefs(imgContainer));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsMainThreadPtrHandle self(
      new nsMainThreadPtrHolder(
          "ToastNotificationHandler", this));

  nsCOMPtr imageFile(mImageFile);
  RefPtr surface = imgContainer->GetFrame(
      imgIContainer::FRAME_FIRST,
      imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
  nsCOMPtr r = NS_NewRunnableFunction(
      "ToastNotificationHandler::AsyncWriteBitmap",
      [self, imageFile, surface]() -> void {
        nsresult rv;
        if (!surface) {
          rv = NS_ERROR_FAILURE;
        } else {
          rv = WinUtils::WriteBitmap(imageFile, surface);
        }

        nsCOMPtr cbRunnable = NS_NewRunnableFunction(
            "ToastNotificationHandler::AsyncWriteBitmapCb",
            [self, rv]() -> void {
              auto handler = const_cast(self.get());
              handler->OnWriteBitmapFinished(rv);
            });

        NS_DispatchToMainThread(cbRunnable);
      });

  return mBackend->BackgroundDispatch(r);
}

void ToastNotificationHandler::OnWriteBitmapFinished(nsresult rv) {
  if (NS_SUCCEEDED(rv)) {
    OnWriteBitmapSuccess();
  }
  TryShowAlert();
}

nsresult ToastNotificationHandler::OnWriteBitmapSuccess() {
  nsresult rv;

  nsCOMPtr fileURI;
  rv = NS_NewFileURI(getter_AddRefs(fileURI), mImageFile);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsAutoCString uriStr;
  rv = fileURI->GetSpec(uriStr);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  AppendUTF8toUTF16(uriStr, mImageUri);

  mHasImage = true;

  return NS_OK;
}

}  // namespace widget
}  // namespace mozilla