Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/notification/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 33 kB image not shown  

Quelle  Notification.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 "mozilla/dom/Notification.h"

#include <utility>

#include "Navigator.h"
#include "NotificationUtils.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/UseCounter.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/glean/DomNotificationMetrics.h"
#include "mozilla/image/FetchDecodedImage.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "nsContentPermissionHelper.h"
#include "nsContentUtils.h"
#include "nsGlobalWindowInner.h"
#include "nsIContentPermissionPrompt.h"
#include "nsIScriptError.h"
#include "nsNetUtil.h"
#include "nsStructuredCloneContainer.h"

namespace mozilla::dom {

using namespace notification;

class NotificationPermissionRequest : public ContentPermissionRequestBase,
                                      public nsIRunnable,
                                      public nsINamed {
 public:
  NS_DECL_NSIRUNNABLE
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NotificationPermissionRequest,
                                           ContentPermissionRequestBase)

  // nsIContentPermissionRequest
  NS_IMETHOD Cancel(void) override;
  NS_IMETHOD Allow(JS::Handle<JS::Value> choices) override;

  NotificationPermissionRequest(nsIPrincipal* aPrincipal,
                                nsIPrincipal* aEffectiveStoragePrincipal,
                                nsPIDOMWindowInner* aWindow, Promise* aPromise,
                                NotificationPermissionCallback* aCallback)
      : ContentPermissionRequestBase(aPrincipal, aWindow, "notification"_ns,
                                     "desktop-notification"_ns),
        mEffectiveStoragePrincipal(aEffectiveStoragePrincipal),
        mPermission(NotificationPermission::Default),
        mPromise(aPromise),
        mCallback(aCallback) {
    MOZ_ASSERT(aPromise);
  }

  NS_IMETHOD GetName(nsACString& aName) override {
    aName.AssignLiteral("NotificationPermissionRequest");
    return NS_OK;
  }

 protected:
  ~NotificationPermissionRequest() = default;

  MOZ_CAN_RUN_SCRIPT nsresult ResolvePromise();
  nsresult DispatchResolvePromise();
  nsCOMPtr<nsIPrincipal> mEffectiveStoragePrincipal;
  NotificationPermission mPermission;
  RefPtr<Promise> mPromise;
  RefPtr<NotificationPermissionCallback> mCallback;
};

namespace {
class GetPermissionRunnable final : public WorkerMainThreadRunnable {
 public:
  explicit GetPermissionRunnable(WorkerPrivate* aWorker,
                                 bool aUseRegularPrincipal,
                                 PermissionCheckPurpose aPurpose)
      : WorkerMainThreadRunnable(aWorker, "Notification :: Get Permission"_ns),
        mUseRegularPrincipal(aUseRegularPrincipal),
        mPurpose(aPurpose) {}

  bool MainThreadRun() override {
    MOZ_ASSERT(mWorkerRef);
    WorkerPrivate* workerPrivate = mWorkerRef->Private();
    nsIPrincipal* principal = workerPrivate->GetPrincipal();
    nsIPrincipal* effectiveStoragePrincipal =
        mUseRegularPrincipal ? principal
                             : workerPrivate->GetPartitionedPrincipal();
    mPermission =
        GetNotificationPermission(principal, effectiveStoragePrincipal,
                                  workerPrivate->IsSecureContext(), mPurpose);
    return true;
  }

  NotificationPermission GetPermission() { return mPermission; }

 private:
  NotificationPermission mPermission = NotificationPermission::Denied;
  bool mUseRegularPrincipal;
  PermissionCheckPurpose mPurpose;
};

}  // anonymous namespace

NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationPermissionRequest,
                                   ContentPermissionRequestBase, mCallback,
                                   mPromise)
NS_IMPL_ADDREF_INHERITED(NotificationPermissionRequest,
                         ContentPermissionRequestBase)
NS_IMPL_RELEASE_INHERITED(NotificationPermissionRequest,
                          ContentPermissionRequestBase)

NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(
    NotificationPermissionRequest, ContentPermissionRequestBase, nsIRunnable,
    nsINamed)

