зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1200896 - Make the document blocked by the topmost element in the top layer r=emilio
Spec: https://html.spec.whatwg.org/multipage/#blocked-by-a-modal-dialog Differential Revision: https://phabricator.services.mozilla.com/D86227
This commit is contained in:
Родитель
514d0b9647
Коммит
3f16476b10
|
@ -13664,6 +13664,60 @@ void Document::TopLayerPush(Element* aElement) {
|
|||
NS_ASSERTION(GetTopLayerTop() == aElement, "Should match");
|
||||
}
|
||||
|
||||
void Document::SetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
|
||||
Element* root = GetRootElement();
|
||||
MOZ_RELEASE_ASSERT(root, "dialog in document without root?");
|
||||
|
||||
// Add inert to the root element so that the inertness is
|
||||
// applied to the entire document. Since the modal dialog
|
||||
// also inherits the inertness, adding
|
||||
// NS_EVENT_STATE_TOPMOST_MODAL_DIALOG to remove the inertness
|
||||
// explicitly.
|
||||
root->AddStates(NS_EVENT_STATE_MOZINERT);
|
||||
aDialogElement.AddStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
|
||||
|
||||
// It's possible that there's another modal dialog has opened
|
||||
// previously which doesn't have the inertness (because we've
|
||||
// removed the inertness explicitly). Since a
|
||||
// new modal dialog is opened, we need to grant the inertness
|
||||
// to the previous one.
|
||||
for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
|
||||
nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
|
||||
if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
|
||||
if (dialog != &aDialogElement) {
|
||||
dialog->RemoveStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
|
||||
// It's ok to exit the loop as only one modal dialog should
|
||||
// have the state
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Document::UnsetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
|
||||
aDialogElement.RemoveStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
|
||||
|
||||
// The document could still be blocked by another modal dialog.
|
||||
// We need to remove the inertness from this modal dialog.
|
||||
for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
|
||||
nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
|
||||
if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
|
||||
if (dialog != &aDialogElement) {
|
||||
dialog->AddStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
|
||||
// Return here because we want to keep the inertness for the
|
||||
// root element as the document is still blocked by a modal
|
||||
// dialog
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Element* root = GetRootElement();
|
||||
if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
|
||||
root->RemoveStates(NS_EVENT_STATE_MOZINERT);
|
||||
}
|
||||
}
|
||||
|
||||
Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicateFunc) {
|
||||
if (mTopLayer.IsEmpty()) {
|
||||
return nullptr;
|
||||
|
|
|
@ -180,6 +180,7 @@ class ImageTracker;
|
|||
class HTMLAllCollection;
|
||||
class HTMLBodyElement;
|
||||
class HTMLMetaElement;
|
||||
class HTMLDialogElement;
|
||||
class HTMLSharedElement;
|
||||
class HTMLImageElement;
|
||||
struct LifecycleCallbackArgs;
|
||||
|
@ -1869,6 +1870,10 @@ class Document : public nsINode,
|
|||
// Cancel the dialog element if the document is blocked by the dialog
|
||||
void TryCancelDialog();
|
||||
|
||||
void SetBlockedByModalDialog(HTMLDialogElement&);
|
||||
|
||||
void UnsetBlockedByModalDialog(HTMLDialogElement&);
|
||||
|
||||
/**
|
||||
* Called when a frame in a child process has entered fullscreen or when a
|
||||
* fullscreen frame in a child process changes to another origin.
|
||||
|
|
|
@ -623,9 +623,10 @@ class Element : public FragmentOrElement {
|
|||
FlushType aFlushType = FlushType::Layout);
|
||||
|
||||
private:
|
||||
// Need to allow the ESM, nsGlobalWindow, and the focus manager to
|
||||
// set our state
|
||||
// Need to allow the ESM, nsGlobalWindow, and the focus manager
|
||||
// and Document to set our state
|
||||
friend class mozilla::EventStateManager;
|
||||
friend class mozilla::dom::Document;
|
||||
friend class ::nsGlobalWindowInner;
|
||||
friend class ::nsGlobalWindowOuter;
|
||||
friend class ::nsFocusManager;
|
||||
|
@ -644,7 +645,8 @@ class Element : public FragmentOrElement {
|
|||
EventStates StyleStateFromLocks() const;
|
||||
|
||||
protected:
|
||||
// Methods for the ESM, nsGlobalWindow and focus manager to manage state bits.
|
||||
// Methods for the ESM, nsGlobalWindow, focus manager and Document to
|
||||
// manage state bits.
|
||||
// These will handle setting up script blockers when they notify, so no need
|
||||
// to do it in the callers unless desired. States passed here must only be
|
||||
// those in EXTERNALLY_MANAGED_STATES.
|
||||
|
|
|
@ -296,7 +296,8 @@ class EventStates {
|
|||
#define NS_EVENT_STATE_MODAL_DIALOG NS_DEFINE_EVENT_STATE_MACRO(53)
|
||||
// Inert subtrees
|
||||
#define NS_EVENT_STATE_MOZINERT NS_DEFINE_EVENT_STATE_MACRO(54)
|
||||
|
||||
// Topmost Modal <dialog> element in top layer
|
||||
#define NS_EVENT_STATE_TOPMOST_MODAL_DIALOG NS_DEFINE_EVENT_STATE_MACRO(55)
|
||||
/**
|
||||
* NOTE: do not go over 63 without updating EventStates::InternalType!
|
||||
*/
|
||||
|
@ -332,7 +333,7 @@ class EventStates {
|
|||
NS_EVENT_STATE_FOCUS_WITHIN | NS_EVENT_STATE_FULLSCREEN | \
|
||||
NS_EVENT_STATE_HOVER | NS_EVENT_STATE_URLTARGET | \
|
||||
NS_EVENT_STATE_FOCUS_VISIBLE | NS_EVENT_STATE_MODAL_DIALOG | \
|
||||
NS_EVENT_STATE_MOZINERT)
|
||||
NS_EVENT_STATE_MOZINERT | NS_EVENT_STATE_TOPMOST_MODAL_DIALOG)
|
||||
|
||||
#define INTRINSIC_STATES (~EXTERNALLY_MANAGED_STATES)
|
||||
|
||||
|
|
|
@ -66,15 +66,28 @@ bool HTMLDialogElement::IsInTopLayer() const {
|
|||
return State().HasState(NS_EVENT_STATE_MODAL_DIALOG);
|
||||
}
|
||||
|
||||
void HTMLDialogElement::AddToTopLayerIfNeeded() {
|
||||
if (IsInTopLayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Document* doc = OwnerDoc();
|
||||
doc->TopLayerPush(this);
|
||||
doc->SetBlockedByModalDialog(*this);
|
||||
AddStates(NS_EVENT_STATE_MODAL_DIALOG);
|
||||
}
|
||||
|
||||
void HTMLDialogElement::RemoveFromTopLayerIfNeeded() {
|
||||
if (!IsInTopLayer()) {
|
||||
return;
|
||||
}
|
||||
auto predictFunc = [&](Element* element) { return element == this; };
|
||||
|
||||
DebugOnly<Element*> removedElement = OwnerDoc()->TopLayerPop(predictFunc);
|
||||
Document* doc = OwnerDoc();
|
||||
DebugOnly<Element*> removedElement = doc->TopLayerPop(predictFunc);
|
||||
MOZ_ASSERT(removedElement == this);
|
||||
RemoveStates(NS_EVENT_STATE_MODAL_DIALOG);
|
||||
doc->UnsetBlockedByModalDialog(*this);
|
||||
}
|
||||
|
||||
void HTMLDialogElement::UnbindFromTree(bool aNullParent) {
|
||||
|
@ -94,11 +107,10 @@ void HTMLDialogElement::ShowModal(ErrorResult& aError) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!IsInTopLayer() && OwnerDoc()->TopLayerPush(this)) {
|
||||
AddStates(NS_EVENT_STATE_MODAL_DIALOG);
|
||||
}
|
||||
AddToTopLayerIfNeeded();
|
||||
|
||||
SetOpen(true, aError);
|
||||
|
||||
FocusDialog();
|
||||
|
||||
aError.SuppressException();
|
||||
|
|
|
@ -56,6 +56,7 @@ class HTMLDialogElement final : public nsGenericHTMLElement {
|
|||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
private:
|
||||
void AddToTopLayerIfNeeded();
|
||||
void RemoveFromTopLayerIfNeeded();
|
||||
};
|
||||
|
||||
|
|
|
@ -835,6 +835,20 @@ dialog:not([open]) {
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* This pseudo-class is used to remove the inertness for
|
||||
the topmost modal dialog in top layer. This reverts
|
||||
what StyleAdjuster's adjust_for_inert does.
|
||||
*/
|
||||
dialog:-moz-topmost-modal-dialog {
|
||||
-moz-inert: none;
|
||||
-moz-user-focus: initial;
|
||||
-moz-user-input: initial;
|
||||
-moz-user-modify: initial;
|
||||
user-select: text;
|
||||
pointer-events: initial;
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
dialog:-moz-modal-dialog {
|
||||
-moz-top-layer: top !important;
|
||||
/* This is a temporary solution until the relevant CSSWG issues
|
||||
|
|
|
@ -148,6 +148,8 @@ bitflags! {
|
|||
|
||||
/// https://html.spec.whatwg.org/multipage/interaction.html#inert-subtrees
|
||||
const IN_MOZINERT_STATE = 1 << 54;
|
||||
/// State for the topmost dialog element in top layer
|
||||
const IN_TOPMOST_MODAL_DIALOG_STATE = 1 << 55;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ macro_rules! apply_non_ts_list {
|
|||
("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
||||
("fullscreen", Fullscreen, IN_FULLSCREEN_STATE, _),
|
||||
("-moz-modal-dialog", MozModalDialog, IN_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
||||
("-moz-topmost-modal-dialog", MozTopmostModalDialog, IN_TOPMOST_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
||||
// TODO(emilio): This is inconsistently named (the capital R).
|
||||
("-moz-focusring", MozFocusRing, IN_FOCUSRING_STATE, _),
|
||||
("-moz-broken", MozBroken, IN_BROKEN_STATE, _),
|
||||
|
|
|
@ -2057,6 +2057,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
NonTSPseudoClass::MozDirAttrLikeAuto |
|
||||
NonTSPseudoClass::MozAutofill |
|
||||
NonTSPseudoClass::MozModalDialog |
|
||||
NonTSPseudoClass::MozTopmostModalDialog |
|
||||
NonTSPseudoClass::Active |
|
||||
NonTSPseudoClass::Hover |
|
||||
NonTSPseudoClass::MozAutofillPreview => {
|
||||
|
|
|
@ -162,6 +162,8 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
|
|||
/// user-select: none;
|
||||
/// pointer-events: none;
|
||||
/// cursor: default;
|
||||
/// dialog:-moz-topmost-modal-dialog is used to override above rules to remove
|
||||
/// the inertness for the topmost modal dialog.
|
||||
fn adjust_for_inert(&mut self) {
|
||||
use properties::longhands::_moz_inert::computed_value::T as Inert;
|
||||
use properties::longhands::_moz_user_focus::computed_value::T as UserFocus;
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
[inert-does-not-match-disabled-selector.html]
|
||||
prefs: [dom.dialog_element.enabled:false]
|
||||
[Tests inert elements do not match the :disabled selector.]
|
||||
expected:
|
||||
if (os == "android") and not debug: ["FAIL", "PASS"]
|
||||
FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[inert-node-is-unfocusable.html]
|
||||
[Test that inert nodes are not focusable.]
|
||||
expected: FAIL
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body id="body">
|
||||
<dialog>
|
||||
This is a dialog
|
||||
</dialog>
|
||||
<input />
|
||||
<script>
|
||||
"use strict";
|
||||
function testFocus(element, expectFocus) {
|
||||
var focusedElement = null;
|
||||
element.addEventListener('focus', function() { focusedElement = element; }, false);
|
||||
element.focus();
|
||||
var theElement = element;
|
||||
assert_equals(focusedElement === theElement, expectFocus, element.id);
|
||||
}
|
||||
|
||||
test(function() {
|
||||
var dialog = document.querySelector('dialog');
|
||||
dialog.showModal();
|
||||
|
||||
var input = document.querySelector('input');
|
||||
testFocus(input, false);
|
||||
|
||||
dialog.remove();
|
||||
testFocus(input, true);
|
||||
}, "Test that removing dialog unblocks the document.");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче