Skip to content

Commit

Permalink
Bug 1732845 - Add nsINode::IsInDesignMode() to check whether the no…
Browse files Browse the repository at this point in the history
…de is directly in design mode r=smaug

There are a lot of check of `Document`'s editable state **with** comments. This
means that it's unclear for developers that only `Document` node is editable in
design mode.

Additionally, there are some points which use composed document rather than
uncomposed document even though the raw API uses uncomposed document. Comparing
with the other browsers, checking uncomposed document is compatible behavior,
i.e., nodes in shadow trees are not editable unless `contenteditable`.

Therefore, `nsINode` should have a method to check whether it's in design mode
or not.

Note that it may be called with a node in UA widget.  Therefore, this patch
adds new checks if it's in UA widget subtree or native anonymous subtree,
checking whether it's in design mode with its host.

Differential Revision: https://phabricator.services.mozilla.com/D126764
  • Loading branch information
masayuki-nakano committed Oct 12, 2021
1 parent 4c204f2 commit 25a3c48
Show file tree
Hide file tree
Showing 25 changed files with 524 additions and 93 deletions.
3 changes: 2 additions & 1 deletion accessible/generic/DocAccessible.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "nsIDocShell.h"
#include "mozilla/dom/Document.h"
#include "nsPIDOMWindow.h"
#include "nsIContentInlines.h"
#include "nsIEditingSession.h"
#include "nsIFrame.h"
#include "nsIInterfaceRequestorUtils.h"
Expand Down Expand Up @@ -297,7 +298,7 @@ void DocAccessible::TakeFocus() const {
already_AddRefed<EditorBase> DocAccessible::GetEditor() const {
// Check if document is editable (designMode="on" case). Otherwise check if
// the html:body (for HTML document case) or document element is editable.
if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) &&
if (!mDocumentNode->IsInDesignMode() &&
(!mContent || !mContent->HasFlag(NODE_IS_EDITABLE))) {
return nullptr;
}
Expand Down
9 changes: 5 additions & 4 deletions dom/base/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@
#include "nsICategoryManager.h"
#include "nsICertOverrideService.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIContentPolicy.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIContentSink.h"
Expand Down Expand Up @@ -4391,7 +4392,7 @@ bool Document::HasFocus(ErrorResult& rv) const {
}

void Document::GetDesignMode(nsAString& aDesignMode) {
if (HasFlag(NODE_IS_EDITABLE)) {
if (IsInDesignMode()) {
aDesignMode.AssignLiteral("on");
} else {
aDesignMode.AssignLiteral("off");
Expand Down Expand Up @@ -4424,7 +4425,7 @@ void Document::SetDesignMode(const nsAString& aDesignMode,
rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
return;
}
bool editableMode = HasFlag(NODE_IS_EDITABLE);
const bool editableMode = IsInDesignMode();
if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
SetEditableFlag(!editableMode);
// Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
Expand Down Expand Up @@ -5926,7 +5927,7 @@ nsresult Document::EditingStateChanged() {
return NS_OK;
}

bool designMode = HasFlag(NODE_IS_EDITABLE);
const bool designMode = IsInDesignMode();
EditingState newState =
designMode ? EditingState::eDesignMode
: (mContentEditableCount > 0 ? EditingState::eContentEditable
Expand Down Expand Up @@ -7956,7 +7957,7 @@ void Document::DispatchContentLoadedEvents() {

void Document::EndLoad() {
bool turnOnEditing =
mParser && (HasFlag(NODE_IS_EDITABLE) || mContentEditableCount > 0);
mParser && (IsInDesignMode() || mContentEditableCount > 0);

#if defined(DEBUG)
// only assert if nothing stopped the load on purpose
Expand Down
2 changes: 1 addition & 1 deletion dom/base/FragmentOrElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ dom::Element* nsIContent::GetEditingHost() {
}

// If this is in designMode, we should return <body>
if (doc->HasFlag(NODE_IS_EDITABLE) && !IsInShadowTree()) {
if (IsInDesignMode() && !IsInShadowTree()) {
return doc->GetBodyElement();
}

Expand Down
3 changes: 2 additions & 1 deletion dom/base/Selection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "nsContentCID.h"
#include "nsDeviceContext.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsRange.h"
#include "nsITableCellLayout.h"
#include "nsTArray.h"
Expand Down Expand Up @@ -3052,7 +3053,7 @@ void Selection::StyledRanges::MaybeFocusCommonEditingHost(
nsPIDOMWindowOuter* window = document->GetWindow();
// If the document is in design mode or doesn't have contenteditable
// element, we don't need to move focus.
if (window && !document->HasFlag(NODE_IS_EDITABLE) &&
if (window && !document->IsInDesignMode() &&
nsContentUtils::GetHTMLEditor(presContext)) {
RefPtr<Element> newEditingHost = GetCommonEditingHost();
RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
Expand Down
2 changes: 1 addition & 1 deletion dom/base/nsContentUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7097,7 +7097,7 @@ EditorBase* nsContentUtils::GetActiveEditor(nsPIDOMWindowOuter* aWindow) {

// If it's in designMode, nobody can have focus. Therefore, the HTMLEditor
// handles all events. I.e., it's focused editor in this case.
if (aWindow->GetExtantDoc()->HasFlag(NODE_IS_EDITABLE)) {
if (aWindow->GetExtantDoc()->IsInDesignMode()) {
return GetHTMLEditor(nsDocShell::Cast(aWindow->GetDocShell()));
}

Expand Down
23 changes: 15 additions & 8 deletions dom/base/nsFocusManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "nsContentUtils.h"
#include "ContentParent.h"
#include "nsPIDOMWindow.h"
#include "nsIContentInlines.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIFormControl.h"
Expand Down Expand Up @@ -2026,16 +2027,19 @@ bool nsFocusManager::IsNonFocusableRoot(nsIContent* aContent) {
MOZ_ASSERT(aContent, "aContent must not be NULL");
MOZ_ASSERT(aContent->IsInComposedDoc(), "aContent must be in a document");

// If aContent is in designMode, the root element is not focusable.
// NOTE: in designMode, most elements are not focusable, just the document is
// focusable.
// Also, if aContent is not editable but it isn't in designMode, it's not
// If the uncomposed document of aContent is in designMode, the root element
// is not focusable.
// NOTE: Most elements whose uncomposed document is in design mode are not
// focusable, just the document is focusable. However, if it's in a
// shadow tree, it may be focus able even if the shadow host is in
// design mode.
// Also, if aContent is not editable and it's not in designMode, it's not
// focusable.
// And in userfocusignored context nothing is focusable.
Document* doc = aContent->GetComposedDoc();
NS_ASSERTION(doc, "aContent must have current document");
return aContent == doc->GetRootElement() &&
(doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable());
(aContent->IsInDesignMode() || !aContent->IsEditable());
}

Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement,
Expand Down Expand Up @@ -2592,10 +2596,13 @@ void nsFocusManager::Focus(
// document and then the window.
if (aIsNewDocument) {
Document* doc = aWindow->GetExtantDoc();
// The focus change should be notified to IMEStateManager from here if
// the focused element is a designMode editor since any content won't
// The focus change should be notified to IMEStateManager from here if:
// * the focused element is in design mode or
// * nobody gets focus and the document is in design mode
// since any element whose uncomposed document is in design mode won't
// receive focus event.
if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
if (doc && ((aElement && aElement->IsInDesignMode()) ||
(!aElement && doc->IsInDesignMode()))) {
IMEStateManager::OnChangeFocus(presShell->GetPresContext(), nullptr,
GetFocusMoveActionCause(aFlags));
}
Expand Down
54 changes: 51 additions & 3 deletions dom/base/nsIContentInlines.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,59 @@ inline bool nsINode::IsEditable() const {
}

// Check if the node is in a document and the document is in designMode.
//
return IsInDesignMode();
}

inline bool nsINode::IsInDesignMode() const {
if (!OwnerDoc()->HasFlag(NODE_IS_EDITABLE)) {
return false;
}

if (IsDocument()) {
return HasFlag(NODE_IS_EDITABLE);
}

// NOTE(emilio): If you change this to be the composed doc you also need to
// change NotifyEditableStateChange() in Document.cpp.
Document* doc = GetUncomposedDoc();
return doc && doc->HasFlag(NODE_IS_EDITABLE);
// NOTE(masayuki): Perhaps, we should keep this behavior because of
// web-compat.
if (IsInUncomposedDoc() && GetUncomposedDoc()->HasFlag(NODE_IS_EDITABLE)) {
return true;
}

// FYI: In design mode, form controls don't work as usual. For example,
// <input type=text> isn't focusable but can be deleted and replaced
// with typed text. <select> is also not focusable but always selected
// all to be deleted or replaced. On the other hand, newer controls
// don't behave as the traditional controls. For example, data/time
// picker can be opened and change the value from the picker. And also
// the buttons of <video controls> work as usual. On the other hand,
// their UI (i.e., nodes in their shadow tree) are not editable.
// Therefore, we need special handling for nodes in anonymous subtree
// unless we fix <https://bugzilla.mozilla.org/show_bug.cgi?id=1734512>.

// If the shadow host is not in design mode, this can never be in design
// mode. Otherwise, the content is never editable by design mode of
// composed document.
if (IsInUAWidget()) {
nsIContent* host = GetContainingShadowHost();
MOZ_DIAGNOSTIC_ASSERT(host != this);
return host && host->IsInDesignMode();
}
MOZ_ASSERT(!IsUAWidget());

// If we're in a native anonymous subtree, we should consider it with the
// host.
if (IsInNativeAnonymousSubtree()) {
nsIContent* host = GetClosestNativeAnonymousSubtreeRootParent();
MOZ_DIAGNOSTIC_ASSERT(host != this);
return host && host->IsInDesignMode();
}

// Otherwise, i.e., when it's in a shadow tree which is not created by us,
// the node is not editable by design mode (but it's possible that it may be
// editable if this node is in `contenteditable` element in the shadow tree).
return false;
}

inline void nsIContent::HandleInsertionToOrRemovalFromSlot() {
Expand Down
3 changes: 1 addition & 2 deletions dom/base/nsINode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -558,8 +558,7 @@ nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell) {
HTMLEditor* htmlEditor = nsContentUtils::GetHTMLEditor(presContext);
if (htmlEditor) {
// This node is in HTML editor.
Document* doc = GetComposedDoc();
if (!doc || doc->HasFlag(NODE_IS_EDITABLE) ||
if (!IsInComposedDoc() || IsInDesignMode() ||
!HasFlag(NODE_IS_EDITABLE)) {
nsIContent* editorRoot = htmlEditor->GetRoot();
NS_ENSURE_TRUE(editorRoot, nullptr);
Expand Down
12 changes: 12 additions & 0 deletions dom/base/nsINode.h
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,18 @@ class nsINode : public mozilla::dom::EventTarget {

inline bool IsEditable() const;

/**
* Check if this node is in design mode or not. When this returns true and:
* - if this is a Document node, it's the design mode root.
* - if this is a content node, it's connected, it's not in a shadow tree
* (except shadow tree for UI widget and native anonymous subtree) and its
* uncomposed document is in design mode.
* Note that returning true does NOT mean the node or its children is
* editable. E.g., when this node is in a shadow tree of a UA widget and its
* host is in design mode.
*/
inline bool IsInDesignMode() const;

/**
* Returns true if |this| or any of its ancestors is native anonymous.
*/
Expand Down
4 changes: 2 additions & 2 deletions dom/events/GlobalKeyListener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "nsFocusManager.h"
#include "nsGkAtoms.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIDocShell.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
Expand Down Expand Up @@ -689,8 +690,7 @@ bool RootWindowGlobalKeyListener::IsHTMLEditorFocused() {
return false;
}

dom::Document* doc = htmlEditor->GetDocument();
if (doc->HasFlag(NODE_IS_EDITABLE)) {
if (htmlEditor->IsInDesignMode()) {
// Don't need to perform any checks in designMode documents.
return true;
}
Expand Down
15 changes: 7 additions & 8 deletions dom/events/IMEStateManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "mozilla/Unused.h"
#include "mozilla/dom/BrowserBridgeChild.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/HTMLFormElement.h"
#include "mozilla/dom/HTMLTextAreaElement.h"
#include "mozilla/dom/MouseEventBinding.h"
Expand All @@ -36,7 +37,7 @@
#include "nsContentUtils.h"
#include "nsFocusManager.h"
#include "nsIContent.h"
#include "mozilla/dom/Document.h"
#include "nsIContentInlines.h"
#include "nsIFormControl.h"
#include "nsINode.h"
#include "nsISupports.h"
Expand Down Expand Up @@ -1130,8 +1131,8 @@ IMEState IMEStateManager::GetNewIMEState(nsPresContext* aPresContext,
if (!aContent) {
// Even if there are no focused content, the focused document might be
// editable, such case is design mode.
Document* doc = aPresContext->Document();
if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
if (aPresContext->Document() &&
aPresContext->Document()->IsInDesignMode()) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" GetNewIMEState() returns IMEEnabled::Enabled because "
"design mode editor has focus"));
Expand Down Expand Up @@ -1939,11 +1940,9 @@ nsINode* IMEStateManager::GetRootEditableNode(nsPresContext* aPresContext,
}
return root;
}
if (aPresContext) {
Document* document = aPresContext->Document();
if (document && document->IsEditable()) {
return document;
}
if (aPresContext && aPresContext->Document() &&
aPresContext->Document()->IsInDesignMode()) {
return aPresContext->Document();
}
return nullptr;
}
Expand Down
5 changes: 3 additions & 2 deletions dom/html/HTMLObjectElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@

#include "mozilla/EventStates.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/HTMLObjectElement.h"
#include "mozilla/dom/HTMLObjectElementBinding.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/WindowProxyHolder.h"
#include "nsAttrValueInlines.h"
#include "nsGkAtoms.h"
#include "nsError.h"
#include "mozilla/dom/Document.h"
#include "nsIContentInlines.h"
#include "nsIWidget.h"
#include "nsContentUtils.h"
#ifdef XP_MACOSX
Expand Down Expand Up @@ -164,7 +165,7 @@ bool HTMLObjectElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
// TODO: this should probably be managed directly by IsHTMLFocusable.
// See bug 597242.
Document* doc = GetComposedDoc();
if (!doc || doc->HasFlag(NODE_IS_EDITABLE)) {
if (!doc || IsInDesignMode()) {
if (aTabIndex) {
*aTabIndex = -1;
}
Expand Down
24 changes: 11 additions & 13 deletions dom/html/nsGenericHTMLElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2138,7 +2138,7 @@ bool nsGenericHTMLElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
}

Document* doc = GetComposedDoc();
if (!doc || doc->HasFlag(NODE_IS_EDITABLE)) {
if (!doc || IsInDesignMode()) {
// In designMode documents we only allow focusing the document.
if (aTabIndex) {
*aTabIndex = -1;
Expand Down Expand Up @@ -2325,12 +2325,11 @@ void nsGenericHTMLElement::RecompileScriptEventListeners() {
}

bool nsGenericHTMLElement::IsEditableRoot() const {
Document* document = GetComposedDoc();
if (!document) {
if (!IsInComposedDoc()) {
return false;
}

if (document->HasFlag(NODE_IS_EDITABLE)) {
if (IsInDesignMode()) {
return false;
}

Expand All @@ -2343,8 +2342,7 @@ bool nsGenericHTMLElement::IsEditableRoot() const {
return !parent || !parent->HasFlag(NODE_IS_EDITABLE);
}

static void MakeContentDescendantsEditable(nsIContent* aContent,
Document* aDocument) {
static void MakeContentDescendantsEditable(nsIContent* aContent) {
// If aContent is not an element, we just need to update its
// internal editable state and don't need to notify anyone about
// that. For elements, we need to send a ContentStateChanged
Expand All @@ -2363,7 +2361,7 @@ static void MakeContentDescendantsEditable(nsIContent* aContent,
if (!child->IsElement() ||
!child->AsElement()->HasAttr(kNameSpaceID_None,
nsGkAtoms::contenteditable)) {
MakeContentDescendantsEditable(child, aDocument);
MakeContentDescendantsEditable(child);
}
}
}
Expand All @@ -2380,21 +2378,21 @@ void nsGenericHTMLElement::ChangeEditableState(int32_t aChange) {
previousEditingState = document->GetEditingState();
}

if (document->HasFlag(NODE_IS_EDITABLE)) {
document = nullptr;
}

// MakeContentDescendantsEditable is going to call ContentStateChanged for
// this element and all descendants if editable state has changed.
// We might as well wrap it all in one script blocker.
nsAutoScriptBlocker scriptBlocker;
MakeContentDescendantsEditable(this, document);
MakeContentDescendantsEditable(this);

// If the document already had contenteditable and JS adds new
// contenteditable, that might cause changing editing host to current editing
// host's ancestor. In such case, HTMLEditor needs to know that
// synchronously to update selection limitter.
if (document && aChange > 0 &&
// Additionally, elements in shadow DOM is not editable in the normal cases,
// but if its content has `contenteditable`, only in it can be ediable.
// So we don't need to notify HTMLEditor of this change only when we're not
// in shadow DOM and the composed document is in design mode.
if (IsInDesignMode() && !IsInShadowTree() && aChange > 0 &&
previousEditingState == Document::EditingState::eContentEditable) {
if (HTMLEditor* htmlEditor =
nsContentUtils::GetHTMLEditor(document->GetPresContext())) {
Expand Down
Loading

0 comments on commit 25a3c48

Please sign in to comment.