NS_IMETHODIMP
NotificationPermissionRequest::Run() {
  if (IsNotificationAllowedFor(mPrincipal)) {
    mPermission = NotificationPermission::Granted;
  } else if (IsNotificationForbiddenFor(
                 mPrincipal, mEffectiveStoragePrincipal,
                 mWindow->IsSecureContext(),
                 PermissionCheckPurpose::PermissionRequest,
                 mWindow->GetExtantDoc())) {
    mPermission = NotificationPermission::Denied;
  } else if (!StaticPrefs::dom_webnotifications_allowcrossoriginiframe() &&
             !mPrincipal->Subsumes(mTopLevelPrincipal)) {
    mPermission = NotificationPermission::Denied;
  }

  // We can't call ShowPrompt() directly here since our logic for determining
  // whether to display a prompt depends on the checks above as well as the
  // result of CheckPromptPrefs().  So we have to manually check the prompt
  // prefs and decide what to do based on that.
  PromptResult pr = CheckPromptPrefs();
  switch (pr) {
    case PromptResult::Granted:
      mPermission = NotificationPermission::Granted;
      break;
    case PromptResult::Denied:
      mPermission = NotificationPermission::Denied;
      break;
    default:
      // ignore
      break;
  }

  if (!mHasValidTransientUserGestureActivation &&
      !StaticPrefs::dom_webnotifications_requireuserinteraction()) {
    nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
    if (doc) {
      doc->WarnOnceAbout(Document::eNotificationsRequireUserGestureDeprecation);
    }
  }

  if (mPermission != NotificationPermission::Default) {
    return DispatchResolvePromise();
  }

  return nsContentPermissionUtils::AskPermission(this, mWindow);
}

NS_IMETHODIMP
NotificationPermissionRequest::Cancel() {
  // `Cancel` is called if the user denied permission or dismissed the
  // permission request. To distinguish between the two, we set the
  // permission to "default" and query the permission manager in
  // `ResolvePromise`.
  mPermission = NotificationPermission::Default;
  return DispatchResolvePromise();
}

NS_IMETHODIMP
NotificationPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) {
  MOZ_ASSERT(aChoices.isUndefined());

  mPermission = NotificationPermission::Granted;
  return DispatchResolvePromise();
}

inline nsresult NotificationPermissionRequest::DispatchResolvePromise() {
  nsCOMPtr<nsIRunnable> resolver =
      NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise",
                        this, &NotificationPermissionRequest::ResolvePromise);
  return nsGlobalWindowInner::Cast(mWindow.get())->Dispatch(resolver.forget());
}

nsresult NotificationPermissionRequest::ResolvePromise() {
  nsresult rv = NS_OK;
  // This will still be "default" if the user dismissed the doorhanger,
  // or "denied" otherwise.
  if (mPermission == NotificationPermission::Default) {
    // When the front-end has decided to deny the permission request
    // automatically and we are not handling user input, then log a
    // warning in the current document that this happened because
    // Notifications require a user gesture.
    if (!mHasValidTransientUserGestureActivation &&
        StaticPrefs::dom_webnotifications_requireuserinteraction()) {
      nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
      if (doc) {
        nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns,
                                        doc, PropertiesFile::DOM_PROPERTIES,
                                        "NotificationsRequireUserGesture");
      }
    }

    mPermission = GetRawNotificationPermission(mPrincipal);
  }
  if (mCallback) {
    ErrorResult error;
    RefPtr<NotificationPermissionCallback> callback(mCallback);
    callback->Call(mPermission, error);
    rv = error.StealNSResult();
  }
  mPromise->MaybeResolve(mPermission);
  return rv;
}

// static
bool Notification::PrefEnabled(JSContext* aCx, JSObject* aObj) {
  return StaticPrefs::dom_webnotifications_enabled();
}

Notification::Notification(nsIGlobalObject* aGlobal,
                           const IPCNotification& aIPCNotification,
                           const nsAString& aScope)
    : DOMEventTargetHelper(aGlobal),
      mIPCNotification(aIPCNotification),
      mData(JS::NullValue()),
      mScope(aScope) {
  KeepAliveIfHasListenersFor(nsGkAtoms::onclick);
  KeepAliveIfHasListenersFor(nsGkAtoms::onshow);
  KeepAliveIfHasListenersFor(nsGkAtoms::onerror);
  KeepAliveIfHasListenersFor(nsGkAtoms::onclose);
}

