diff options
Diffstat (limited to 'dom/html/HTMLDialogElement.cpp')
-rw-r--r-- | dom/html/HTMLDialogElement.cpp | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/dom/html/HTMLDialogElement.cpp b/dom/html/HTMLDialogElement.cpp new file mode 100644 index 0000000000..be68bb1e81 --- /dev/null +++ b/dom/html/HTMLDialogElement.cpp @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/HTMLDialogElement.h" +#include "mozilla/dom/ElementBinding.h" +#include "mozilla/dom/HTMLDialogElementBinding.h" +#include "mozilla/dom/HTMLUnknownElement.h" +#include "mozilla/StaticPrefs_dom.h" + +#include "nsContentUtils.h" +#include "nsFocusManager.h" +#include "nsIFrame.h" + +// Expand NS_IMPL_NS_NEW_HTML_ELEMENT(Dialog) with pref check +nsGenericHTMLElement* NS_NewHTMLDialogElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser) { + RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); + auto* nim = nodeInfo->NodeInfoManager(); + bool isChromeDocument = nsContentUtils::IsChromeDoc(nodeInfo->GetDocument()); + if (mozilla::StaticPrefs::dom_dialog_element_enabled() || isChromeDocument) { + return new (nim) mozilla::dom::HTMLDialogElement(nodeInfo.forget()); + } + return new (nim) mozilla::dom::HTMLUnknownElement(nodeInfo.forget()); +} + +namespace mozilla::dom { + +HTMLDialogElement::~HTMLDialogElement() = default; + +NS_IMPL_ELEMENT_CLONE(HTMLDialogElement) + +bool HTMLDialogElement::IsDialogEnabled(JSContext* aCx, + JS::Handle<JSObject*> aObj) { + return StaticPrefs::dom_dialog_element_enabled() || + nsContentUtils::IsSystemCaller(aCx); +} + +void HTMLDialogElement::Close( + const mozilla::dom::Optional<nsAString>& aReturnValue) { + if (!Open()) { + return; + } + if (aReturnValue.WasPassed()) { + SetReturnValue(aReturnValue.Value()); + } + + SetOpen(false, IgnoreErrors()); + + RemoveFromTopLayerIfNeeded(); + + RefPtr<Element> previouslyFocusedElement = + do_QueryReferent(mPreviouslyFocusedElement); + + if (previouslyFocusedElement) { + mPreviouslyFocusedElement = nullptr; + + FocusOptions options; + options.mPreventScroll = true; + previouslyFocusedElement->Focus(options, CallerType::NonSystem, + IgnoredErrorResult()); + } + + RefPtr<AsyncEventDispatcher> eventDispatcher = + new AsyncEventDispatcher(this, u"close"_ns, CanBubble::eNo); + eventDispatcher->PostDOMEvent(); +} + +void HTMLDialogElement::Show(ErrorResult& aError) { + if (Open()) { + if (!IsInTopLayer()) { + return; + } + return aError.ThrowInvalidStateError( + "Cannot call show() on an open modal dialog."); + } + + if (IsPopoverOpen()) { + return aError.ThrowInvalidStateError( + "Dialog element is already an open popover."); + } + + SetOpen(true, IgnoreErrors()); + + StorePreviouslyFocusedElement(); + + OwnerDoc()->HideAllPopoversWithoutRunningScript(); + FocusDialog(); +} + +bool HTMLDialogElement::IsInTopLayer() const { + return State().HasState(ElementState::MODAL); +} + +void HTMLDialogElement::AddToTopLayerIfNeeded() { + MOZ_ASSERT(IsInComposedDoc()); + if (IsInTopLayer()) { + return; + } + + OwnerDoc()->AddModalDialog(*this); +} + +void HTMLDialogElement::RemoveFromTopLayerIfNeeded() { + if (!IsInTopLayer()) { + return; + } + OwnerDoc()->RemoveModalDialog(*this); +} + +void HTMLDialogElement::StorePreviouslyFocusedElement() { + if (Document* doc = GetComposedDoc()) { + if (nsIContent* unretargetedFocus = doc->GetUnretargetedFocusedContent()) { + mPreviouslyFocusedElement = + do_GetWeakReference(unretargetedFocus->AsElement()); + } + } +} + +void HTMLDialogElement::UnbindFromTree(bool aNullParent) { + RemoveFromTopLayerIfNeeded(); + nsGenericHTMLElement::UnbindFromTree(aNullParent); +} + +void HTMLDialogElement::ShowModal(ErrorResult& aError) { + if (Open()) { + if (IsInTopLayer()) { + return; + } + return aError.ThrowInvalidStateError( + "Cannot call showModal() on an open non-modal dialog."); + } + + if (!IsInComposedDoc()) { + return aError.ThrowInvalidStateError("Dialog element is not connected"); + } + + if (IsPopoverOpen()) { + return aError.ThrowInvalidStateError( + "Dialog element is already an open popover."); + } + + AddToTopLayerIfNeeded(); + + SetOpen(true, aError); + + StorePreviouslyFocusedElement(); + + OwnerDoc()->HideAllPopoversWithoutRunningScript(); + FocusDialog(); + + aError.SuppressException(); +} + +void HTMLDialogElement::FocusDialog() { + // 1) If subject is inert, return. + // 2) Let control be the first descendant element of subject, in tree + // order, that is not inert and has the autofocus attribute specified. + RefPtr<Document> doc = OwnerDoc(); + if (IsInComposedDoc()) { + doc->FlushPendingNotifications(FlushType::Frames); + } + + RefPtr<Element> control = GetFocusDelegate(false /* aWithMouse */); + + // If there isn't one of those either, then let control be subject. + if (!control) { + control = this; + } + + FocusCandidate(*control, IsInTopLayer()); +} + +void HTMLDialogElement::QueueCancelDialog() { + // queues an element task on the user interaction task source + OwnerDoc() + ->EventTargetFor(TaskCategory::UI) + ->Dispatch(NewRunnableMethod("HTMLDialogElement::RunCancelDialogSteps", + this, + &HTMLDialogElement::RunCancelDialogSteps)); +} + +void HTMLDialogElement::RunCancelDialogSteps() { + // 1) Let close be the result of firing an event named cancel at dialog, with + // the cancelable attribute initialized to true. + bool defaultAction = true; + nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u"cancel"_ns, + CanBubble::eNo, Cancelable::eYes, + &defaultAction); + + // 2) If close is true and dialog has an open attribute, then close the dialog + // with no return value. + if (defaultAction) { + Optional<nsAString> retValue; + Close(retValue); + } +} + +JSObject* HTMLDialogElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return HTMLDialogElement_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom |