diff --git a/mozilla/content/base/public/nsIContent.h b/mozilla/content/base/public/nsIContent.h index f80e02af8fe..105be819376 100644 --- a/mozilla/content/base/public/nsIContent.h +++ b/mozilla/content/base/public/nsIContent.h @@ -63,9 +63,8 @@ class nsIDocShell; // IID for the nsIContent interface #define NS_ICONTENT_IID \ -{ 0xb6408b0, 0x20c6, 0x4d60, \ - { 0xb7, 0x2f, 0x90, 0xb7, 0x7a, 0x9d, 0xb9, 0xb6 } } - +{ 0x36b375cb, 0xf01e, 0x4c18, \ + { 0xbf, 0x9e, 0xba, 0xad, 0x77, 0x1d, 0xce, 0x22 } } // hack to make egcs / gcc 2.95.2 happy class nsIContent_base : public nsINode { @@ -715,11 +714,8 @@ public: */ // XXXbz this is PRInt32 because all the ESM content state APIs use // PRInt32. We should really use PRUint32 instead. - virtual PRInt32 IntrinsicState() const - { - return 0; - } - + virtual PRInt32 IntrinsicState() const; + /* The default script type (language) ID for this content. All content must support fetching the default script language. */ @@ -790,6 +786,12 @@ public: */ virtual nsIAtom *GetClassAttributeName() const = 0; + /** + * Should be called when the node can become editable or when it can stop + * being editable (for example when its contentEditable attribute changes, + * when it is moved into an editable parent, ...). + */ + virtual void UpdateEditableState(); #ifdef DEBUG /** diff --git a/mozilla/content/base/public/nsINode.h b/mozilla/content/base/public/nsINode.h index 74eb33e3dfc..d06e9e259ea 100644 --- a/mozilla/content/base/public/nsINode.h +++ b/mozilla/content/base/public/nsINode.h @@ -89,11 +89,13 @@ class nsNodeSupportsWeakRefTearoff; // Whether a binding manager may have a pointer to this #define NODE_MAY_BE_IN_BINDING_MNGR 0x00000080U +#define NODE_IS_EDITABLE 0x00000100U + // Four bits for the script-type ID -#define NODE_SCRIPT_TYPE_OFFSET 8 +#define NODE_SCRIPT_TYPE_OFFSET 9 // Remaining bits are node type specific. -#define NODE_TYPE_SPECIFIC_BITS_OFFSET 0x0c +#define NODE_TYPE_SPECIFIC_BITS_OFFSET 0x0d // Useful macro for getting a node given an nsIContent and an nsIDocument // Returns the first argument cast to nsINode if it is non-null, otherwise @@ -105,8 +107,8 @@ class nsNodeSupportsWeakRefTearoff; // IID for the nsINode interface #define NS_INODE_IID \ -{ 0x22ab1440, 0xa6ee, 0x4da7, \ - { 0xbc, 0x3b, 0x94, 0x2e, 0x56, 0x0d, 0xdc, 0xe0 } } +{ 0xd3e63f80, 0x9e98, 0x47d7, \ + { 0xac, 0x8d, 0xad, 0x6f, 0x20, 0x6c, 0xe7, 0xc6 } } // hack to make egcs / gcc 2.95.2 happy class nsINode_base : public nsPIDOMEventTarget { @@ -596,6 +598,16 @@ public: *flags &= ~aFlagsToUnset; } + void SetEditableFlag(PRBool aEditable) + { + if (aEditable) { + SetFlags(NODE_IS_EDITABLE); + } + else { + UnsetFlags(NODE_IS_EDITABLE); + } + } + protected: // Override this function to create a custom slots class. diff --git a/mozilla/content/base/public/nsISelectionPrivate.idl b/mozilla/content/base/public/nsISelectionPrivate.idl index d6a9e96c498..178a91de928 100644 --- a/mozilla/content/base/public/nsISelectionPrivate.idl +++ b/mozilla/content/base/public/nsISelectionPrivate.idl @@ -41,6 +41,7 @@ interface nsIDOMRange; interface nsISelectionListener; +interface nsIContent; %{C++ class nsFrameSelection; @@ -54,7 +55,7 @@ struct nsPoint; [ptr] native nsIPresShell(nsIPresShell); [ref] native nsPointRef(nsPoint); -[scriptable, uuid(3225CA54-D7E1-4FF5-8EE9-091B0BFCDA1F)] +[scriptable, uuid(b416c692-eeb8-4186-addd-c444e81b68e5)] interface nsISelectionPrivate : nsISupports { const short ENDOFPRECEDINGLINE=0; @@ -115,5 +116,7 @@ interface nsISelectionPrivate : nsISupports * Returnes a reference to the frame selection associated with this selection */ [noscript] nsFrameSelection getFrameSelection(); + + [noscript] void setAncestorLimiter(in nsIContent aContent); }; diff --git a/mozilla/content/base/src/nsGenericDOMDataNode.cpp b/mozilla/content/base/src/nsGenericDOMDataNode.cpp index 9741254b74c..ecb45429c14 100644 --- a/mozilla/content/base/src/nsGenericDOMDataNode.cpp +++ b/mozilla/content/base/src/nsGenericDOMDataNode.cpp @@ -598,6 +598,8 @@ nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsNodeUtils::ParentChainChanged(this); + UpdateEditableState(); + NS_POSTCONDITION(aDocument == GetCurrentDoc(), "Bound to wrong document"); NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent"); NS_POSTCONDITION(aBindingParent == GetBindingParent(), diff --git a/mozilla/content/base/src/nsGenericElement.cpp b/mozilla/content/base/src/nsGenericElement.cpp index 53a2f247824..aa97723f193 100644 --- a/mozilla/content/base/src/nsGenericElement.cpp +++ b/mozilla/content/base/src/nsGenericElement.cpp @@ -300,6 +300,28 @@ nsIContent::SetNativeAnonymous(PRBool aAnonymous) } } +PRInt32 +nsIContent::IntrinsicState() const +{ + PRBool editable = HasFlag(NODE_IS_EDITABLE); + if (!editable) { + nsIDocument *doc = GetCurrentDoc(); + if (doc) { + editable = doc->HasFlag(NODE_IS_EDITABLE); + } + } + + return editable ? NS_EVENT_STATE_MOZ_READWRITE : NS_EVENT_STATE_MOZ_READONLY; +} + +void +nsIContent::UpdateEditableState() +{ + nsIContent *parent = GetParent(); + + SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE)); +} + //---------------------------------------------------------------------- nsChildContentList::~nsChildContentList() @@ -2002,6 +2024,8 @@ nsGenericElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, } } + UpdateEditableState(); + // Now recurse into our kids PRUint32 i; // Don't call GetChildCount() here since that'll make XUL generate diff --git a/mozilla/content/base/src/nsGkAtomList.h b/mozilla/content/base/src/nsGkAtomList.h index ba19825839f..b5a007b54af 100755 --- a/mozilla/content/base/src/nsGkAtomList.h +++ b/mozilla/content/base/src/nsGkAtomList.h @@ -208,6 +208,7 @@ GK_ATOM(commandupdater, "commandupdater") GK_ATOM(comment, "comment") GK_ATOM(compact, "compact") GK_ATOM(concat, "concat") +GK_ATOM(contenteditable, "contenteditable") GK_ATOM(conditions, "conditions") GK_ATOM(constructor, "constructor") GK_ATOM(container, "container") @@ -410,6 +411,7 @@ GK_ATOM(indent, "indent") GK_ATOM(index, "index") GK_ATOM(infer, "infer") GK_ATOM(infinity, "infinity") +GK_ATOM(inherit, "inherit") GK_ATOM(inherits, "inherits") GK_ATOM(inheritstyle, "inheritstyle") GK_ATOM(input, "input") diff --git a/mozilla/content/base/src/nsTextNode.cpp b/mozilla/content/base/src/nsTextNode.cpp index c622bd6a69b..53f97542d6c 100644 --- a/mozilla/content/base/src/nsTextNode.cpp +++ b/mozilla/content/base/src/nsTextNode.cpp @@ -239,7 +239,9 @@ nsTextNode::List(FILE* out, PRInt32 aIndent) const PRInt32 index; for (index = aIndent; --index >= 0; ) fputs(" ", out); - fprintf(out, "Text@%p refcount=%d<", this, mRefCnt.get()); + fprintf(out, "Text@%p", this); + fprintf(out, " intrinsicstate=[%08x]", IntrinsicState()); + fprintf(out, " refcount=%d<", mRefCnt.get()); nsAutoString tmp; ToCString(tmp, 0, mText.GetLength()); diff --git a/mozilla/content/html/content/src/nsGenericHTMLElement.cpp b/mozilla/content/html/content/src/nsGenericHTMLElement.cpp index 21a7f9fe51c..bd35f2f2649 100644 --- a/mozilla/content/html/content/src/nsGenericHTMLElement.cpp +++ b/mozilla/content/html/content/src/nsGenericHTMLElement.cpp @@ -1102,18 +1102,12 @@ nsGenericHTMLElement::GetSpellcheck(PRBool* aSpellcheck) return NS_OK; // Not spellchecked by default } - // Is this the actual body of the current document? if (IsCurrentBodyElement()) { - // Is designMode on? - nsCOMPtr nsHTMLDocument = - do_QueryInterface(GetCurrentDoc()); - if (!nsHTMLDocument) { - return PR_FALSE; + nsCOMPtr doc = do_QueryInterface(GetCurrentDoc()); + if (doc) { + *aSpellcheck = doc->IsEditingOn(); } - nsAutoString designMode; - nsHTMLDocument->GetDesignMode(designMode); - *aSpellcheck = designMode.EqualsLiteral("on"); return NS_OK; } @@ -1164,6 +1158,20 @@ nsGenericHTMLElement::InNavQuirksMode(nsIDocument* aDoc) return aDoc && aDoc->GetCompatibilityMode() == eCompatibility_NavQuirks; } +void +nsGenericHTMLElement::UpdateEditableState() +{ + // XXX Should we do this only when in a document? + ContentEditableTristate value = GetContentEditableValue(); + if (value != eInherit) { + SetEditableFlag(value); + + return; + } + + nsGenericElement::UpdateEditableState(); +} + nsresult nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, @@ -1174,6 +1182,14 @@ nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); + if (aDocument && HasFlag(NODE_IS_EDITABLE) && + GetContentEditableValue() == eTrue) { + nsCOMPtr htmlDocument = do_QueryInterface(aDocument); + if (htmlDocument) { + htmlDocument->ChangeContentEditableCount(this, +1); + } + } + // XXXbz if we already have a style attr parsed, this won't do // anything... need to fix that. ReparseStyleAttribute(); @@ -1190,6 +1206,19 @@ nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, return rv; } +void +nsGenericHTMLElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent) +{ + if (GetContentEditableValue() == eTrue) { + nsCOMPtr htmlDocument = do_QueryInterface(GetCurrentDoc()); + if (htmlDocument) { + htmlDocument->ChangeContentEditableCount(this, -1); + } + } + + nsGenericElement::UnbindFromTree(aDeep, aNullParent); +} + already_AddRefed nsGenericHTMLElement::FindForm(nsIForm* aCurrentForm) { @@ -1398,18 +1427,50 @@ nsGenericHTMLElement::GetEventListenerManagerForAttr(nsIEventListenerManager** a aDefer); } +nsresult +nsGenericHTMLElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, + nsIAtom* aPrefix, const nsAString& aValue, + PRBool aNotify) +{ + PRBool contentEditable = aNameSpaceID == kNameSpaceID_None && + aName == nsGkAtoms::contenteditable; + PRInt32 change; + if (contentEditable) { + change = GetContentEditableValue() == eTrue ? -1 : 0; + } + + nsresult rv = nsGenericElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue, + aNotify); + NS_ENSURE_SUCCESS(rv, rv); + + if (contentEditable) { + if (aValue.IsEmpty() || aValue.LowerCaseEqualsLiteral("true")) { + change += 1; + } + + ChangeEditableState(change); + } + + return NS_OK; +} + nsresult nsGenericHTMLElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute, PRBool aNotify) { // Check for event handlers - if (aNameSpaceID == kNameSpaceID_None && - nsContentUtils::IsEventAttributeName(aAttribute, EventNameType_HTML)) { - nsCOMPtr manager; - GetListenerManager(PR_FALSE, getter_AddRefs(manager)); + if (aNameSpaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::contenteditable) { + ChangeEditableState(GetContentEditableValue() == eTrue ? -1 : 0); + } + else if (nsContentUtils::IsEventAttributeName(aAttribute, + EventNameType_HTML)) { + nsCOMPtr manager; + GetListenerManager(PR_FALSE, getter_AddRefs(manager)); - if (manager) { - manager->RemoveScriptEventListener(aAttribute); + if (manager) { + manager->RemoveScriptEventListener(aAttribute); + } } } @@ -1580,6 +1641,11 @@ nsGenericHTMLElement::ParseAttribute(PRInt32 aNamespaceID, aResult.ParseAtom(aValue); return PR_TRUE; } + + if (aAttribute == nsGkAtoms::contenteditable) { + aResult.ParseAtom(aValue); + return PR_TRUE; + } } return nsGenericElement::ParseAttribute(aNamespaceID, aAttribute, aValue, @@ -2021,6 +2087,24 @@ void nsGenericHTMLElement::MapCommonAttributesInto(const nsMappedAttributes* aAttributes, nsRuleData* aData) { + if (aData->mSID == eStyleStruct_UserInterface) { + nsRuleDataUserInterface *ui = aData->mUserInterfaceData; + if (ui->mUserModify.GetUnit() == eCSSUnit_Null) { + const nsAttrValue* value = + aAttributes->GetAttr(nsGkAtoms::contenteditable); + if (value) { + if (value->Equals(nsGkAtoms::_empty, eCaseMatters) || + value->Equals(nsGkAtoms::_true, eIgnoreCase)) { + ui->mUserModify.SetIntValue(NS_STYLE_USER_MODIFY_READ_WRITE, + eCSSUnit_Enumerated); + } + else { + ui->mUserModify.SetIntValue(NS_STYLE_USER_MODIFY_READ_ONLY, + eCSSUnit_Enumerated); + } + } + } + } if (aData->mSID == eStyleStruct_Visibility) { const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::lang); if (value && value->Type() == nsAttrValue::eString) { @@ -2030,10 +2114,34 @@ nsGenericHTMLElement::MapCommonAttributesInto(const nsMappedAttributes* aAttribu } } +void +nsGenericHTMLFormElement::UpdateEditableFormControlState() +{ + ContentEditableTristate value = GetContentEditableValue(); + if (value != eInherit) { + SetEditableFlag(value); + + return; + } + + nsIContent *parent = GetParent(); + PRBool editable = parent && parent->HasFlag(NODE_IS_EDITABLE); + + if (!editable) { + // If not contentEditable we still need to check the readonly attribute. + PRBool roState; + GetBoolAttr(nsGkAtoms::readonly, &roState); + + editable = !roState; + } + + SetEditableFlag(editable); +} /* static */ const nsGenericHTMLElement::MappedAttributeEntry nsGenericHTMLElement::sCommonAttributeMap[] = { + { &nsGkAtoms::contenteditable }, { &nsGkAtoms::lang }, { nsnull } }; @@ -2528,6 +2636,47 @@ nsGenericHTMLElement::GetURIListAttr(nsIAtom* aAttr, nsAString& aResult) return NS_OK; } +nsresult +nsGenericHTMLElement::GetContentEditable(nsAString& aContentEditable) +{ + ContentEditableTristate value = GetContentEditableValue(); + + if (value == eTrue) { + aContentEditable.AssignLiteral("true"); + } + else if (value == eFalse) { + aContentEditable.AssignLiteral("false"); + } + else { + aContentEditable.AssignLiteral("inherit"); + } + + return NS_OK; +} + +nsresult +nsGenericHTMLElement::SetContentEditable(const nsAString& aContentEditable) +{ + nsString contentEditable; + ToLowerCase(aContentEditable, contentEditable); + + if (contentEditable.EqualsLiteral("inherit")) { + UnsetAttr(kNameSpaceID_None, nsGkAtoms::contenteditable, PR_TRUE); + + return NS_OK; + } + + if (!contentEditable.EqualsLiteral("true") && + !contentEditable.EqualsLiteral("false")) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + SetAttr(kNameSpaceID_None, nsGkAtoms::contenteditable, contentEditable, + PR_TRUE); + + return NS_OK; +} + //---------------------------------------------------------------------- NS_IMPL_INT_ATTR_DEFAULT_VALUE(nsGenericHTMLFrameElement, TabIndex, tabindex, 0) @@ -3153,10 +3302,19 @@ nsGenericHTMLElement::IsFocusable(PRInt32 *aTabIndex) PRInt32 tabIndex = 0; // Default value for non HTML elements with -moz-user-focus GetTabIndex(&tabIndex); - // Just check for disabled attribute on all HTML elements - PRBool disabled = HasAttr(kNameSpaceID_None, nsGkAtoms::disabled); - if (disabled) { - tabIndex = -1; + PRBool disabled; + if (IsEditableRoot()) { + disabled = PR_FALSE; + if (!HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) { + tabIndex = 0; + } + } + else { + // Just check for disabled attribute on all HTML elements + disabled = HasAttr(kNameSpaceID_None, nsGkAtoms::disabled); + if (disabled) { + tabIndex = -1; + } } if (aTabIndex) { @@ -3768,3 +3926,93 @@ nsGenericHTMLElement::RecompileScriptEventListeners() AddScriptEventListener(attr, value, PR_TRUE); } } + +PRBool +nsGenericHTMLElement::IsEditableRoot() const +{ + nsIDocument *document = GetCurrentDoc(); + if (!document) { + return PR_FALSE; + } + + if (document->HasFlag(NODE_IS_EDITABLE)) { + return this == document->GetRootContent(); + } + + if (!HasFlag(NODE_IS_EDITABLE)) { + return PR_FALSE; + } + + nsIContent *parent = GetParent(); + + return !parent || !parent->HasFlag(NODE_IS_EDITABLE); +} + +nsIContent* +nsGenericHTMLElement::FindEditableRoot() +{ + nsIDocument *document = GetCurrentDoc(); + if (!document) { + return nsnull; + } + + if (document->HasFlag(NODE_IS_EDITABLE)) { + return document->GetRootContent(); + } + + if (!HasFlag(NODE_IS_EDITABLE)) { + return nsnull; + } + + nsIContent *parent, *content = this; + while ((parent = content->GetParent()) && parent->HasFlag(NODE_IS_EDITABLE)) { + content = parent; + } + + return content; +} + +static void +MakeContentDescendantsEditable(nsIContent *aContent, nsIDocument *aDocument) +{ + PRInt32 stateBefore = aContent->IntrinsicState(); + + aContent->UpdateEditableState(); + + if (aDocument && stateBefore != aContent->IntrinsicState()) { + aDocument->ContentStatesChanged(aContent, nsnull, + NS_EVENT_STATE_MOZ_READONLY | + NS_EVENT_STATE_MOZ_READWRITE); + } + + PRUint32 i, n = aContent->GetChildCount(); + for (i = 0; i < n; ++i) { + nsIContent *child = aContent->GetChildAt(i); + if (!child->HasAttr(kNameSpaceID_None, nsGkAtoms::contenteditable)) { + MakeContentDescendantsEditable(child, aDocument); + } + } +} + +void +nsGenericHTMLElement::ChangeEditableState(PRInt32 aChange) +{ + nsIDocument* document = GetCurrentDoc(); + if (!document) { + return; + } + + if (aChange != 0) { + nsCOMPtr htmlDocument = + do_QueryInterface(document); + if (htmlDocument) { + htmlDocument->ChangeContentEditableCount(this, aChange); + } + } + + if (document->HasFlag(NODE_IS_EDITABLE)) { + document = nsnull; + } + + MakeContentDescendantsEditable(this, document); +} diff --git a/mozilla/content/html/content/src/nsGenericHTMLElement.h b/mozilla/content/html/content/src/nsGenericHTMLElement.h index c7fa8fc3d9e..e0519d1de0c 100644 --- a/mozilla/content/html/content/src/nsGenericHTMLElement.h +++ b/mozilla/content/html/content/src/nsGenericHTMLElement.h @@ -44,6 +44,7 @@ #include "nsIFormControl.h" #include "nsIDOMNSHTMLFrameElement.h" #include "nsFrameLoader.h" +#include "nsGkAtoms.h" class nsIDOMAttr; class nsIDOMEventListener; @@ -165,6 +166,8 @@ public: NS_IMETHOD SetTabIndex(PRInt32 aTabIndex); NS_IMETHOD GetSpellcheck(PRBool* aSpellcheck); NS_IMETHOD SetSpellcheck(PRBool aSpellcheck); + nsresult GetContentEditable(nsAString &aContentEditable); + nsresult SetContentEditable(const nsAString &aContentEditable); /** * Get the frame's offset information for offsetTop/Left/Width/Height. @@ -196,6 +199,16 @@ public: virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, PRBool aCompileEventHandlers); + virtual void UnbindFromTree(PRBool aDeep = PR_TRUE, + PRBool aNullParent = PR_TRUE); + nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, + const nsAString& aValue, PRBool aNotify) + { + return SetAttr(aNameSpaceID, aName, nsnull, aValue, aNotify); + } + virtual nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, + nsIAtom* aPrefix, const nsAString& aValue, + PRBool aNotify); virtual nsresult UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, PRBool aNotify); virtual PRBool IsNodeOfType(PRUint32 aFlags) const; @@ -225,6 +238,8 @@ public: } virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const; + virtual void UpdateEditableState(); + virtual const nsAttrValue* GetClasses() const; virtual nsIAtom *GetIDAttributeName() const; virtual nsIAtom *GetClassAttributeName() const; @@ -768,6 +783,50 @@ protected: * spellchecking. */ static void SyncEditorsOnSubtree(nsIContent* content); + + enum ContentEditableTristate { + eInherit = -1, + eFalse = 0, + eTrue = 1 + }; + + /** + * Returns eTrue if the element has a contentEditable attribute and its value + * is "true" or an empty string. Returns eFalse if the element has a + * contentEditable attribute and its value is "false". Otherwise returns + * eInherit. + */ + NS_HIDDEN_(ContentEditableTristate) GetContentEditableValue() const + { + static const nsIContent::AttrValuesArray values[] = + { &nsGkAtoms::_false, &nsGkAtoms::_true, &nsGkAtoms::_empty, nsnull }; + + PRInt32 value = FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::contenteditable, values, + eIgnoreCase); + + return value > 0 ? eTrue : (value == 0 ? eFalse : eInherit); + } + +private: + /** + * Returns whether this element is an editable root. An editable root is + * defined as an element that is editable and whose parent is either a + * non-editable element or an editable document (so if the whole document is + * editable, then there is only one editable root, namely the + * documentElement). + */ + PRBool IsEditableRoot() const; + + /** + * Returns the first node amongst this node and its ancestors that is an + * editable root. + * + * @see IsEditableRoot for a definition of an editable root. + */ + nsIContent* FindEditableRoot(); + + void ChangeEditableState(PRInt32 aChange); }; @@ -817,6 +876,7 @@ public: virtual nsresult UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, PRBool aNotify); virtual PRUint32 GetDesiredIMEState(); + virtual PRInt32 IntrinsicState() const; protected: /** @@ -838,7 +898,7 @@ protected: */ PRBool CanBeDisabled() const; - virtual PRInt32 IntrinsicState() const; + void UpdateEditableFormControlState(); void SetFocusAndScrollIntoView(nsPresContext* aPresContext); diff --git a/mozilla/content/html/content/src/nsHTMLInputElement.cpp b/mozilla/content/html/content/src/nsHTMLInputElement.cpp index 517714bbd51..b839bcfa9bd 100644 --- a/mozilla/content/html/content/src/nsHTMLInputElement.cpp +++ b/mozilla/content/html/content/src/nsHTMLInputElement.cpp @@ -238,6 +238,11 @@ public: virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const; + virtual void UpdateEditableState() + { + return UpdateEditableFormControlState(); + } + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsHTMLInputElement, nsGenericHTMLFormElement) @@ -601,6 +606,21 @@ nsHTMLInputElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, NS_EVENT_STATE_LOADING); } } + + // If readonly is changed for text and password we need to handle + // :read-only / :read-write + if (aNotify && aName == nsGkAtoms::readonly && + (mType == NS_FORM_INPUT_TEXT || mType == NS_FORM_INPUT_PASSWORD)) { + UpdateEditableState(); + + nsIDocument* document = GetCurrentDoc(); + if (document) { + mozAutoDocUpdate upd(document, UPDATE_CONTENT_STATE, PR_TRUE); + document->ContentStatesChanged(this, nsnull, + NS_EVENT_STATE_MOZ_READONLY | + NS_EVENT_STATE_MOZ_READWRITE); + } + } } return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName, diff --git a/mozilla/content/html/content/src/nsHTMLTextAreaElement.cpp b/mozilla/content/html/content/src/nsHTMLTextAreaElement.cpp index cebf913870a..8cb6e900a12 100644 --- a/mozilla/content/html/content/src/nsHTMLTextAreaElement.cpp +++ b/mozilla/content/html/content/src/nsHTMLTextAreaElement.cpp @@ -165,6 +165,11 @@ public: nsIContent* aChild, PRInt32 aIndexInContainer); + virtual void UpdateEditableState() + { + return UpdateEditableFormControlState(); + } + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHTMLTextAreaElement, nsGenericHTMLFormElement) @@ -202,6 +207,9 @@ protected: * parent; we should only respond to the change if aContent is non-anonymous. */ void ContentChanged(nsIContent* aContent); + + virtual nsresult AfterSetAttr(PRInt32 aNamespaceID, nsIAtom *aName, + const nsAString* aValue, PRBool aNotify); }; @@ -990,3 +998,23 @@ nsHTMLTextAreaElement::ContentChanged(nsIContent* aContent) Reset(); } } + +nsresult +nsHTMLTextAreaElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, + const nsAString* aValue, PRBool aNotify) +{ + if (aNotify && aNameSpaceID == kNameSpaceID_None && + aName == nsGkAtoms::readonly) { + UpdateEditableState(); + + nsIDocument* document = GetCurrentDoc(); + if (document) { + mozAutoDocUpdate upd(document, UPDATE_CONTENT_STATE, PR_TRUE); + document->ContentStatesChanged(this, nsnull, + NS_EVENT_STATE_MOZ_READONLY | + NS_EVENT_STATE_MOZ_READWRITE); + } + } + return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName, aValue, + aNotify); +} diff --git a/mozilla/content/html/document/src/Makefile.in b/mozilla/content/html/document/src/Makefile.in index 7dc7c7b1b58..23ac37343a3 100644 --- a/mozilla/content/html/document/src/Makefile.in +++ b/mozilla/content/html/document/src/Makefile.in @@ -76,6 +76,7 @@ REQUIRES = xpcom \ composer \ editor \ plugin \ + txtsvc \ $(NULL) CPPSRCS = \ diff --git a/mozilla/content/html/document/src/nsHTMLDocument.cpp b/mozilla/content/html/document/src/nsHTMLDocument.cpp index 08f25b3ff8f..172490993fe 100644 --- a/mozilla/content/html/document/src/nsHTMLDocument.cpp +++ b/mozilla/content/html/document/src/nsHTMLDocument.cpp @@ -125,6 +125,7 @@ #include "nsIMutableArray.h" #include "nsArrayUtils.h" #include "nsIEffectiveTLDService.h" +#include "nsIEventStateManager.h" #include "nsIPrompt.h" //AHMED 12-2 @@ -133,6 +134,11 @@ #include "nsIEditingSession.h" #include "nsIEditor.h" #include "nsNodeInfoManager.h" +#include "nsIEditor.h" +#include "nsIEditorDocShell.h" +#include "nsIEditorStyleSheets.h" +#include "nsIInlineSpellChecker.h" +#include "nsRange.h" #define NS_MAX_DOCUMENT_WRITE_DEPTH 20 @@ -1204,8 +1210,13 @@ nsHTMLDocument::EndLoad() mWriteState == eDocumentClosed, "EndLoad called early"); mWriteState = eNotWriting; + PRBool turnOnEditing = + mParser && (HasFlag(NODE_IS_EDITABLE) || mContentEditableCount > 0); // Note: nsDocument::EndLoad nulls out mParser. nsDocument::EndLoad(); + if (turnOnEditing) { + EditingStateChanged(); + } } NS_IMETHODIMP @@ -2207,14 +2218,14 @@ nsHTMLDocument::OpenCommon(const nsACString& aContentType, PRBool aReplace) mRootContent = root; } - if (mEditingIsOn) { + if (IsEditingOn()) { // Reset() blows away all event listeners in the document, and our // editor relies heavily on those. Midas is turned on, to make it // work, re-initialize it to give it a chance to add its event // listeners again. - SetDesignMode(NS_LITERAL_STRING("off")); - SetDesignMode(NS_LITERAL_STRING("on")); + TurnEditingOff(); + EditingStateChanged(); } // Zap the old title -- otherwise it would hang around until document.close() @@ -3717,7 +3728,7 @@ nsHTMLDocument::GenerateParserKey(void) NS_IMETHODIMP nsHTMLDocument::GetDesignMode(nsAString & aDesignMode) { - if (mEditingIsOn) { + if (HasFlag(NODE_IS_EDITABLE)) { aDesignMode.AssignLiteral("on"); } else { @@ -3726,9 +3737,152 @@ nsHTMLDocument::GetDesignMode(nsAString & aDesignMode) return NS_OK; } -NS_IMETHODIMP -nsHTMLDocument::SetDesignMode(const nsAString & aDesignMode) +nsresult +nsHTMLDocument::ChangeContentEditableCount(nsIContent *aElement, + PRInt32 aChange) { + NS_ASSERTION(mContentEditableCount + aChange >= 0, + "Trying to decrement too much."); + + mContentEditableCount += aChange; + + if (mParser) { + return NS_OK; + } + + EditingState oldState = mEditingState; + + nsresult rv = EditingStateChanged(); + NS_ENSURE_SUCCESS(rv, rv); + + if (oldState == mEditingState && mEditingState == eContentEditable) { + // We just changed the contentEditable state of a node, we need to reset + // the spellchecking state of that node. + nsCOMPtr node = do_QueryInterface(aElement); + if (node) { + nsPIDOMWindow *window = GetWindow(); + if (!window) + return NS_ERROR_FAILURE; + + nsIDocShell *docshell = window->GetDocShell(); + if (!docshell) + return NS_ERROR_FAILURE; + + nsCOMPtr editorDocShell = + do_QueryInterface(docshell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr editor; + rv = editorDocShell->GetEditor(getter_AddRefs(editor)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr range; + rv = NS_NewRange(getter_AddRefs(range)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = range->SelectNode(node); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr spellChecker; + rv = editor->GetInlineSpellChecker(PR_FALSE, + getter_AddRefs(spellChecker)); + NS_ENSURE_SUCCESS(rv, rv); + + if (spellChecker) { + rv = spellChecker->SpellCheckRange(range); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + return NS_OK; +} + +static void +NotifyEditableStateChange(nsINode *aNode, nsIDocument *aDocument, + PRBool aEditable) +{ + PRUint32 i, n = aNode->GetChildCount(); + for (i = 0; i < n; ++i) { + nsIContent *child = aNode->GetChildAt(i); + if (child->HasFlag(NODE_IS_EDITABLE) != aEditable) { + aDocument->ContentStatesChanged(child, nsnull, + NS_EVENT_STATE_MOZ_READONLY | + NS_EVENT_STATE_MOZ_READWRITE); + } + NotifyEditableStateChange(child, aDocument, aEditable); + } +} + +nsresult +nsHTMLDocument::TurnEditingOff() +{ + NS_ASSERTION(mEditingState != eOff, "Editing is already off."); + + nsPIDOMWindow *window = GetWindow(); + if (!window) + return NS_ERROR_FAILURE; + + nsIDocShell *docshell = window->GetDocShell(); + if (!docshell) + return NS_ERROR_FAILURE; + + nsresult rv; + nsCOMPtr editorDocShell = + do_QueryInterface(docshell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr editor; + rv = editorDocShell->GetEditor(getter_AddRefs(editor)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr editSession = do_GetInterface(docshell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // turn editing off + rv = editSession->TearDownEditorOnWindow(window, PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr editorss = do_QueryInterface(editor, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (!HasFlag(NODE_IS_EDITABLE)) { + editorss->RemoveOverrideStyleSheet(NS_LITERAL_STRING("resource:/res/contenteditable.css")); + editorss->RemoveOverrideStyleSheet(NS_LITERAL_STRING("resource:/res/designmode.css")); + + rv = docshell->SetAllowJavascript(mScriptsEnabled); + NS_ENSURE_SUCCESS(rv, rv); + + rv = docshell->SetAllowPlugins(mPluginsEnabled); + NS_ENSURE_SUCCESS(rv, rv); + } + + mEditingState = eOff; + + return NS_OK; +} + +nsresult +nsHTMLDocument::EditingStateChanged() +{ + if (mEditingState == eSettingUp) { + // XXX We shouldn't recurse. + return NS_OK; + } + + PRBool designMode = HasFlag(NODE_IS_EDITABLE); + EditingState newState = designMode ? eDesignMode : + (mContentEditableCount > 0 ? eContentEditable : eOff); + if (mEditingState == newState) { + // No changes in editing mode. + return NS_OK; + } + + if (newState == eOff) { + // Editing is being turned off. + return TurnEditingOff(); + } + // get editing session nsPIDOMWindow *window = GetWindow(); if (!window) @@ -3738,6 +3892,128 @@ nsHTMLDocument::SetDesignMode(const nsAString & aDesignMode) if (!docshell) return NS_ERROR_FAILURE; + nsresult rv; + nsCOMPtr editSession = do_GetInterface(docshell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + PRBool makeWindowEditable = (mEditingState == eOff); + if (makeWindowEditable) { + // Editing is being turned on (through designMode or contentEditable) + // Turn on editor. + // XXX This can cause flushing which can change the editing state, so make + // sure to avoid recursing. + EditingState oldState = mEditingState; + mEditingState = eSettingUp; + + rv = editSession->MakeWindowEditable(window, "html", PR_FALSE, PR_FALSE, + PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + + mEditingState = oldState; + } + + // XXX Need to call TearDownEditorOnWindow for all failures. + nsCOMPtr editorDocShell = + do_QueryInterface(docshell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr editor; + rv = editorDocShell->GetEditor(getter_AddRefs(editor)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr editorss = do_QueryInterface(editor, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + editorss->AddOverrideStyleSheet(NS_LITERAL_STRING("resource:/res/contenteditable.css")); + + // Should we update the editable state of all the nodes in the document? We + // need to do this when the designMode value changes, as that overrides + // specific states on the elements. + PRBool updateState; + + PRBool spellRecheckAll = PR_FALSE; + if (designMode) { + // designMode is being turned on (overrides contentEditable). + editorss->AddOverrideStyleSheet(NS_LITERAL_STRING("resource:/res/designmode.css")); + + // Store scripting and plugins state. + PRBool tmp; + rv = docshell->GetAllowJavascript(&tmp); + NS_ENSURE_SUCCESS(rv, rv); + + mScriptsEnabled = tmp; + + rv = docshell->GetAllowPlugins(&tmp); + NS_ENSURE_SUCCESS(rv, rv); + + mPluginsEnabled = tmp; + + updateState = PR_TRUE; + spellRecheckAll = mEditingState == eContentEditable; + } + else if (mEditingState == eDesignMode) { + // designMode is being turned off (contentEditable is still on). + editorss->RemoveOverrideStyleSheet(NS_LITERAL_STRING("resource:/res/designmode.css")); + + rv = docshell->SetAllowJavascript(mScriptsEnabled); + NS_ENSURE_SUCCESS(rv, rv); + + rv = docshell->SetAllowPlugins(mPluginsEnabled); + NS_ENSURE_SUCCESS(rv, rv); + + updateState = PR_TRUE; + } + else { + // contentEditable is being turned on (and designMode is off). + updateState = PR_FALSE; + } + + mEditingState = newState; + + if (makeWindowEditable) { + // Set the editor to not insert br's on return when in p + // elements by default. + // XXX Do we only want to do this for designMode? + PRBool unused; + rv = ExecCommand(NS_LITERAL_STRING("insertBrOnReturn"), PR_FALSE, + NS_LITERAL_STRING("false"), &unused); + + if (NS_FAILED(rv)) { + // Editor setup failed. Editing is not on after all. + // XXX Should we reset the editable flag on nodes? + editSession->TearDownEditorOnWindow(window, PR_TRUE); + mEditingState = eOff; + + return rv; + } + } + + if (updateState) { + mozAutoDocUpdate upd(this, UPDATE_CONTENT_STATE, PR_TRUE); + NotifyEditableStateChange(this, this, !designMode); + } + + // Resync the editor's spellcheck state. + if (spellRecheckAll) { + nsCOMPtr selcon; + nsresult rv = editor->GetSelectionController(getter_AddRefs(selcon)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr spellCheckSelection; + rv = selcon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, + getter_AddRefs(spellCheckSelection)); + if (NS_SUCCEEDED(rv)) { + spellCheckSelection->RemoveAllRanges(); + } + } + editor->SyncRealTimeSpell(); + + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLDocument::SetDesignMode(const nsAString & aDesignMode) +{ nsresult rv = NS_OK; if (!nsContentUtils::IsCallerTrustedForWrite()) { @@ -3751,53 +4027,14 @@ nsHTMLDocument::SetDesignMode(const nsAString & aDesignMode) } } - nsCOMPtr editSession = do_GetInterface(docshell); - if (!editSession) - return NS_ERROR_FAILURE; + PRBool editableMode = HasFlag(NODE_IS_EDITABLE); + if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) { + SetEditableFlag(!editableMode); - if (aDesignMode.LowerCaseEqualsLiteral("on") && !mEditingIsOn) { - rv = editSession->MakeWindowEditable(window, "html", PR_FALSE); - - if (NS_SUCCEEDED(rv)) { - // now that we've successfully created the editor, we can - // reset our flag - mEditingIsOn = PR_TRUE; - - // Set the editor to not insert br's on return when in p - // elements by default. - PRBool unused; - rv = ExecCommand(NS_LITERAL_STRING("insertBrOnReturn"), PR_FALSE, - NS_LITERAL_STRING("false"), &unused); - - if (NS_FAILED(rv)) { - // Editor setup failed. Editing is is not on after all. - - editSession->TearDownEditorOnWindow(window); - - mEditingIsOn = PR_FALSE; - } else { - // Resync the editor's spellcheck state, since when the editor was - // created it asked us whether designMode was on, and we told it no. - // Note that reporting "yes" (by setting mEditingIsOn true before - // calling MakeWindowEditable()) exposed several crash bugs (see bugs - // 348497, 348981). - nsCOMPtr editor; - rv = editSession->GetEditorForWindow(window, getter_AddRefs(editor)); - if (NS_SUCCEEDED(rv)) { - editor->SyncRealTimeSpell(); - } - } - } - } else if (aDesignMode.LowerCaseEqualsLiteral("off") && mEditingIsOn) { - // turn editing off - rv = editSession->TearDownEditorOnWindow(window); - - if (NS_SUCCEEDED(rv)) { - mEditingIsOn = PR_FALSE; - } + return EditingStateChanged(); } - return rv; + return NS_OK; } nsresult @@ -3982,19 +4219,21 @@ nsHTMLDocument::ConvertToMidasInternalCommand(const nsAString & inCommandID, NS_ConvertUTF16toUTF8 convertedParam(inParam); // check to see if we need to convert the parameter - PRUint32 j; - for (j = 0; j < MidasParamCount; ++j) { - if (convertedParam.Equals(gMidasParamTable[j].incomingParamString, - nsCaseInsensitiveCStringComparator())) { - outParam.Assign(gMidasParamTable[j].internalParamString); - break; + if (outCommandID.EqualsLiteral("cmd_paragraphState")) { + PRUint32 j; + for (j = 0; j < MidasParamCount; ++j) { + if (convertedParam.Equals(gMidasParamTable[j].incomingParamString, + nsCaseInsensitiveCStringComparator())) { + outParam.Assign(gMidasParamTable[j].internalParamString); + break; + } } - } - // if we didn't convert the parameter, just - // pass through the parameter that was passed to us - if (j == MidasParamCount) + return j != MidasParamCount; + } + else { outParam.Assign(convertedParam); + } } } } // end else for useNewParam (do convert existing param) @@ -4069,12 +4308,12 @@ nsHTMLDocument::ExecCommand(const nsAString & commandID, *_retval = PR_FALSE; // if editing is not on, bail - if (!mEditingIsOn) + if (!IsEditingOn()) return NS_ERROR_FAILURE; // if they are requesting UI from us, let's fail since we have no UI if (doShowUI) - return NS_ERROR_NOT_IMPLEMENTED; + return NS_OK; nsresult rv = NS_OK; @@ -4105,7 +4344,7 @@ nsHTMLDocument::ExecCommand(const nsAString & commandID, PRBool isBool, boolVal; if (!ConvertToMidasInternalCommand(commandID, value, cmdToDispatch, paramStr, isBool, boolVal)) - return NS_ERROR_NOT_IMPLEMENTED; + return NS_OK; if (!isBool && paramStr.IsEmpty()) { rv = cmdMgr->DoCommand(cmdToDispatch.get(), nsnull, window); @@ -4144,7 +4383,7 @@ nsHTMLDocument::ExecCommandShowHelp(const nsAString & commandID, *_retval = PR_FALSE; // if editing is not on, bail - if (!mEditingIsOn) + if (!IsEditingOn()) return NS_ERROR_FAILURE; return NS_ERROR_NOT_IMPLEMENTED; @@ -4159,7 +4398,7 @@ nsHTMLDocument::QueryCommandEnabled(const nsAString & commandID, *_retval = PR_FALSE; // if editing is not on, bail - if (!mEditingIsOn) + if (!IsEditingOn()) return NS_ERROR_FAILURE; // get command manager and dispatch command to our window if it's acceptable @@ -4190,7 +4429,7 @@ nsHTMLDocument::QueryCommandIndeterm(const nsAString & commandID, *_retval = PR_FALSE; // if editing is not on, bail - if (!mEditingIsOn) + if (!IsEditingOn()) return NS_ERROR_FAILURE; // get command manager and dispatch command to our window if it's acceptable @@ -4232,7 +4471,7 @@ nsHTMLDocument::QueryCommandState(const nsAString & commandID, PRBool *_retval) *_retval = PR_FALSE; // if editing is not on, bail - if (!mEditingIsOn) + if (!IsEditingOn()) return NS_ERROR_FAILURE; // get command manager and dispatch command to our window if it's acceptable @@ -4294,7 +4533,7 @@ nsHTMLDocument::QueryCommandSupported(const nsAString & commandID, *_retval = PR_FALSE; // if editing is not on, bail - if (!mEditingIsOn) + if (!IsEditingOn()) return NS_ERROR_FAILURE; return NS_ERROR_NOT_IMPLEMENTED; @@ -4308,7 +4547,7 @@ nsHTMLDocument::QueryCommandText(const nsAString & commandID, _retval.SetLength(0); // if editing is not on, bail - if (!mEditingIsOn) + if (!IsEditingOn()) return NS_ERROR_FAILURE; return NS_ERROR_NOT_IMPLEMENTED; @@ -4322,7 +4561,7 @@ nsHTMLDocument::QueryCommandValue(const nsAString & commandID, _retval.SetLength(0); // if editing is not on, bail - if (!mEditingIsOn) + if (!IsEditingOn()) return NS_ERROR_FAILURE; // get command manager and dispatch command to our window if it's acceptable diff --git a/mozilla/content/html/document/src/nsHTMLDocument.h b/mozilla/content/html/document/src/nsHTMLDocument.h index a419601af1b..d4ead1465de 100644 --- a/mozilla/content/html/document/src/nsHTMLDocument.h +++ b/mozilla/content/html/document/src/nsHTMLDocument.h @@ -59,6 +59,8 @@ #include "nsICommandManager.h" +class nsIEditor; +class nsIEditorDocShell; class nsIParser; class nsIURI; class nsIMarkupDocumentViewer; @@ -207,6 +209,13 @@ public: nsIContent** aResult); #endif + nsresult ChangeContentEditableCount(nsIContent *aElement, PRInt32 aChange); + + virtual PRBool IsEditingOn() + { + return mEditingState != eOff; + } + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsHTMLDocument, nsDocument) protected: @@ -365,7 +374,20 @@ protected: PRBool& isBoolean, PRBool& boolValue); nsCOMPtr mMidasCommandManager; - PRBool mEditingIsOn; + + nsresult TurnEditingOff(); + nsresult EditingStateChanged(); + + PRUint32 mContentEditableCount; + enum EditingState { + eSettingUp = -1, + eOff = 0, + eDesignMode, + eContentEditable + }; + EditingState mEditingState; + PRPackedBool mScriptsEnabled; + PRPackedBool mPluginsEnabled; nsresult DoClipboardSecurityCheck(PRBool aPaste); static jsval sCutCopyInternal_id; diff --git a/mozilla/content/html/document/src/nsIHTMLDocument.h b/mozilla/content/html/document/src/nsIHTMLDocument.h index 6f01e2c3112..b4e2279eee1 100644 --- a/mozilla/content/html/document/src/nsIHTMLDocument.h +++ b/mozilla/content/html/document/src/nsIHTMLDocument.h @@ -55,8 +55,8 @@ class nsIDOMHTMLBodyElement; class nsIScriptElement; #define NS_IHTMLDOCUMENT_IID \ -{ 0xcfe72003, 0xcc90, 0x4624, \ - { 0xb4, 0x1b, 0xc3, 0x14, 0x1d, 0x31, 0x7a, 0x71 } } +{ 0xf6aa3582, 0x67c3, 0x4f42, \ + { 0xb6, 0xee, 0x89, 0x19, 0x24, 0x5c, 0x15, 0x89 } } /** @@ -127,6 +127,23 @@ public: * the document that are of type nsIContent::eHTML_FORM_CONTROL). */ virtual nsContentList* GetFormControls() = 0; + + /** + * Should be called when an element's editable changes as a result of + * changing its contentEditable attribute/property. + * + * @param aElement the element for which the contentEditable + * attribute/property was changed + * @param aChange +1 if the contentEditable attribute/property was changed to + * true, -1 if it was changed to false + */ + virtual nsresult ChangeContentEditableCount(nsIContent *aElement, + PRInt32 aChange) = 0; + + /** + * Returns whether the document is editable. + */ + virtual PRBool IsEditingOn() = 0; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIHTMLDocument, NS_IHTMLDOCUMENT_IID) diff --git a/mozilla/content/xul/content/src/nsXULElement.cpp b/mozilla/content/xul/content/src/nsXULElement.cpp index be735fd7173..b3748bab53f 100644 --- a/mozilla/content/xul/content/src/nsXULElement.cpp +++ b/mozilla/content/xul/content/src/nsXULElement.cpp @@ -1040,6 +1040,15 @@ nsXULElement::AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aName, HideWindowChrome(aValue && NS_LITERAL_STRING("true").Equals(*aValue)); } + // handle :read-only/:read-write + nsIDocument *document = GetCurrentDoc(); + if (aName == nsGkAtoms::readonly && document) { + mozAutoDocUpdate upd(document, UPDATE_CONTENT_STATE, PR_TRUE); + document->ContentStatesChanged(this, nsnull, + NS_EVENT_STATE_MOZ_READONLY | + NS_EVENT_STATE_MOZ_READWRITE); + } + // XXX need to check if they're changing an event handler: if // so, then we need to unhook the old one. Or something. } @@ -2098,6 +2107,22 @@ nsXULElement::AddPopupListener(nsIAtom* aName) return NS_OK; } +PRInt32 +nsXULElement::IntrinsicState() const +{ + PRInt32 state = nsGenericElement::IntrinsicState(); + + const nsIAtom* tag = Tag(); + if (GetNameSpaceID() == kNameSpaceID_XUL && + (tag == nsGkAtoms::textbox || tag == nsGkAtoms::textarea) && + !HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) { + state |= NS_EVENT_STATE_MOZ_READWRITE; + state &= ~NS_EVENT_STATE_MOZ_READONLY; + } + + return state; +} + //---------------------------------------------------------------------- nsGenericElement::nsAttrInfo diff --git a/mozilla/content/xul/content/src/nsXULElement.h b/mozilla/content/xul/content/src/nsXULElement.h index 6468d00fa9f..4fc777e14d5 100644 --- a/mozilla/content/xul/content/src/nsXULElement.h +++ b/mozilla/content/xul/content/src/nsXULElement.h @@ -558,6 +558,7 @@ public: NS_DECL_NSIDOMXULELEMENT virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const; + virtual PRInt32 IntrinsicState() const; nsresult GetStyle(nsIDOMCSSStyleDeclaration** aStyle); diff --git a/mozilla/docshell/base/nsDocShellEditorData.cpp b/mozilla/docshell/base/nsDocShellEditorData.cpp index 2480a2d8170..90c8b410369 100644 --- a/mozilla/docshell/base/nsDocShellEditorData.cpp +++ b/mozilla/docshell/base/nsDocShellEditorData.cpp @@ -73,7 +73,7 @@ nsDocShellEditorData::~nsDocShellEditorData() nsCOMPtr domWindow = do_GetInterface(mDocShell); // This will eventually call nsDocShellEditorData::SetEditor(nsnull) // which will call mEditorPreDestroy() and delete the editor - mEditingSession->TearDownEditorOnWindow(domWindow); + mEditingSession->TearDownEditorOnWindow(domWindow, PR_TRUE); } else if (mEditor) // Should never have this w/o nsEditingSession! { @@ -104,7 +104,8 @@ nsDocShellEditorData::MakeEditable(PRBool inWaitForUriLoad /*, PRBool inEditable mEditor = nsnull; } - mMakeEditable = PR_TRUE; + if (inWaitForUriLoad) + mMakeEditable = PR_TRUE; return NS_OK; } @@ -191,6 +192,8 @@ nsDocShellEditorData::SetEditor(nsIEditor *inEditor) } mEditor = inEditor; // owning addref + if (!mEditor) + mMakeEditable = PR_FALSE; } return NS_OK; diff --git a/mozilla/docshell/base/nsWebShell.cpp b/mozilla/docshell/base/nsWebShell.cpp index 14ffba20e3d..3000869df18 100644 --- a/mozilla/docshell/base/nsWebShell.cpp +++ b/mozilla/docshell/base/nsWebShell.cpp @@ -772,7 +772,11 @@ nsWebShell::OnLinkClick(nsIContent* aContent, if (mFiredUnloadEvent) { return NS_OK; } - + + if (aContent->HasFlag(NODE_IS_EDITABLE)) { + return NS_OK; + } + nsCOMPtr ev = new OnLinkClickEvent(this, aContent, aURI, aTargetSpec, aPostDataStream, aHeadersDataStream); @@ -800,6 +804,10 @@ nsWebShell::OnLinkClickSync(nsIContent *aContent, return NS_OK; } + if (aContent->HasFlag(NODE_IS_EDITABLE)) { + return NS_OK; + } + { // defer to an external protocol handler if necessary... nsCOMPtr extProtService = do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID); @@ -895,6 +903,10 @@ nsWebShell::OnOverLink(nsIContent* aContent, nsIURI* aURI, const PRUnichar* aTargetSpec) { + if (aContent->HasFlag(NODE_IS_EDITABLE)) { + return NS_OK; + } + nsCOMPtr browserChrome2 = do_GetInterface(mTreeOwner); nsresult rv = NS_ERROR_FAILURE; diff --git a/mozilla/dom/public/idl/html/nsIDOMNSHTMLElement.idl b/mozilla/dom/public/idl/html/nsIDOMNSHTMLElement.idl index b978b4d59c8..59cae2ed339 100644 --- a/mozilla/dom/public/idl/html/nsIDOMNSHTMLElement.idl +++ b/mozilla/dom/public/idl/html/nsIDOMNSHTMLElement.idl @@ -38,7 +38,7 @@ #include "domstubs.idl" -[scriptable, uuid(b0a29b0a-ce2b-4cdf-b98a-4c7ed994d6e2)] +[scriptable, uuid(eac0a4ee-2e4f-403c-9b77-5cf32cfb42f7)] interface nsIDOMNSHTMLElement : nsISupports { readonly attribute long offsetTop; @@ -60,6 +60,8 @@ interface nsIDOMNSHTMLElement : nsISupports attribute long tabIndex; + attribute DOMString contentEditable; + void blur(); void focus(); diff --git a/mozilla/editor/composer/public/nsIEditingSession.idl b/mozilla/editor/composer/public/nsIEditingSession.idl index 8f2635f16f6..5714bbf0ccf 100644 --- a/mozilla/editor/composer/public/nsIEditingSession.idl +++ b/mozilla/editor/composer/public/nsIEditingSession.idl @@ -43,7 +43,7 @@ interface nsIEditor; -[scriptable, uuid(d39fd2b4-3978-45d2-a4be-ba448171b61b)] +[scriptable, uuid(aee80d50-2065-4411-834d-0cadfb649a19)] interface nsIEditingSession : nsISupports { @@ -68,8 +68,16 @@ interface nsIEditingSession : nsISupports * Make this window editable * @param aWindow nsIDOMWindow, the window the embedder needs to make editable * @param aEditorType string, "html" "htmlsimple" "text" "textsimple" + * @param aMakeWholeDocumentEditable if PR_TRUE make the whole document in + * aWindow editable, otherwise it's the + * embedder who should make the document + * (or part of it) editable. + * @param aInteractive if PR_FALSE turn off scripting and plugins */ - void makeWindowEditable(in nsIDOMWindow window, in string aEditorType, in boolean doAfterUriLoad); + void makeWindowEditable(in nsIDOMWindow window, in string aEditorType, + in boolean doAfterUriLoad, + in boolean aMakeWholeDocumentEditable, + in boolean aInteractive); /** * Test whether a specific window has had its editable flag set; it may have an editor @@ -93,7 +101,7 @@ interface nsIEditingSession : nsISupports /** * Destroy editor and related support objects */ - void tearDownEditorOnWindow(in nsIDOMWindow window); + void tearDownEditorOnWindow(in nsIDOMWindow window, in boolean aStopEditing); void setEditorOnControllers(in nsIDOMWindow aWindow, in nsIEditor aEditor); diff --git a/mozilla/editor/composer/src/nsEditingSession.cpp b/mozilla/editor/composer/src/nsEditingSession.cpp index 24cd54abc99..966fbfa4835 100644 --- a/mozilla/editor/composer/src/nsEditingSession.cpp +++ b/mozilla/editor/composer/src/nsEditingSession.cpp @@ -95,6 +95,7 @@ nsEditingSession::nsEditingSession() : mDoneSetup(PR_FALSE) , mCanCreateEditor(PR_FALSE) +, mInteractive(PR_FALSE) , mScriptsEnabled(PR_TRUE) , mPluginsEnabled(PR_TRUE) , mProgressListenerRegistered(PR_FALSE) @@ -128,14 +129,18 @@ NS_IMPL_ISUPPORTS3(nsEditingSession, nsIEditingSession, nsIWebProgressListener, aEditorType string, "html" "htmlsimple" "text" "textsimple" void makeWindowEditable(in nsIDOMWindow aWindow, in string aEditorType, - in boolean aDoAfterUriLoad); + in boolean aDoAfterUriLoad, + in boolean aMakeWholeDocumentEditable, + in boolean aInteractive); ----------------------------------------------------------------------------*/ #define DEFAULT_EDITOR_TYPE "html" NS_IMETHODIMP nsEditingSession::MakeWindowEditable(nsIDOMWindow *aWindow, const char *aEditorType, - PRBool aDoAfterUriLoad) + PRBool aDoAfterUriLoad, + PRBool aMakeWholeDocumentEditable, + PRBool aInteractive) { mEditorType.Truncate(); mEditorFlags = 0; @@ -146,27 +151,42 @@ nsEditingSession::MakeWindowEditable(nsIDOMWindow *aWindow, if (!docShell) return NS_ERROR_FAILURE; nsresult rv; - // Disable JavaScript in this document: - PRBool tmp; - rv = docShell->GetAllowJavascript(&tmp); - NS_ENSURE_SUCCESS(rv, rv); + if (aMakeWholeDocumentEditable) { + nsCOMPtr domDoc; + rv = aWindow->GetDocument(getter_AddRefs(domDoc)); + NS_ENSURE_SUCCESS(rv, rv); - mScriptsEnabled = tmp; + nsCOMPtr doc = do_QueryInterface(domDoc, &rv); + NS_ENSURE_SUCCESS(rv, rv); - rv = docShell->SetAllowJavascript(PR_FALSE); - NS_ENSURE_SUCCESS(rv, rv); + doc->SetEditableFlag(PR_TRUE); + } - // Disable plugins in this document: - rv = docShell->GetAllowPlugins(&tmp); - NS_ENSURE_SUCCESS(rv, rv); + mInteractive = aInteractive; - mPluginsEnabled = tmp; + if (!mInteractive) { + // Disable JavaScript in this document: + PRBool tmp; + rv = docShell->GetAllowJavascript(&tmp); + NS_ENSURE_SUCCESS(rv, rv); - rv = docShell->SetAllowPlugins(PR_FALSE); - NS_ENSURE_SUCCESS(rv, rv); + mScriptsEnabled = tmp; + + rv = docShell->SetAllowJavascript(PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + // Disable plugins in this document: + rv = docShell->GetAllowPlugins(&tmp); + NS_ENSURE_SUCCESS(rv, rv); + + mPluginsEnabled = tmp; + + rv = docShell->SetAllowPlugins(PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + } // Always remove existing editor - TearDownEditorOnWindow(aWindow); + TearDownEditorOnWindow(aWindow, PR_FALSE); // Tells embedder that startup is in progress mEditorStatus = eEditorCreationInProgress; @@ -215,7 +235,7 @@ nsEditingSession::MakeWindowEditable(nsIDOMWindow *aWindow, // Since this is used only when editing an existing page, // it IS ok to destroy current editor if (NS_FAILED(rv)) - TearDownEditorOnWindow(aWindow); + TearDownEditorOnWindow(aWindow, PR_FALSE); } return rv; } @@ -361,6 +381,10 @@ nsEditingSession::SetupEditorOnWindow(nsIDOMWindow *aWindow) needHTMLController = PR_TRUE; } + if (mInteractive) { + mEditorFlags |= nsIPlaintextEditor::eEditorAllowInteraction; + } + // make the UI state maintainer nsComposerCommandsUpdater *stateMaintainer; NS_NEWXPCOM(stateMaintainer, nsComposerCommandsUpdater); @@ -390,13 +414,15 @@ nsEditingSession::SetupEditorOnWindow(nsIDOMWindow *aWindow) nsIDocShell *docShell = GetDocShellFromWindow(aWindow); if (!docShell) return NS_ERROR_FAILURE; - // Disable animation of images in this document: - nsCOMPtr utils(do_GetInterface(aWindow)); - if (!utils) return NS_ERROR_FAILURE; + if (!mInteractive) { + // Disable animation of images in this document: + nsCOMPtr utils(do_GetInterface(aWindow)); + if (!utils) return NS_ERROR_FAILURE; - rv = utils->GetImageAnimationMode(&mImageAnimationMode); - if (NS_FAILED(rv)) return rv; - utils->SetImageAnimationMode(imgIContainer::kDontAnimMode); + rv = utils->GetImageAnimationMode(&mImageAnimationMode); + if (NS_FAILED(rv)) return rv; + utils->SetImageAnimationMode(imgIContainer::kDontAnimMode); + } // create and set editor nsCOMPtr editorDocShell = do_QueryInterface(docShell, &rv); @@ -480,10 +506,12 @@ nsEditingSession::SetupEditorOnWindow(nsIDOMWindow *aWindow) TearDownEditorOnWindow - void tearDownEditorOnWindow (in nsIDOMWindow aWindow); + void tearDownEditorOnWindow (in nsIDOMWindow aWindow, + in boolean aStopEditing); ----------------------------------------------------------------------------*/ NS_IMETHODIMP -nsEditingSession::TearDownEditorOnWindow(nsIDOMWindow *aWindow) +nsEditingSession::TearDownEditorOnWindow(nsIDOMWindow *aWindow, + PRBool aStopEditing) { if (!mDoneSetup) return NS_OK; @@ -501,23 +529,7 @@ nsEditingSession::TearDownEditorOnWindow(nsIDOMWindow *aWindow) mDoneSetup = PR_FALSE; - nsCOMPtr dom_doc; - aWindow->GetDocument(getter_AddRefs(dom_doc)); - - nsCOMPtr html_doc(do_QueryInterface(dom_doc)); - PRBool isMidas = PR_FALSE; - - if (html_doc) { - nsAutoString designMode; - html_doc->GetDesignMode(designMode); - - isMidas = designMode.EqualsLiteral("on"); - } - - if (isMidas) { - // We're tearing down a midas editor, unregister callbacks since - // we're all done editing here. - + if (aStopEditing) { nsCOMPtr webProgress = do_GetInterface(docShell); if (webProgress) { webProgress->RemoveProgressListener(this); @@ -622,7 +634,7 @@ nsEditingSession::TearDownEditorOnWindow(nsIDOMWindow *aWindow) mHTMLCommandControllerId = 0; } - if (isMidas) { + if (aStopEditing && !mInteractive) { // Make things the way they were before we started editing. if (mScriptsEnabled) { docShell->SetAllowJavascript(PR_TRUE); @@ -972,7 +984,7 @@ nsEditingSession::StartDocumentLoad(nsIWebProgress *aWebProgress, aWebProgress->GetDOMWindow(getter_AddRefs(domWindow)); if (domWindow) { - TearDownEditorOnWindow(domWindow); + TearDownEditorOnWindow(domWindow, PR_FALSE); } if (aIsToBeMadeEditable) @@ -1042,27 +1054,45 @@ nsEditingSession::EndDocumentLoad(nsIWebProgress *aWebProgress, if (makeEditable) { - mCanCreateEditor = PR_FALSE; - rv = SetupEditorOnWindow(domWindow); + // do we already have an editor here? + nsCOMPtr editor; + rv = editorDocShell->GetEditor(getter_AddRefs(editor)); if (NS_FAILED(rv)) + return rv; + if (!editor) { - // If we had an error, setup timer to load a blank page later - if (mLoadBlankDocTimer) + mCanCreateEditor = PR_FALSE; + rv = SetupEditorOnWindow(domWindow); + if (NS_FAILED(rv)) { - // Must cancel previous timer? - mLoadBlankDocTimer->Cancel(); - mLoadBlankDocTimer = NULL; - } + // If we had an error, setup timer to load a blank page later + if (mLoadBlankDocTimer) + { + // Must cancel previous timer? + mLoadBlankDocTimer->Cancel(); + mLoadBlankDocTimer = NULL; + } - mLoadBlankDocTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); - if (NS_FAILED(rv)) return rv; + mLoadBlankDocTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_FAILED(rv)) return rv; - mEditorStatus = eEditorCreationInProgress; - mLoadBlankDocTimer->InitWithFuncCallback( - nsEditingSession::TimerCallback, - (void*)docShell, - 10, nsITimer::TYPE_ONE_SHOT); + mEditorStatus = eEditorCreationInProgress; + mLoadBlankDocTimer->InitWithFuncCallback( + nsEditingSession::TimerCallback, + (void*)docShell, + 10, nsITimer::TYPE_ONE_SHOT); + } } + + // XXX This should move somewhere else! + nsCOMPtr domDoc; + rv = domWindow->GetDocument(getter_AddRefs(domDoc)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr doc = do_QueryInterface(domDoc, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + doc->SetEditableFlag(PR_TRUE); } } return rv; @@ -1144,7 +1174,7 @@ nsEditingSession::EndPageLoad(nsIWebProgress *aWebProgress, #if 0 // Shouldn't we do this when we want to edit sub-frames? - return MakeWindowEditable(domWindow, "html", PR_FALSE); + return MakeWindowEditable(domWindow, "html", PR_FALSE, mInteractive); #else return NS_OK; #endif diff --git a/mozilla/editor/composer/src/nsEditingSession.h b/mozilla/editor/composer/src/nsEditingSession.h index f8bec766f80..5929ade09b4 100644 --- a/mozilla/editor/composer/src/nsEditingSession.h +++ b/mozilla/editor/composer/src/nsEditingSession.h @@ -129,6 +129,8 @@ protected: // before creating an editor PRPackedBool mCanCreateEditor; + PRPackedBool mInteractive; + // True if scripts were enabled before the editor turned scripts // off, otherwise false. PRPackedBool mScriptsEnabled; diff --git a/mozilla/editor/composer/src/res/EditorOverride.css b/mozilla/editor/composer/src/res/EditorOverride.css index f2b137e6dea..5b43ca9a14c 100644 --- a/mozilla/editor/composer/src/res/EditorOverride.css +++ b/mozilla/editor/composer/src/res/EditorOverride.css @@ -35,6 +35,10 @@ * * ***** END LICENSE BLOCK ***** */ +*|* { + -moz-user-modify: read-write; +} + /* Styles to alter look of things in the Editor content window * that should NOT be removed when we display in completely WYSIWYG * "Browser Preview" mode. diff --git a/mozilla/editor/docs/Editor_Embedding_Guide.html b/mozilla/editor/docs/Editor_Embedding_Guide.html index 696579ea80c..3c076f3f9dd 100644 --- a/mozilla/editor/docs/Editor_Embedding_Guide.html +++ b/mozilla/editor/docs/Editor_Embedding_Guide.html @@ -18,10 +18,12 @@ GetContentDOMWindow call.  Then simply call nsIWebBrowser->do_GetInterface on the nsIWebBrowser to retrieve the nsIEditingSession from it.  From there you call editingSession->MakeWindowEditable(domWindow, editortype, -PR_TRUE);    The first parameter is the nsIDOMWindow you -just retrieved, the second is the editor type you want to create and the +PR_TRUE, PR_FALSE);    The first parameter is the nsIDOMWindow +you just retrieved, the second is the editor type you want to create and the third is whether you want the window editable immediately or when the -document is done loading.  In calling this method the editor is +document is done loading, the fourth is whether you want the editor to make +the whole document editable, the fifth is whether you want to turn of +scripts, plugins, ...  In calling this method the editor is created underneath and the event listeners etc. are all prepared.

    nsCOMPtr<nsIDOMWindow> domWindow;