// May be called on any thread.
// static
already_AddRefed<Notification> Notification::Constructor(
    const GlobalObject& aGlobal, const nsAString& aTitle,
    const NotificationOptions& aOptions, ErrorResult& aRv) {
  // FIXME(nsm): If the sticky flag is set, throw an error.
  RefPtr<ServiceWorkerGlobalScope> scope;
  UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
  if (scope) {
    aRv.ThrowTypeError(
        "Notification constructor cannot be used in ServiceWorkerGlobalScope. "
        "Use registration.showNotification() instead.");
    return nullptr;
  }

  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  RefPtr<Notification> notification = ValidateAndCreate(
      aGlobal.Context(), global, aTitle, aOptions, u""_ns, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  RefPtr<Promise> promise = Promise::CreateInfallible(global);
  promise->AddCallbacksWithCycleCollectedArgs(
      [](JSContext*, JS::Handle<JS::Value>, ErrorResult&,
         Notification* aNotification) {
        aNotification->DispatchTrustedEvent(u"show"_ns);
      },
      [](JSContext*, JS::Handle<JS::Value> aValue, ErrorResult&,
         Notification* aNotification) {
        aNotification->DispatchTrustedEvent(u"error"_ns);
        aNotification->Deactivate();
      },
      notification);

  ContextInfo contextInfo = notification->GetContextInfo();
  if (!notification->CreateActor(contextInfo)) {
    notification->Deactivate();
    aRv.ThrowUnknownError("Failed to create actor.");
    return nullptr;
  }

  notification->LoadImageAndShow(promise, std::move(contextInfo));

  notification->KeepAliveIfHasListenersFor(nsGkAtoms::onclick);
  notification->KeepAliveIfHasListenersFor(nsGkAtoms::onshow);
  notification->KeepAliveIfHasListenersFor(nsGkAtoms::onerror);
  notification->KeepAliveIfHasListenersFor(nsGkAtoms::onclose);

  return notification.forget();
}

// NOTE(krosylight): Maybe move this check to the parent process?
Result<Ok, nsresult> ValidateBase64Data(const nsAString& aData) {
  if (aData.IsEmpty()) {
    return Ok();
  }

  // To and from to ensure it is valid base64.
  RefPtr<nsStructuredCloneContainer> container =
      new nsStructuredCloneContainer();
  MOZ_TRY(container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION));

  nsString result;
  MOZ_TRY(container->GetDataAsBase64(result));

  return Ok();
}

// static
Result<already_AddRefed<Notification>, nsresult> Notification::ConstructFromIPC(
    nsIGlobalObject* aGlobal, const IPCNotification& aIPCNotification,
    const nsAString& aServiceWorkerRegistrationScope) {
  MOZ_ASSERT(aGlobal);

  MOZ_TRY(ValidateBase64Data(aIPCNotification.options().dataSerialized()));

  RefPtr<Notification> notification = new Notification(
      aGlobal, aIPCNotification, aServiceWorkerRegistrationScope);

  return notification.forget();
}

void Notification::MaybeNotifyClose() {
  if (mIsClosed) {
    return;
  }
  mIsClosed = true;
  DispatchTrustedEvent(u"close"_ns);
}

static Result<nsString, nsresult> SerializeDataAsBase64(
    JSContext* aCx, JS::Handle<JS::Value> aData) {
  if (aData.isNull()) {
    return nsString();
  }
  RefPtr<nsStructuredCloneContainer> dataObjectContainer =
      new nsStructuredCloneContainer();
  MOZ_TRY(dataObjectContainer->InitFromJSVal(aData, aCx));

  nsString result;
  MOZ_TRY(dataObjectContainer->GetDataAsBase64(result));

  return result;
}

#define SetUseCounterIf(wasUsed, memberName)                             \
  if (wasUsed) {                                                         \
    if (NS_IsMainThread()) {                                             \
      SetUseCounter(aGlobal->GetGlobalJSObject(),                        \
                    eUseCounter_NotificationOptions_##memberName);       \
    } else {                                                             \
      SetUseCounter(UseCounterWorker::NotificationOptions_##memberName); \
    }                                                                    \
  }

/* static */
// https://notifications.spec.whatwg.org/#create-a-notification
already_AddRefed<Notification> Notification::ValidateAndCreate(
    JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aTitle,
    const NotificationOptions& aOptions, const nsAString& aScope,
    ErrorResult& aRv) {
  MOZ_ASSERT(aGlobal);

  SetUseCounterIf(aOptions.mNavigate.WasPassed(), navigate);
  SetUseCounterIf(aOptions.mImage.WasPassed(), image);
  SetUseCounterIf(aOptions.mBadge.WasPassed(), badge);
  SetUseCounterIf(aOptions.mVibrate.WasPassed(), vibrate);
  SetUseCounterIf(aOptions.mTimestamp.WasPassed(), timestamp);
  SetUseCounterIf(aOptions.mRenotify, renotify);
  SetUseCounterIf(aOptions.mRequireInteraction, requireInteraction);
  SetUseCounterIf(!aOptions.mActions.IsEmpty(), actions);

  // Step 4: Set notification’s data to
  // StructuredSerializeForStorage(options["data"]).
  JS::Rooted<JS::Value> data(aCx, aOptions.mData);
  Result<nsString, nsresult> dataResult = SerializeDataAsBase64(aCx, data);
  if (dataResult.isErr()) {
    aRv = dataResult.unwrapErr();
    return nullptr;
  }

  // Step 17: Set notification’s silent preference to options["silent"].
  bool silent = false;
  if (StaticPrefs::dom_webnotifications_silent_enabled()) {
    silent = aOptions.mSilent;
  }

  nsTArray<uint32_t> vibrate;
  if (StaticPrefs::dom_webnotifications_vibrate_enabled() &&
      aOptions.mVibrate.WasPassed()) {
    // Step 2: If options["silent"] is true and options["vibrate"] exists, then
    // throw a TypeError.
    if (silent) {
      aRv.ThrowTypeError(
          "Silent notifications must not specify vibration patterns.");
      return nullptr;
    }

    // Step 14: If options["vibrate"] exists, then validate and normalize it and
    // set notification’s vibration pattern to the return value.
    const OwningUnsignedLongOrUnsignedLongSequence& value =
        aOptions.mVibrate.Value();
    if (value.IsUnsignedLong()) {
      AutoTArray<uint32_t, 1> array;
      array.AppendElement(value.GetAsUnsignedLong());
      vibrate = SanitizeVibratePattern(array);
    } else {
      vibrate = SanitizeVibratePattern(value.GetAsUnsignedLongSequence());
    }
  }

  // Step 12: If options["icon"] exists, then parse it using baseURL, and if
  // that does not return failure, set notification’s icon URL to the return
  // value. (Otherwise icon URL is not set.)
  RefPtr<nsIURI> iconUrl = ResolveIconURL(aGlobal, aOptions.mIcon);

  // Step 19: Set notification’s actions to « ».
  nsTArray<IPCNotificationAction> actions;
  if (StaticPrefs::dom_webnotifications_actions_enabled()) {
    // Step 20: For each entry in options["actions"], up to the maximum number
    // of actions supported (skip any excess entries):
    for (const auto& entry : aOptions.mActions) {
      // Step 20.1: Let action be a new notification action.
      IPCNotificationAction action;
      // Step 20.2: Set action’s name to entry["action"].
      action.name() = entry.mAction;
      // Step 20.3: Set action’s title to entry["title"].
      action.title() = entry.mTitle;
      // Step 20.4: (Skipping icon support, see
      // https://github.com/whatwg/notifications/issues/233)
      // Step 20.5: Append action to notification’s actions.
      actions.AppendElement(std::move(action));
      if (actions.Length() == kMaxActions) {
        break;
      }
    }
  }

  IPCNotification ipcNotification(
      nsString(), IPCNotificationOptions(
                      nsString(aTitle), aOptions.mDir, nsString(aOptions.mLang),
                      nsString(aOptions.mBody), nsString(aOptions.mTag),
                      iconUrl, aOptions.mRequireInteraction, silent, vibrate,
                      nsString(dataResult.unwrap()), actions));

  RefPtr<Notification> notification =
      new Notification(aGlobal, ipcNotification, aScope);
  return notification.forget();
}

Notification::~Notification() { mozilla::DropJSObjects(this); }

NS_IMPL_CYCLE_COLLECTION_CLASS(Notification)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification,
                                                DOMEventTargetHelper)
  tmp->mData.setUndefined();
  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification,
                                                  DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification,
                                               DOMEventTargetHelper)
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_IMPL_ADDREF_INHERITED(Notification, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Notification)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