@@ -36,7 +38,8 @@ editingSession;
nsIWebBrowser->do_GetInterface(getter_AddRefs(editingSession));
    if (editingSession)
        -editingSession->MakeWindowEditable(domWindow, "html", PR_TRUE);

+editingSession->MakeWindowEditable(domWindow, "html", PR_TRUE, +PR_FALSE, PR_TRUE, PR_FALSE);

The valid editor types are:

    diff --git a/mozilla/editor/idl/nsIEditor.idl b/mozilla/editor/idl/nsIEditor.idl index 8760f3959db..ea7b634a94f 100644 --- a/mozilla/editor/idl/nsIEditor.idl +++ b/mozilla/editor/idl/nsIEditor.idl @@ -62,7 +62,7 @@ typedef short EDirection; [ptr] native nsIPresShellPtr(nsIPresShell); -[scriptable, uuid(470e18e4-2e82-48de-8850-7474cdbbd97d)] +[scriptable, uuid(dc81f464-89dd-47bf-bf21-10df3b65b956)] interface nsIEditor : nsISupports { @@ -562,4 +562,7 @@ interface nsIEditor : nsISupports /* Run unit tests. Noop in optimized builds */ void debugUnitTests(out long outNumTests, out long outNumTestsFailed); + + /* checks if a node is read-only or not */ + [notxpcom] boolean isModifiableNode(in nsIDOMNode aNode); }; diff --git a/mozilla/editor/idl/nsIPlaintextEditor.idl b/mozilla/editor/idl/nsIPlaintextEditor.idl index 12f5dbe41f2..8a3669f73e4 100644 --- a/mozilla/editor/idl/nsIPlaintextEditor.idl +++ b/mozilla/editor/idl/nsIPlaintextEditor.idl @@ -39,34 +39,23 @@ interface nsIDOMKeyEvent; -[scriptable, uuid(28dbb4d0-5fea-43f4-aca7-344fc2ecc4f9)] +[scriptable, uuid(35d74f2b-3d03-43c5-8ace-9d90c3e31244)] interface nsIPlaintextEditor : nsISupports { - /* the bits in an editor behavior mask. */ - const short eEditorPlaintextBit = 0; /* only plain text entry is allowed via events */ - const short eEditorSingleLineBit = 1; /* enter key and CR-LF handled specially */ - const short eEditorPasswordBit = 2; /* text is not entered into content, only a representative character */ - const short eEditorReadonlyBit = 3; /* editing events are disabled. Editor may still accept focus. */ - const short eEditorDisabledBit = 4; /* all events are disabled (like scrolling). Editor will not accept focus. */ - const short eEditorFilterInputBit = 5; /* text input is limited to certain character types, use mFilter */ - const short eEditorMailBit = 6; /* use mail-compose editting rules */ - const short eEditorUseAsyncUpdatesBit = 7; /* prevent immediate reflows and view refreshes */ - const short eEditorEnableWrapHackBit = 8; /* allow the editor to set font: monospace on the root node */ - const short eEditorWidgetBit = 9; /* bit for widgets */ - const short eEditorNoCSSBit = 10; /* this HTML editor should not create css styles */ - - const long eEditorPlaintextMask = 1; - const long eEditorSingleLineMask = 2; - const long eEditorPasswordMask = 4; - const long eEditorReadonlyMask = 8; - const long eEditorDisabledMask = 16; - const long eEditorFilterInputMask = 32; - const long eEditorMailMask = 64; - const long eEditorUseAsyncUpdatesMask = 128; - const long eEditorEnableWrapHackMask = 256; - const long eEditorWidgetMask = 512; - const long eEditorNoCSSMask = 1024; + // XXX Why aren't these in nsIEditor? + const long eEditorPlaintextMask = 0x0001; /* only plain text entry is allowed via events */ + const long eEditorSingleLineMask = 0x0002; /* enter key and CR-LF handled specially */ + const long eEditorPasswordMask = 0x0004; /* text is not entered into content, only a representative character */ + const long eEditorReadonlyMask = 0x0008; /* editing events are disabled. Editor may still accept focus. */ + const long eEditorDisabledMask = 0x0010; /* all events are disabled (like scrolling). Editor will not accept focus. */ + const long eEditorFilterInputMask = 0x0020; /* text input is limited to certain character types, use mFilter */ + const long eEditorMailMask = 0x0040; /* use mail-compose editing rules */ + const long eEditorUseAsyncUpdatesMask = 0x0080; /* prevent immediate reflows and view refreshes */ + const long eEditorEnableWrapHackMask = 0x0100; /* allow the editor to set font: monospace on the root node */ + const long eEditorWidgetMask = 0x0200; /* bit for widgets */ + const long eEditorNoCSSMask = 0x0400; /* this HTML editor should not create css styles */ + const long eEditorAllowInteraction = 0x0800; /* */ /* * The valid values for newlines handling. diff --git a/mozilla/editor/libeditor/base/DeleteElementTxn.cpp b/mozilla/editor/libeditor/base/DeleteElementTxn.cpp index 9ea120068a6..3b9cb5c91a7 100644 --- a/mozilla/editor/libeditor/base/DeleteElementTxn.cpp +++ b/mozilla/editor/libeditor/base/DeleteElementTxn.cpp @@ -58,11 +58,21 @@ DeleteElementTxn::DeleteElementTxn() { } -NS_IMETHODIMP DeleteElementTxn::Init(nsIDOMNode *aElement, +NS_IMETHODIMP DeleteElementTxn::Init(nsIEditor *aEditor, + nsIDOMNode *aElement, nsRangeUpdater *aRangeUpdater) { - if (!aElement) return NS_ERROR_NULL_POINTER; + if (!aEditor || !aElement) return NS_ERROR_NULL_POINTER; + mEditor = aEditor; mElement = do_QueryInterface(aElement); + nsresult result = mElement->GetParentNode(getter_AddRefs(mParent)); + if (NS_FAILED(result)) { return result; } + + // do nothing if the parent is read-only + if (mParent && !mEditor->IsModifiableNode(mParent)) { + return NS_ERROR_FAILURE; + } + mRangeUpdater = aRangeUpdater; return NS_OK; } @@ -76,8 +86,6 @@ NS_IMETHODIMP DeleteElementTxn::DoTransaction(void) if (!mElement) return NS_ERROR_NOT_INITIALIZED; - nsresult result = mElement->GetParentNode(getter_AddRefs(mParent)); - if (NS_FAILED(result)) { return result; } if (!mParent) { return NS_OK; } // this is a no-op, there's no parent to delete mElement from #ifdef NS_DEBUG @@ -105,7 +113,7 @@ NS_IMETHODIMP DeleteElementTxn::DoTransaction(void) #endif // remember which child mElement was (by remembering which child was next) - result = mElement->GetNextSibling(getter_AddRefs(mRefNode)); // can return null mRefNode + nsresult result = mElement->GetNextSibling(getter_AddRefs(mRefNode)); // can return null mRefNode // give range updater a chance. SelAdjDeleteNode() needs to be called *before* // we do the action, unlike some of the other nsRangeStore update methods. diff --git a/mozilla/editor/libeditor/base/DeleteElementTxn.h b/mozilla/editor/libeditor/base/DeleteElementTxn.h index 14610eef383..0337856d5ee 100644 --- a/mozilla/editor/libeditor/base/DeleteElementTxn.h +++ b/mozilla/editor/libeditor/base/DeleteElementTxn.h @@ -41,6 +41,7 @@ #include "EditTxn.h" #include "nsIDOMNode.h" +#include "nsIEditor.h" #include "nsCOMPtr.h" #define DELETE_ELEMENT_TXN_CID \ @@ -62,7 +63,7 @@ public: /** initialize the transaction. * @param aElement the node to delete */ - NS_IMETHOD Init(nsIDOMNode *aElement, nsRangeUpdater *aRangeUpdater); + NS_IMETHOD Init(nsIEditor *aEditor, nsIDOMNode *aElement, nsRangeUpdater *aRangeUpdater); private: DeleteElementTxn(); @@ -83,6 +84,9 @@ protected: /** next sibling to remember for undo/redo purposes */ nsCOMPtr mRefNode; + /** the editor for this transaction */ + nsIEditor* mEditor; + /** range updater object */ nsRangeUpdater *mRangeUpdater; diff --git a/mozilla/editor/libeditor/base/DeleteRangeTxn.cpp b/mozilla/editor/libeditor/base/DeleteRangeTxn.cpp index abbdbe73ce1..d3dae9d2bd4 100644 --- a/mozilla/editor/libeditor/base/DeleteRangeTxn.cpp +++ b/mozilla/editor/libeditor/base/DeleteRangeTxn.cpp @@ -88,6 +88,17 @@ NS_IMETHODIMP DeleteRangeTxn::Init(nsIEditor *aEditor, result = aRange->GetCommonAncestorContainer(getter_AddRefs(mCommonParent)); NS_ASSERTION((NS_SUCCEEDED(result)), "GetCommonParent failed."); + if (!mEditor->IsModifiableNode(mStartParent)) { + return NS_ERROR_FAILURE; + } + + if (mStartParent!=mEndParent && + (!mEditor->IsModifiableNode(mEndParent) || + !mEditor->IsModifiableNode(mCommonParent))) + { + return NS_ERROR_FAILURE; + } + #ifdef NS_DEBUG { PRUint32 count; @@ -236,8 +247,9 @@ DeleteRangeTxn::CreateTxnsToDeleteBetween(nsIDOMNode *aStartParent, numToDel = 1; else numToDel = aEndOffset-aStartOffset; - txn->Init(mEditor, textNode, aStartOffset, numToDel, mRangeUpdater); - AppendChild(txn); + result = txn->Init(mEditor, textNode, aStartOffset, numToDel, mRangeUpdater); + if (NS_SUCCEEDED(result)) + AppendChild(txn); NS_RELEASE(txn); } else @@ -265,8 +277,9 @@ DeleteRangeTxn::CreateTxnsToDeleteBetween(nsIDOMNode *aStartParent, if (NS_FAILED(result)) return result; if (!txn) return NS_ERROR_NULL_POINTER; - txn->Init(child, mRangeUpdater); - AppendChild(txn); + result = txn->Init(mEditor, child, mRangeUpdater); + if (NS_SUCCEEDED(result)) + AppendChild(txn); NS_RELEASE(txn); } } @@ -302,8 +315,9 @@ NS_IMETHODIMP DeleteRangeTxn::CreateTxnsToDeleteContent(nsIDOMNode *aParent, if (NS_FAILED(result)) return result; if (!txn) return NS_ERROR_NULL_POINTER; - txn->Init(mEditor, textNode, start, numToDelete, mRangeUpdater); - AppendChild(txn); + result = txn->Init(mEditor, textNode, start, numToDelete, mRangeUpdater); + if (NS_SUCCEEDED(result)) + AppendChild(txn); NS_RELEASE(txn); } } @@ -319,7 +333,7 @@ NS_IMETHODIMP DeleteRangeTxn::CreateTxnsToDeleteNodesBetween() nsresult result = iter->Init(mRange); if (NS_FAILED(result)) return result; - while (!iter->IsDone()) + while (!iter->IsDone() && NS_SUCCEEDED(result)) { nsCOMPtr node = do_QueryInterface(iter->GetCurrentNode()); if (!node) @@ -330,8 +344,9 @@ NS_IMETHODIMP DeleteRangeTxn::CreateTxnsToDeleteNodesBetween() if (NS_FAILED(result)) return result; if (!txn) return NS_ERROR_NULL_POINTER; - txn->Init(node, mRangeUpdater); - AppendChild(txn); + result = txn->Init(mEditor, node, mRangeUpdater); + if (NS_SUCCEEDED(result)) + AppendChild(txn); NS_RELEASE(txn); iter->Next(); } diff --git a/mozilla/editor/libeditor/base/DeleteTextTxn.cpp b/mozilla/editor/libeditor/base/DeleteTextTxn.cpp index e5e3f84fbb1..fd7c412b919 100644 --- a/mozilla/editor/libeditor/base/DeleteTextTxn.cpp +++ b/mozilla/editor/libeditor/base/DeleteTextTxn.cpp @@ -65,6 +65,11 @@ NS_IMETHODIMP DeleteTextTxn::Init(nsIEditor *aEditor, mEditor = aEditor; mElement = do_QueryInterface(aElement); + // do nothing if the node is read-only + if (!mEditor->IsModifiableNode(mElement)) { + return NS_ERROR_FAILURE; + } + mOffset = aOffset; mNumCharsToDelete = aNumCharsToDelete; NS_ASSERTION(0!=aNumCharsToDelete, "bad arg, numCharsToDelete"); diff --git a/mozilla/editor/libeditor/base/JoinElementTxn.cpp b/mozilla/editor/libeditor/base/JoinElementTxn.cpp index e0d631777af..2c0ebbc9450 100644 --- a/mozilla/editor/libeditor/base/JoinElementTxn.cpp +++ b/mozilla/editor/libeditor/base/JoinElementTxn.cpp @@ -57,6 +57,12 @@ NS_IMETHODIMP JoinElementTxn::Init(nsEditor *aEditor, if (!aEditor || !aLeftNode || !aRightNode) { return NS_ERROR_NULL_POINTER; } mEditor = aEditor; mLeftNode = do_QueryInterface(aLeftNode); + nsCOMPtrleftParent; + nsresult result = mLeftNode->GetParentNode(getter_AddRefs(leftParent)); + if (NS_FAILED(result)) return result; + if (!mEditor->IsModifiableNode(leftParent)) { + return NS_ERROR_FAILURE; + } mRightNode = do_QueryInterface(aRightNode); mOffset=0; return NS_OK; diff --git a/mozilla/editor/libeditor/base/nsEditPropertyAtomList.h b/mozilla/editor/libeditor/base/nsEditPropertyAtomList.h index 5890943749b..ac8ef52c1ce 100644 --- a/mozilla/editor/libeditor/base/nsEditPropertyAtomList.h +++ b/mozilla/editor/libeditor/base/nsEditPropertyAtomList.h @@ -166,6 +166,7 @@ EDITOR_ATOM(cssWhitespace, "white-space") EDITOR_ATOM(cssWidth, "width") EDITOR_ATOM(cssZIndex, "z-index") +EDITOR_ATOM(cssMozUserModify, "-moz-user-modify") EDITOR_ATOM(cssMozUserSelect, "-moz-user-select") EDITOR_ATOM(mozdirty, "_moz_dirty") diff --git a/mozilla/editor/libeditor/base/nsEditor.cpp b/mozilla/editor/libeditor/base/nsEditor.cpp index 8194e75e838..adbd4dc0bf8 100644 --- a/mozilla/editor/libeditor/base/nsEditor.cpp +++ b/mozilla/editor/libeditor/base/nsEditor.cpp @@ -3777,8 +3777,8 @@ nsEditor::IsEditable(nsIDOMNode *aNode) GetPresShell(getter_AddRefs(shell)); if (!shell) return PR_FALSE; - if (IsMozEditorBogusNode(aNode)) return PR_FALSE; - + if (IsMozEditorBogusNode(aNode) || !IsModifiableNode(aNode)) return PR_FALSE; + // see if it has a frame. If so, we'll edit it. // special case for textnodes: frame must have width. nsCOMPtr content = do_QueryInterface(aNode); @@ -4734,7 +4734,7 @@ NS_IMETHODIMP nsEditor::CreateTxnForDeleteElement(nsIDOMNode * aElement, { result = TransactionFactory::GetNewTransaction(DeleteElementTxn::GetCID(), (EditTxn **)aTxn); if (NS_SUCCEEDED(result)) { - result = (*aTxn)->Init(aElement, &mRangeUpdater); + result = (*aTxn)->Init(this, aElement, &mRangeUpdater); } } return result; @@ -5355,3 +5355,9 @@ nsEditor::DumpNode(nsIDOMNode *aNode, PRInt32 indent) } } #endif + +PRBool +nsEditor::IsModifiableNode(nsIDOMNode *aNode) +{ + return PR_TRUE; +} diff --git a/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp b/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp index ce0e3910f54..3fc1a0f0054 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp @@ -585,6 +585,51 @@ nsHTMLEditRules::WillDoAction(nsISelection *aSelection, // my kingdom for dynamic cast nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo); + + // Deal with actions for which we don't need to check whether the selection is + // editable. + if (info->action == kOutputText) { + return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled); + } + + nsCOMPtr domRange; + nsresult rv = aSelection->GetRangeAt(0, getter_AddRefs(domRange)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr selStartNode; + rv = domRange->GetStartContainer(getter_AddRefs(selStartNode)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mHTMLEditor->IsModifiableNode(selStartNode)) + { + *aCancel = PR_TRUE; + + return NS_OK; + } + + nsCOMPtr selEndNode; + rv = domRange->GetEndContainer(getter_AddRefs(selEndNode)); + NS_ENSURE_SUCCESS(rv, rv); + + if (selStartNode != selEndNode) + { + if (!mHTMLEditor->IsModifiableNode(selEndNode)) + { + *aCancel = PR_TRUE; + + return NS_OK; + } + + nsCOMPtr range = do_QueryInterface(domRange); + nsCOMPtr ancestor = + do_QueryInterface(range->GetCommonAncestor()); + if (!mHTMLEditor->IsModifiableNode(ancestor)) + { + *aCancel = PR_TRUE; + + return NS_OK; + } + } switch (info->action) { @@ -1569,6 +1614,13 @@ nsHTMLEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBo if (!blockParent) return NS_ERROR_FAILURE; + // do nothing if the node is read-only + if (!mHTMLEditor->IsModifiableNode(blockParent)) + { + *aCancel = PR_TRUE; + return NS_OK; + } + // if block is empty, populate with br. // (for example, imagine a div that contains the word "text". the user selects // "text" and types return. "text" is deleted leaving an empty block. we want diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp index a78bdf4700e..111851b9de7 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp @@ -130,6 +130,7 @@ #include "nsIView.h" #include "nsIWidget.h" #include "nsIParserService.h" +#include "nsIEventStateManager.h" // Some utilities to handle annoying overloading of "A" tag for link and named anchor static char hrefText[] = "href"; @@ -302,7 +303,7 @@ nsHTMLEditor::Init(nsIDOMDocument *aDoc, nsIPresShell *aPresShell, // disable links nsPresContext *context = aPresShell->GetPresContext(); if (!context) return NS_ERROR_NULL_POINTER; - if (!(mFlags & eEditorPlaintextMask)) { + if (!(mFlags & (eEditorPlaintextMask | eEditorAllowInteraction))) { mLinkHandler = context->GetLinkHandler(); context->SetLinkHandler(nsnull); @@ -317,8 +318,10 @@ nsHTMLEditor::Init(nsIDOMDocument *aDoc, nsIPresShell *aPresShell, mSelectionListenerP = new ResizerSelectionListener(this); if (!mSelectionListenerP) {return NS_ERROR_NULL_POINTER;} - // ignore any errors from this in case the file is missing - AddOverrideStyleSheet(NS_LITERAL_STRING("resource:/res/EditorOverride.css")); + if (!(mFlags & eEditorAllowInteraction)) { + // ignore any errors from this in case the file is missing + AddOverrideStyleSheet(NS_LITERAL_STRING("resource:/res/EditorOverride.css")); + } nsCOMPtrselection; result = GetSelection(getter_AddRefs(selection)); @@ -3884,6 +3887,11 @@ nsHTMLEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList) NS_IMETHODIMP nsHTMLEditor::DeleteNode(nsIDOMNode * aNode) { + // do nothing if the node is read-only + if (!IsModifiableNode(aNode)) { + return NS_ERROR_FAILURE; + } + nsCOMPtr selectAllNode = FindUserSelectAllNode(aNode); if (selectAllNode) @@ -3897,6 +3905,11 @@ NS_IMETHODIMP nsHTMLEditor::DeleteText(nsIDOMCharacterData *aTextNode, PRUint32 aOffset, PRUint32 aLength) { + // do nothing if the node is read-only + if (!IsModifiableNode(aTextNode)) { + return NS_ERROR_FAILURE; + } + nsCOMPtr selectAllNode = FindUserSelectAllNode(aTextNode); if (selectAllNode) @@ -3906,6 +3919,18 @@ NS_IMETHODIMP nsHTMLEditor::DeleteText(nsIDOMCharacterData *aTextNode, return nsEditor::DeleteText(aTextNode, aOffset, aLength); } +NS_IMETHODIMP nsHTMLEditor::InsertTextImpl(const nsAString& aStringToInsert, + nsCOMPtr *aInOutNode, + PRInt32 *aInOutOffset, + nsIDOMDocument *aDoc) +{ + // do nothing if the node is read-only + if (!IsModifiableNode(*aInOutNode)) { + return NS_ERROR_FAILURE; + } + + return nsEditor::InsertTextImpl(aStringToInsert, aInOutNode, aInOutOffset, aDoc); +} #ifdef XP_MAC #pragma mark - @@ -3947,6 +3972,14 @@ nsCOMPtr nsHTMLEditor::FindUserSelectAllNode(nsIDOMNode *aNode) return resultNode; } +NS_IMETHODIMP_(PRBool) +nsHTMLEditor::IsModifiableNode(nsIDOMNode *aNode) +{ + nsCOMPtr content = do_QueryInterface(aNode); + + return !content || !(content->IntrinsicState() & NS_EVENT_STATE_MOZ_READONLY); +} + static nsresult SetSelectionAroundHeadChildren(nsCOMPtr aSelection, nsWeakPtr aDocWeak) { nsresult res = NS_OK; @@ -4204,6 +4237,54 @@ nsHTMLEditor::SelectEntireDocument(nsISelection *aSelection) return nsEditor::SelectEntireDocument(aSelection); } +static nsIContent* +FindEditableRoot(nsIContent *aContent) +{ + nsIDocument *document = aContent->GetCurrentDoc(); + if (!document || document->HasFlag(NODE_IS_EDITABLE) || + !aContent->HasFlag(NODE_IS_EDITABLE)) { + return nsnull; + } + + nsIContent *parent, *content = aContent; + while ((parent = content->GetParent()) && parent->HasFlag(NODE_IS_EDITABLE)) { + content = parent; + } + + return content; +} + +NS_IMETHODIMP +nsHTMLEditor::SelectAll() +{ + ForceCompositionEnd(); + + nsresult rv; + nsCOMPtr selCon = do_QueryReferent(mSelConWeak, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr selection; + rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr anchorNode; + rv = selection->GetAnchorNode(getter_AddRefs(anchorNode)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr anchorContent = do_QueryInterface(anchorNode, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsIContent *rootContent = FindEditableRoot(anchorContent); + if (!rootContent) { + return SelectEntireDocument(selection); + } + + nsCOMPtr rootElement = do_QueryInterface(rootContent, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return selection->SelectAllChildren(rootElement); +} #ifdef XP_MAC diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.h b/mozilla/editor/libeditor/html/nsHTMLEditor.h index dfa5b3e5982..b860cbce778 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.h +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.h @@ -363,6 +363,13 @@ public: NS_IMETHODIMP DeleteText(nsIDOMCharacterData *aTextNode, PRUint32 aOffset, PRUint32 aLength); + NS_IMETHOD InsertTextImpl(const nsAString& aStringToInsert, + nsCOMPtr *aInOutNode, + PRInt32 *aInOutOffset, + nsIDOMDocument *aDoc); + NS_IMETHOD_(PRBool) IsModifiableNode(nsIDOMNode *aNode); + + NS_IMETHOD SelectAll(); /* ------------ nsICSSLoaderObserver -------------- */ NS_IMETHOD StyleSheetLoaded(nsICSSStyleSheet*aSheet, PRBool aWasAlternate, diff --git a/mozilla/editor/libeditor/text/nsEditorEventListeners.cpp b/mozilla/editor/libeditor/text/nsEditorEventListeners.cpp index 90f46a5ebbb..1c1461c8d37 100644 --- a/mozilla/editor/libeditor/text/nsEditorEventListeners.cpp +++ b/mozilla/editor/libeditor/text/nsEditorEventListeners.cpp @@ -68,6 +68,7 @@ #include "nsEditorUtils.h" #include "nsIDOMEventTarget.h" #include "nsIEventStateManager.h" +#include "nsISelectionPrivate.h" //#define DEBUG_IME @@ -223,7 +224,8 @@ nsTextEditorKeyListener::KeyPress(nsIDOMEvent* aKeyEvent) case nsIDOMKeyEvent::DOM_VK_TAB: if ((flags & nsIPlaintextEditor::eEditorSingleLineMask) || (flags & nsIPlaintextEditor::eEditorPasswordMask) || - (flags & nsIPlaintextEditor::eEditorWidgetMask)) + (flags & nsIPlaintextEditor::eEditorWidgetMask) || + (flags & nsIPlaintextEditor::eEditorAllowInteraction)) return NS_OK; // let it be used for focus switching if (isAnyModifierKeyButShift) @@ -1032,6 +1034,30 @@ IsTargetFocused(nsIDOMEventTarget* aTarget) return (focusedContent == content); } +static nsIContent* +FindEditableRoot(nsIContent *aContent) +{ + nsIDocument *document = aContent->GetCurrentDoc(); + if (!document) { + return nsnull; + } + + if (document->HasFlag(NODE_IS_EDITABLE)) { + return document->GetRootContent(); + } + + if (!aContent->HasFlag(NODE_IS_EDITABLE)) { + return nsnull; + } + + nsIContent *parent, *content = aContent; + while ((parent = content->GetParent()) && parent->HasFlag(NODE_IS_EDITABLE)) { + content = parent; + } + + return content; +} + nsresult nsTextEditorFocusListener::Focus(nsIDOMEvent* aEvent) { @@ -1059,18 +1085,38 @@ nsTextEditorFocusListener::Focus(nsIDOMEvent* aEvent) mEditor->GetFlags(&flags); if (! (flags & nsIPlaintextEditor::eEditorDisabledMask)) { // only enable caret and selection if the editor is not disabled - nsCOMPtreditor = do_QueryInterface(mEditor); - if (editor) + nsCOMPtr content = do_QueryInterface(target); + + nsIContent *editableRoot = content ? FindEditableRoot(content) : nsnull; + + nsCOMPtr selCon; + mEditor->GetSelectionController(getter_AddRefs(selCon)); + nsCOMPtr presShell = do_QueryInterface(selCon); + if (selCon && editableRoot) { - nsCOMPtrselCon; - editor->GetSelectionController(getter_AddRefs(selCon)); - if (selCon) + nsCOMPtr selection; + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + + if (presShell && selection) { + nsCOMPtr caret; + presShell->GetCaret(getter_AddRefs(caret)); + if (caret) { + caret->SetCaretDOMSelection(selection); + } + } + + const PRBool kIsReadonly = (flags & nsIPlaintextEditor::eEditorReadonlyMask) != 0; + selCon->SetCaretReadOnly(kIsReadonly); + selCon->SetCaretEnabled(PR_TRUE); + selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); + selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL); + + nsCOMPtr selectionPrivate = + do_QueryInterface(selection); + if (selectionPrivate) { - const PRBool kIsReadonly = (flags & nsIPlaintextEditor::eEditorReadonlyMask) != 0; - selCon->SetCaretReadOnly(kIsReadonly); - selCon->SetCaretEnabled(PR_TRUE); - selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); - selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL); + selectionPrivate->SetAncestorLimiter(editableRoot); } } } @@ -1105,6 +1151,16 @@ nsTextEditorFocusListener::Blur(nsIDOMEvent* aEvent) editor->GetSelectionController(getter_AddRefs(selCon)); if (selCon) { + nsCOMPtr selection; + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + + nsCOMPtr selectionPrivate = + do_QueryInterface(selection); + if (selectionPrivate) { + selectionPrivate->SetAncestorLimiter(nsnull); + } + selCon->SetCaretEnabled(PR_FALSE); PRUint32 flags; diff --git a/mozilla/editor/libeditor/text/nsTextEditRules.cpp b/mozilla/editor/libeditor/text/nsTextEditRules.cpp index a825412fc74..81f0c5fbcdf 100644 --- a/mozilla/editor/libeditor/text/nsTextEditRules.cpp +++ b/mozilla/editor/libeditor/text/nsTextEditRules.cpp @@ -1296,7 +1296,9 @@ nsTextEditRules::CreateBogusNodeIfNeeded(nsISelection *aSelection) nsresult res = mBody->GetFirstChild(getter_AddRefs(bodyChild)); while ((NS_SUCCEEDED(res)) && bodyChild) { - if (mEditor->IsMozEditorBogusNode(bodyChild) || mEditor->IsEditable(bodyChild)) + if (mEditor->IsMozEditorBogusNode(bodyChild) || + !mEditor->IsEditable(mBody) || + mEditor->IsEditable(bodyChild)) { needsBogusContent = PR_FALSE; break; diff --git a/mozilla/editor/ui/composer/content/editor.js b/mozilla/editor/ui/composer/content/editor.js index d59464f7edb..c3f21522e3e 100644 --- a/mozilla/editor/ui/composer/content/editor.js +++ b/mozilla/editor/ui/composer/content/editor.js @@ -54,6 +54,7 @@ const kDisplayModeTabIDS = ["NormalModeButton", "TagModeButton", "SourceModeButt const kNormalStyleSheet = "chrome://editor/content/EditorContent.css"; const kAllTagsStyleSheet = "chrome://editor/content/EditorAllTags.css"; const kParagraphMarksStyleSheet = "chrome://editor/content/EditorParagraphMarks.css"; +const kContentEditableStyleSheet = "resource:/res/contenteditable.css"; const kTextMimeType = "text/plain"; const kHTMLMimeType = "text/html"; @@ -399,6 +400,10 @@ var gEditorDocumentObserver = // and extra styles for showing anchors, table borders, smileys, etc editor.addOverrideStyleSheet(kNormalStyleSheet); + + // remove contenteditable stylesheets if they were applied by the + // editingSession + editor.removeOverrideStyleSheet(kContentEditableStyleSheet); } catch (e) {} // Things for just the Web Composer application diff --git a/mozilla/embedding/browser/activex/src/control/MozillaBrowser.cpp b/mozilla/embedding/browser/activex/src/control/MozillaBrowser.cpp index d8aa5790f11..05fc80d4877 100644 --- a/mozilla/embedding/browser/activex/src/control/MozillaBrowser.cpp +++ b/mozilla/embedding/browser/activex/src/control/MozillaBrowser.cpp @@ -1291,7 +1291,8 @@ HRESULT CMozillaBrowser::SetEditorMode(BOOL bEnabled) if (NS_FAILED(rv)) return E_FAIL; - rv = mEditingSession->MakeWindowEditable(domWindow, "html", PR_FALSE); + rv = mEditingSession->MakeWindowEditable(domWindow, "html", PR_FALSE, + PR_FALSE); return S_OK; } diff --git a/mozilla/embedding/qa/testembed/nsIEditSession.cpp b/mozilla/embedding/qa/testembed/nsIEditSession.cpp index 14eafb50a53..0c4fdbfb548 100644 --- a/mozilla/embedding/qa/testembed/nsIEditSession.cpp +++ b/mozilla/embedding/qa/testembed/nsIEditSession.cpp @@ -101,7 +101,8 @@ void CnsIEditSession::MakeWinEditTest(PRBool afterUriLoad, PRInt16 displayMode) editingSession = GetEditSessionObject(); domWindow = GetTheDOMWindow(qaWebBrowser); if (editingSession) { - rv= editingSession->MakeWindowEditable(domWindow, "text", afterUriLoad); + rv= editingSession->MakeWindowEditable(domWindow, "text", afterUriLoad, + PR_TRUE, PR_FALSE); RvTestResult(rv, "MakeWindowEditable() test", displayMode); if (displayMode == 1) RvTestResultDlg(rv, "MakeWindowEditable() test"); @@ -171,7 +172,7 @@ void CnsIEditSession::TearEditorWinTest(PRInt16 displayMode) editingSession = GetEditSessionObject(); domWindow = GetTheDOMWindow(qaWebBrowser); if (editingSession) { - rv = editingSession->TearDownEditorOnWindow(domWindow); + rv = editingSession->TearDownEditorOnWindow(domWindow, PR_FALSE); RvTestResult(rv, "TearDownEditorOnWindow() test", displayMode); if (displayMode == 1) RvTestResultDlg(rv, "TearDownEditorOnWindow() test"); diff --git a/mozilla/embedding/tests/wxEmbed/EditorFrame.cpp b/mozilla/embedding/tests/wxEmbed/EditorFrame.cpp index e16832d8b8e..7ef3dfb3a96 100644 --- a/mozilla/embedding/tests/wxEmbed/EditorFrame.cpp +++ b/mozilla/embedding/tests/wxEmbed/EditorFrame.cpp @@ -84,7 +84,7 @@ void EditorFrame::MakeEditable() nsCOMPtr editingSession = do_GetInterface(mWebBrowser); if (!editingSession) return;// NS_ERROR_FAILURE; - editingSession->MakeWindowEditable(domWindow, NULL, PR_TRUE); + editingSession->MakeWindowEditable(domWindow, NULL, PR_TRUE, PR_FALSE); } nsresult EditorFrame::DoCommand(const char *aCommand, nsICommandParams *aCommandParams) diff --git a/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp b/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp index 27848fcbb54..0fb1a12b268 100644 --- a/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp +++ b/mozilla/extensions/spellcheck/src/mozInlineSpellChecker.cpp @@ -93,6 +93,8 @@ #include "nsString.h" #include "nsThreadUtils.h" #include "nsUnicharUtils.h" +#include "nsIContent.h" +#include "nsIEventStateManager.h" // Set to spew messages to the console about what is happening. //#define DEBUG_INLINESPELL @@ -1108,6 +1110,11 @@ mozInlineSpellChecker::SkipSpellCheckForNode(nsIEditor* aEditor, parent = nextParent; } } + else { + // XXX Do we really want this for all read-write content? + nsCOMPtr content = do_QueryInterface(aNode); + *checkSpelling = content->IntrinsicState() & NS_EVENT_STATE_MOZ_READWRITE; + } return NS_OK; } diff --git a/mozilla/layout/base/nsCaret.cpp b/mozilla/layout/base/nsCaret.cpp index 4fe1931811a..dd105adbfe6 100644 --- a/mozilla/layout/base/nsCaret.cpp +++ b/mozilla/layout/base/nsCaret.cpp @@ -584,14 +584,10 @@ nsCaret::DrawAtPositionWithHint(nsIDOMNode* aNode, &theFrame, &theFrameOffset); if (NS_FAILED(rv) || !theFrame) return PR_FALSE; - + // now we have a frame, check whether it's appropriate to show the caret here const nsStyleUserInterface* userinterface = theFrame->GetStyleUserInterface(); - if ( -#ifdef SUPPORT_USER_MODIFY - // editable content still defaults to NS_STYLE_USER_MODIFY_READ_ONLY at present. See bug 15284 - (userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) || -#endif + if ((userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) || (userinterface->mUserInput == NS_STYLE_USER_INPUT_NONE) || (userinterface->mUserInput == NS_STYLE_USER_INPUT_DISABLED)) { diff --git a/mozilla/layout/generic/nsFrame.cpp b/mozilla/layout/generic/nsFrame.cpp index eb9150ef00a..a08b6f4d3ac 100644 --- a/mozilla/layout/generic/nsFrame.cpp +++ b/mozilla/layout/generic/nsFrame.cpp @@ -5737,17 +5737,6 @@ nsIFrame::IsFocusable(PRInt32 *aTabIndex, PRBool aWithMouse) const nsStyleVisibility* vis = GetStyleVisibility(); if (vis->mVisible != NS_STYLE_VISIBILITY_COLLAPSE && vis->mVisible != NS_STYLE_VISIBILITY_HIDDEN) { - if (mContent->IsNodeOfType(nsINode::eHTML)) { - nsCOMPtr container(PresContext()->GetContainer()); - nsCOMPtr editorDocShell(do_QueryInterface(container)); - if (editorDocShell) { - PRBool isEditable; - editorDocShell->GetEditable(&isEditable); - if (isEditable) { - return NS_OK; // Editor content is not focusable - } - } - } const nsStyleUserInterface* ui = GetStyleUserInterface(); if (ui->mUserFocus != NS_STYLE_USER_FOCUS_IGNORE && ui->mUserFocus != NS_STYLE_USER_FOCUS_NONE) { diff --git a/mozilla/layout/generic/nsFrameSelection.h b/mozilla/layout/generic/nsFrameSelection.h index c0d2b177191..919d677f249 100644 --- a/mozilla/layout/generic/nsFrameSelection.h +++ b/mozilla/layout/generic/nsFrameSelection.h @@ -457,6 +457,9 @@ public: */ nsIContent* GetLimiter() { return mLimiter; } + nsIContent* GetAncestorLimiter() { return mAncestorLimiter; } + void SetAncestorLimiter(nsIContent *aLimiter); + /** This will tell the frame selection that a double click has been pressed * so it can track abort future drags if inside the same selection * @aDoubleDown has the double click down happened @@ -631,6 +634,8 @@ private: PRInt32 mBatching; nsIContent *mLimiter; //limit selection navigation to a child of this node. + nsIContent *mAncestorLimiter; // Limit selection navigation to a descendant of + // this node. nsIPresShell *mShell; PRInt16 mSelectionChangeReason; // reason for notifications of selection changing diff --git a/mozilla/layout/generic/nsSelection.cpp b/mozilla/layout/generic/nsSelection.cpp index 4aecd0a9978..f4163ff58ab 100644 --- a/mozilla/layout/generic/nsSelection.cpp +++ b/mozilla/layout/generic/nsSelection.cpp @@ -664,12 +664,22 @@ IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsIContent *aContent) return PR_FALSE; if (aFrameSel) { - nsCOMPtr tLimiter = aFrameSel->GetLimiter(); - if (tLimiter && tLimiter != aContent) + nsIContent *limiter = aFrameSel->GetLimiter(); + if (limiter) { - if (tLimiter != aContent->GetParent()) //if newfocus == the limiter. that's ok. but if not there and not parent bad + if (limiter != aContent && limiter != aContent->GetParent()) //if newfocus == the limiter. that's ok. but if not there and not parent bad return PR_FALSE; //not in the right content. tLimiter said so } + limiter = aFrameSel->GetAncestorLimiter(); + if (limiter) + { + nsIContent *content = aContent; + while (content && content != limiter) + { + content = content->GetParent(); + } + return content != nsnull; + } } return PR_TRUE; } @@ -825,6 +835,7 @@ nsFrameSelection::nsFrameSelection() mChangesDuringBatching = PR_FALSE; mNotifyFrames = PR_TRUE; mLimiter = nsnull; //no default limiter. + mAncestorLimiter = nsnull; mMouseDoubleDownState = PR_FALSE; @@ -2220,8 +2231,10 @@ nsFrameSelection::HandleClick(nsIContent *aNewFocus, InvalidateDesiredX(); - if (!aContinueSelection) + if (!aContinueSelection) { mMaintainRange = nsnull; + mAncestorLimiter = nsnull; + } mHint = HINT(aHint); // Don't take focus when dragging off of a table @@ -2786,6 +2799,9 @@ nsFrameSelection::SelectAll() { rootContent = mLimiter;//addrefit } + else if (mAncestorLimiter) { + rootContent = mAncestorLimiter; + } else { nsIDocument *doc = mShell->GetDocument(); @@ -2797,7 +2813,7 @@ nsFrameSelection::SelectAll() } PRInt32 numChildren = rootContent->GetChildCount(); PostReason(nsISelectionListener::NO_REASON); - return TakeFocus(mLimiter, 0, numChildren, PR_FALSE, PR_FALSE); + return TakeFocus(rootContent, 0, numChildren, PR_FALSE, PR_FALSE); } //////////END FRAMESELECTION @@ -3823,6 +3839,23 @@ nsFrameSelection::CreateAndAddRange(nsIDOMNode *aParentNode, PRInt32 aOffset) // End of Table Selection +void +nsFrameSelection::SetAncestorLimiter(nsIContent *aLimiter) +{ + if (mAncestorLimiter != aLimiter) { + mAncestorLimiter = aLimiter; + PRInt8 index = + GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); + if (!IsValidSelectionPoint(this, mDomSelections[index]->FetchFocusNode())) { + ClearNormalSelection(); + if (mAncestorLimiter) { + PostReason(nsISelectionListener::NO_REASON); + TakeFocus(mAncestorLimiter, 0, 0, PR_FALSE, PR_FALSE); + } + } + } +} + //END nsFrameSelection methods @@ -5291,6 +5324,13 @@ nsTypedSelection::GetFrameSelection(nsFrameSelection **aFrameSelection) { return NS_OK; } +NS_IMETHODIMP +nsTypedSelection::SetAncestorLimiter(nsIContent *aContent) +{ + mFrameSelection->SetAncestorLimiter(aContent); + return NS_OK; +} + nsresult nsTypedSelection::StartAutoScrollTimer(nsPresContext *aPresContext, nsIView *aView, diff --git a/mozilla/layout/style/Makefile.in b/mozilla/layout/style/Makefile.in index b73f2769fb4..1ef5f0d9886 100644 --- a/mozilla/layout/style/Makefile.in +++ b/mozilla/layout/style/Makefile.in @@ -178,6 +178,8 @@ _FILES = \ viewsource.css \ arrow.gif \ arrowd.gif \ + contenteditable.css \ + designmode.css \ $(NULL) GARBAGE += $(addprefix $(DIST)/bin/res/,$(_FILES)) diff --git a/mozilla/layout/style/forms.css b/mozilla/layout/style/forms.css index 595fff43eff..350ec1f1031 100644 --- a/mozilla/layout/style/forms.css +++ b/mozilla/layout/style/forms.css @@ -134,6 +134,11 @@ input > .anonymous-div { ime-mode: inherit; } +input:-moz-read-write, +textarea:-moz-read-write { + -moz-user-modify: read-write !important; +} + select { margin: 0; border-color: ThreeDFace; diff --git a/mozilla/toolkit/content/tests/chrome/bug304188_window.xul b/mozilla/toolkit/content/tests/chrome/bug304188_window.xul index 722536adca9..aecc27391ab 100755 --- a/mozilla/toolkit/content/tests/chrome/bug304188_window.xul +++ b/mozilla/toolkit/content/tests/chrome/bug304188_window.xul @@ -64,7 +64,7 @@ find-menu appears in editor element which has had makeEditable() called but desi var webnav = gBrowser.webNavigation; var edsession = webnav.QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIEditingSession); - edsession.makeWindowEditable(gBrowser.contentWindow, "html", false); + edsession.makeWindowEditable(gBrowser.contentWindow, "html", false, true, false); gBrowser.contentWindow.focus(); enterStringIntoEditor("'"); enterStringIntoEditor("/"); diff --git a/mozilla/toolkit/content/widgets/editor.xml b/mozilla/toolkit/content/widgets/editor.xml index 0ca18f16cfe..8cfc1fb509c 100644 --- a/mozilla/toolkit/content/widgets/editor.xml +++ b/mozilla/toolkit/content/widgets/editor.xml @@ -66,7 +66,7 @@