/* static */
bool Notification::RequestPermissionEnabledForScope(JSContext* aCx,
                                                    JSObject* /* unused */) {
  // requestPermission() is not allowed on workers. The calling page should ask
  // for permission on the worker's behalf. This is to prevent 'which window
  // should show the browser pop-up'. See discussion:
  // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html
  return NS_IsMainThread();
}

// static
already_AddRefed<Promise> Notification::RequestPermission(
    const GlobalObject& aGlobal,
    const Optional<OwningNonNull<NotificationPermissionCallback>>& aCallback,
    ErrorResult& aRv) {
  AssertIsOnMainThread();

  // Get principal from global to make permission request for notifications.
  nsCOMPtr<nsPIDOMWindowInner> window =
      do_QueryInterface(aGlobal.GetAsSupports());
  nsCOMPtr<nsIScriptObjectPrincipal> sop =
      do_QueryInterface(aGlobal.GetAsSupports());
  if (!sop || !window) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
  nsCOMPtr<nsIPrincipal> effectiveStoragePrincipal =
      sop->GetEffectiveStoragePrincipal();
  if (!principal || !effectiveStoragePrincipal) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }
  NotificationPermissionCallback* permissionCallback = nullptr;
  if (aCallback.WasPassed()) {
    permissionCallback = &aCallback.Value();
  }
  nsCOMPtr<nsIRunnable> request =
      new NotificationPermissionRequest(principal, effectiveStoragePrincipal,
                                        window, promise, permissionCallback);

  window->AsGlobal()->Dispatch(request.forget());

  return promise.forget();
}

// static
NotificationPermission Notification::GetPermission(const GlobalObject& aGlobal,
                                                   ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  return GetPermission(global, PermissionCheckPurpose::PermissionAttribute,
                       aRv);
}

// static
NotificationPermission Notification::GetPermission(
    nsIGlobalObject* aGlobal, PermissionCheckPurpose aPurpose,
    ErrorResult& aRv) {
  if (NS_IsMainThread()) {
    return GetPermissionInternal(aGlobal->GetAsInnerWindow(), aPurpose, aRv);
  }

  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
  MOZ_ASSERT(worker);
  RefPtr<GetPermissionRunnable> r = new GetPermissionRunnable(
      worker, worker->UseRegularPrincipal(), aPurpose);
  r->Dispatch(worker, Canceling, aRv);
  if (aRv.Failed()) {
    return NotificationPermission::Denied;
  }

  return r->GetPermission();
}

/* static */
NotificationPermission Notification::GetPermissionInternal(
    nsPIDOMWindowInner* aWindow, PermissionCheckPurpose aPurpose,
    ErrorResult& aRv) {
  // Get principal from global to check permission for notifications.
  nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
  if (!sop) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return NotificationPermission::Denied;
  }

  nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
  nsCOMPtr<nsIPrincipal> effectiveStoragePrincipal =
      sop->GetEffectiveStoragePrincipal();
  if (!principal || !effectiveStoragePrincipal) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return NotificationPermission::Denied;
  }

  return GetNotificationPermission(principal, effectiveStoragePrincipal,
                                   aWindow->IsSecureContext(), aPurpose);
}

uint32_t Notification::MaxActions(const GlobalObject& aGlobal) {
  return kMaxActions;
}

already_AddRefed<nsIURI> Notification::ResolveIconURL(
    nsIGlobalObject* aGlobal, const nsACString& aIconUrl) {
  nsresult rv = NS_OK;

  if (aIconUrl.IsEmpty()) {
    return nullptr;
  }

  nsCOMPtr<nsIURI> baseUri = aGlobal->GetBaseURI();
  if (!baseUri) {
    return nullptr;
  }

  nsCOMPtr<nsIURI> srcUri;
  rv = NS_NewURI(getter_AddRefs(srcUri), aIconUrl, nullptr, baseUri);
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  return srcUri.forget();
}

JSObject* Notification::WrapObject(JSContext* aCx,
                                   JS::Handle<JSObject*> aGivenProto) {
  return mozilla::dom::Notification_Binding::Wrap(aCx, this, aGivenProto);
}

void Notification::Close() {
  if (mIsClosed) {
    return;
  }
  if (!mActor) {
    CreateActor(GetContextInfo());
  }
  if (mActor) {
    (void)mActor->SendClose();
  }
}

bool Notification::RequireInteraction() const {
  return mIPCNotification.options().requireInteraction();
}

bool Notification::Silent() const {
  return mIPCNotification.options().silent();
}

void Notification::GetVibrate(nsTArray<uint32_t>& aRetval) const {
  aRetval = mIPCNotification.options().vibrate().Clone();
}

void Notification::GetData(JSContext* aCx,
                           JS::MutableHandle<JS::Value> aRetval) {
  const nsString& dataSerialized = mIPCNotification.options().dataSerialized();
  if (mData.isNull() && !dataSerialized.IsEmpty()) {
    nsresult rv;
    RefPtr<nsStructuredCloneContainer> container =
        new nsStructuredCloneContainer();
    rv = container->InitFromBase64(dataSerialized, JS_STRUCTURED_CLONE_VERSION);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRetval.setNull();
      return;
    }

    JS::Rooted<JS::Value> data(aCx);
    rv = container->DeserializeToJsval(aCx, &data);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRetval.setNull();
      return;
    }

    if (data.isGCThing()) {
      mozilla::HoldJSObjects(this);
    }
    mData = data;
  }
  if (mData.isNull()) {
    aRetval.setNull();
    return;
  }

  aRetval.set(mData);
}

void Notification::GetActions(nsTArray<NotificationAction>& aRetVal) {
  aRetVal.Clear();
  for (const IPCNotificationAction& entry :
       mIPCNotification.options().actions()) {
    RootedDictionary<NotificationAction> action(RootingCx());
    action.mAction = entry.name();
    action.mTitle = entry.title();
    aRetVal.AppendElement(action);
  }
}

// Steps 2-5 of
// https://notifications.spec.whatwg.org/#dom-serviceworkerregistration-shownotification
/* static */
already_AddRefed<Promise> Notification::ShowPersistentNotification(
    JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aScope,
    const nsAString& aTitle, const NotificationOptions& aOptions,
    const ServiceWorkerRegistrationDescriptor& aDescriptor, ErrorResult& aRv) {
  MOZ_ASSERT(aGlobal);

  // Step 2: Let promise be a new promise in this’s relevant Realm.
  RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  // Step 3: If this’s active worker is null, then reject promise with a
  // TypeError and return promise.
  if (!aDescriptor.GetActive()) {
    aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(NS_ConvertUTF16toUTF8(aScope));
    return nullptr;
  }

  // Step 4: Let notification be the result of creating a notification with a
  // settings object given title, options, and this’s relevant settings object.
  // If this threw an exception, then reject promise with that exception and
  // return promise.
  //
  // Step 5: Set notification’s service worker registration to this.
  //
  // Note: We currently use the scope as the unique identifier for the
  // registration (and there currently is no durable registration identifier,
  // so this is necessary), which is why we pass in the scope.  See
  // https://github.com/whatwg/notifications/issues/205 for some scope-related
  // discussion.
  //
  // XXX: We create Notification object almost solely to share the parameter
  // normalization steps. It would be nice to export that and skip creating
  // object here.
  RefPtr<Notification> notification =
      ValidateAndCreate(aCx, aGlobal, aTitle, aOptions, aScope, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  ContextInfo contextInfo = notification->GetContextInfo();
  if (!notification->CreateActor(contextInfo)) {
    aRv.ThrowUnknownError("Failed to create actor.");
    return nullptr;
  }
  notification->LoadImageAndShow(p, std::move(contextInfo));

  return p.forget();
}

Notification::ContextInfo Notification::GetContextInfo() {
  // TODO: Should get nsIGlobalObject methods for each method
  if (WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate()) {
    return {.mTarget = workerPrivate->HybridEventTarget(),
            .mPrincipal = workerPrivate->GetPrincipal(),
            .mEffectiveStoragePrincipal =
                workerPrivate->GetEffectiveStoragePrincipal(),
            .mIsSecureContext = workerPrivate->IsSecureContext()};
  }

  nsGlobalWindowInner* win = GetOwnerWindow();
  return {
      .mPrincipal = win->GetPrincipal(),
      .mEffectiveStoragePrincipal = win->GetEffectiveStoragePrincipal(),
      .mIsSecureContext = win->IsSecureContext(),
  };
}

bool Notification::CreateActor(const ContextInfo& aInfo) {
  mozilla::ipc::PBackgroundChild* backgroundActor =
      mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();

  // Note: We are not using the typical PBackground managed actor here as we
  // want the actor to be in the main thread of the main process. Instead we
  // pass the endpoint to PBackground, it dispatches a runnable to the main
  // thread, and the endpoint is bound there.

  mozilla::ipc::Endpoint<notification::PNotificationParent> parentEndpoint;
  mozilla::ipc::Endpoint<notification::PNotificationChild> childEndpoint;
  notification::PNotification::CreateEndpoints(&parentEndpoint, &childEndpoint);

  bool persistent = !mScope.IsEmpty();
  RefPtr<nsPIDOMWindowInner> window = GetOwnerWindow();
  mActor = new notification::NotificationChild(
      persistent ? nullptr : this,
      window ? window->GetWindowGlobalChild() : nullptr);

  if (!childEndpoint.Bind(mActor, aInfo.mTarget)) {
    return false;
  }

  (void)backgroundActor->SendCreateNotificationParent(
      std::move(parentEndpoint), WrapNotNull(aInfo.mPrincipal),
      WrapNotNull(aInfo.mEffectiveStoragePrincipal), aInfo.mIsSecureContext,
      mScope, mIPCNotification);

  return true;
}

void Notification::LoadImageAndShow(Promise* aPromise, ContextInfo&& aInfo) {
  nsCOMPtr<nsIURI> uri = mIPCNotification.options().icon();
  Maybe<ClientInfo> clientInfo = GetParentObject()->GetClientInfo();
  if (!uri || clientInfo.isNothing()) {
    SendShow(aPromise, Nothing());
    return;
  }

  RefPtr<StrongWorkerRef> workerRef;
  if (!NS_IsMainThread()) {
    workerRef = StrongWorkerRef::Create(GetCurrentThreadWorkerPrivate(),
                                        "Notification::LoadImageAndShow");
    if (!workerRef) {
      return;
    }
  }

  using IPCImagePromise = mozilla::MozPromise<Maybe<IPCImage>, bool, true>;
  InvokeAsync(
      GetMainThreadSerialEventTarget(), __func__,
      [uri, clientInfo, contextInfo = std::move(aInfo)]() {
        // Don't load the image if we aren't even allowed to show the
        // notification.
        NotificationPermission permission = GetNotificationPermission(
            contextInfo.mPrincipal, contextInfo.mEffectiveStoragePrincipal,
            contextInfo.mIsSecureContext,
            PermissionCheckPurpose::LoadImageForShow);
        if (permission != NotificationPermission::Granted) {
          return image::FetchDecodedImagePromise::CreateAndReject(
              NS_ERROR_FAILURE, __func__);
        }

        // https://notifications.spec.whatwg.org/#fetch-steps
        // Step 1: If the notification platform supports images, fetch
        // notification's image URL, if image URL is set.
        //
        // But the spec doesn't provide proper details (see
        // https://github.com/whatwg/notifications/issues/208), so for
        // now we pretend a Request object with:
        // * method: "GET"
        // * destination: "image"
        // * mode: "no-cors"
        nsCOMPtr<nsIChannel> channel;
        nsresult rv = NS_NewChannel(
            getter_AddRefs(channel), uri, contextInfo.mPrincipal,
            clientInfo.ref(), Maybe<dom::ServiceWorkerDescriptor>(),
            nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
            nsIContentPolicy::TYPE_INTERNAL_IMAGE_NOTIFICATION);
        if (NS_FAILED(rv)) {
          return image::FetchDecodedImagePromise::CreateAndReject(rv, __func__);
        }

        // This function must be called on the main thread.
        return image::FetchDecodedImage(uri, channel, gfx::IntSize{});
      })
      ->Then(
          GetMainThreadSerialEventTarget(), __func__,
          [](already_AddRefed<imgIContainer> aImage) {
            nsCOMPtr<imgIContainer> image(std::move(aImage));
            // The promise should only be resolved for decoded images, so
            // using FLAG_SYNC_DECODE is just a precaution.
            if (RefPtr<mozilla::gfx::SourceSurface> surface =
                    image->GetFrame(imgIContainer::FRAME_FIRST,
                                    imgIContainer::FLAG_SYNC_DECODE |
                                        imgIContainer::FLAG_ASYNC_NOTIFY)) {
              if (RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
                      surface->GetDataSurface()) {
                return IPCImagePromise::CreateAndResolve(
                    nsContentUtils::SurfaceToIPCImage(*dataSurface), __func__);
              }
            }

            return IPCImagePromise::CreateAndResolve(Nothing(), __func__);
          },
          [](nsresult) {
            // Show the notification even if loading the image failed.
            return IPCImagePromise::CreateAndResolve(Nothing(), __func__);
          })
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [self = RefPtr{this}, promise = RefPtr{aPromise},
           workerRef = std::move(workerRef)](Maybe<IPCImage>&& aImage) {
            // SendShow must happen on the original (potentially Worker) thread.
            self->SendShow(promise, std::move(aImage));
          },
          [](bool) {});
}

void Notification::SendShow(Promise* aPromise, Maybe<IPCImage>&& aIcon) {
  if (mIsClosed) {
    MOZ_ASSERT(mIPCNotification.options().icon(),
               "Closure before SendShow can only happen with image resources");
    return;
  }

  mActor->SendShow(std::move(aIcon))
      ->Then(GetCurrentSerialEventTarget(), __func__,
             [self = RefPtr{this}, promise = RefPtr(aPromise)](
                 notification::PNotificationChild::ShowPromise::
                     ResolveOrRejectValue&& aResult) {
               if (aResult.IsReject()) {
                 promise->MaybeRejectWithUnknownError(
                     "Failed to open notification");
                 self->Deactivate();
                 return;
               }

               CopyableErrorResult rv = aResult.ResolveValue();
               if (rv.Failed()) {
                 promise->MaybeReject(std::move(rv));
                 self->Deactivate();
                 return;
               }

               if (promise) {
                 promise->MaybeResolveWithUndefined();
               } else {
                 self->DispatchTrustedEvent(u"show"_ns);
               }
             });
}

void Notification::Deactivate() {
  IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onclick);
  IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onshow);
  IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onerror);
  IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onclose);
  mIsClosed = true;
  if (mActor) {
    mActor->Close();
    mActor = nullptr;
  }
}

nsresult Notification::DispatchToMainThread(
    already_AddRefed<nsIRunnable> aRunnable) {
  if (WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate()) {
    return workerPrivate->DispatchToMainThread(std::move(aRunnable));
  }
  AssertIsOnMainThread();
  return NS_DispatchToCurrentThread(std::move(aRunnable));
}

}  // namespace mozilla::dom

Messung V0.5 in Prozent
C=90 H=96 G=93

¤ Dauer der Verarbeitung: 0.37 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

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.