import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,473 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_CSSEditUtils_h
#define mozilla_CSSEditUtils_h
#include "mozilla/ChangeStyleTransaction.h" // for ChangeStyleTransaction
#include "nsCOMPtr.h" // for already_AddRefed
#include "nsTArray.h" // for nsTArray
#include "nscore.h" // for nsAString, nsresult, nullptr
class nsComputedDOMStyle;
class nsIAtom;
class nsIContent;
class nsIDOMCSSStyleDeclaration;
class nsIDOMElement;
class nsIDOMNode;
class nsINode;
class nsString;
namespace mozilla {
class HTMLEditor;
namespace dom {
class Element;
} // namespace dom
typedef void (*nsProcessValueFunc)(const nsAString* aInputString,
nsAString& aOutputString,
const char* aDefaultValueString,
const char* aPrependString,
const char* aAppendString);
class CSSEditUtils final
{
public:
explicit CSSEditUtils(HTMLEditor* aEditor);
~CSSEditUtils();
enum nsCSSEditableProperty
{
eCSSEditableProperty_NONE=0,
eCSSEditableProperty_background_color,
eCSSEditableProperty_background_image,
eCSSEditableProperty_border,
eCSSEditableProperty_caption_side,
eCSSEditableProperty_color,
eCSSEditableProperty_float,
eCSSEditableProperty_font_family,
eCSSEditableProperty_font_size,
eCSSEditableProperty_font_style,
eCSSEditableProperty_font_weight,
eCSSEditableProperty_height,
eCSSEditableProperty_list_style_type,
eCSSEditableProperty_margin_left,
eCSSEditableProperty_margin_right,
eCSSEditableProperty_text_align,
eCSSEditableProperty_text_decoration,
eCSSEditableProperty_vertical_align,
eCSSEditableProperty_whitespace,
eCSSEditableProperty_width
};
enum StyleType { eSpecified, eComputed };
struct CSSEquivTable
{
nsCSSEditableProperty cssProperty;
nsProcessValueFunc processValueFunctor;
const char* defaultValue;
const char* prependValue;
const char* appendValue;
bool gettable;
bool caseSensitiveValue;
};
/**
* Answers true if the given combination element_name/attribute_name
* has a CSS equivalence in this implementation.
*
* @param aNode [IN] A DOM node.
* @param aProperty [IN] An atom containing a HTML tag name.
* @param aAttribute [IN] A string containing the name of a HTML
* attribute carried by the element above.
* @return A boolean saying if the tag/attribute has a CSS
* equiv.
*/
bool IsCSSEditableProperty(nsINode* aNode, nsIAtom* aProperty,
const nsAString* aAttribute);
/**
* Adds/remove a CSS declaration to the STYLE atrribute carried by a given
* element.
*
* @param aElement [IN] A DOM element.
* @param aProperty [IN] An atom containing the CSS property to set.
* @param aValue [IN] A string containing the value of the CSS
* property.
* @param aSuppressTransaction [IN] A boolean indicating, when true,
* that no transaction should be recorded.
*/
nsresult SetCSSProperty(dom::Element& aElement, nsIAtom& aProperty,
const nsAString& aValue, bool aSuppressTxn = false);
nsresult SetCSSPropertyPixels(dom::Element& aElement,
nsIAtom& aProperty, int32_t aIntValue);
nsresult RemoveCSSProperty(dom::Element& aElement,
nsIAtom& aProperty,
const nsAString& aPropertyValue,
bool aSuppressTxn = false);
/**
* Directly adds/remove a CSS declaration to the STYLE atrribute carried by
* a given element without going through the transaction manager.
*
* @param aElement [IN] A DOM element.
* @param aProperty [IN] A string containing the CSS property to
* set/remove.
* @param aValue [IN] A string containing the new value of the CSS
* property.
*/
nsresult SetCSSProperty(nsIDOMElement* aElement,
const nsAString& aProperty,
const nsAString& aValue);
nsresult SetCSSPropertyPixels(nsIDOMElement* aElement,
const nsAString& aProperty,
int32_t aIntValue);
/**
* Gets the specified/computed style value of a CSS property for a given
* node (or its element ancestor if it is not an element).
*
* @param aNode [IN] A DOM node.
* @param aProperty [IN] An atom containing the CSS property to get.
* @param aPropertyValue [OUT] The retrieved value of the property.
*/
nsresult GetSpecifiedProperty(nsINode& aNode, nsIAtom& aProperty,
nsAString& aValue);
nsresult GetComputedProperty(nsINode& aNode, nsIAtom& aProperty,
nsAString& aValue);
/**
* Removes a CSS property from the specified declarations in STYLE attribute
* and removes the node if it is an useless span.
*
* @param aNode [IN] The specific node we want to remove a style
* from.
* @param aProperty [IN] The CSS property atom to remove.
* @param aPropertyValue [IN] The value of the property we have to remove
* if the property accepts more than one value.
*/
nsresult RemoveCSSInlineStyle(nsIDOMNode* aNode, nsIAtom* aProperty,
const nsAString& aPropertyValue);
/**
* Answers true is the property can be removed by setting a "none" CSS value
* on a node.
*
* @param aProperty [IN] An atom containing a CSS property.
* @param aAttribute [IN] Pointer to an attribute name or null if this
* information is irrelevant.
* @return A boolean saying if the property can be remove by
* setting a "none" value.
*/
bool IsCSSInvertible(nsIAtom& aProperty, const nsAString* aAttribute);
/**
* Get the default browser background color if we need it for
* GetCSSBackgroundColorState().
*
* @param aColor [OUT] The default color as it is defined in prefs.
*/
void GetDefaultBackgroundColor(nsAString& aColor);
/**
* Get the default length unit used for CSS Indent/Outdent.
*
* @param aLengthUnit [OUT] The default length unit as it is defined in
* prefs.
*/
void GetDefaultLengthUnit(nsAString & aLengthUnit);
/**
* Returns the list of values for the CSS equivalences to
* the passed HTML style for the passed node.
*
* @param aNode [IN] A DOM node.
* @param aHTMLProperty [IN] An atom containing an HTML property.
* @param aAttribute [IN] A pointer to an attribute name or nullptr if
* irrelevant.
* @param aValueString [OUT] The list of CSS values.
* @param aStyleType [IN] eSpecified or eComputed.
*/
nsresult GetCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode,
nsIAtom* aHTMLProperty,
const nsAString* aAttribute,
nsAString& aValueString,
StyleType aStyleType);
/**
* Does the node aNode (or his parent if it is not an element node) carries
* the CSS equivalent styles to the HTML style for this node ?
*
* @param aNode [IN] A DOM node.
* @param aHTMLProperty [IN] An atom containing an HTML property.
* @param aAttribute [IN] A pointer to an attribute name or nullptr if
* irrelevant.
* @param aIsSet [OUT] A boolean being true if the css properties are
* set.
* @param aValueString [IN/OUT] The attribute value (in) the list of CSS
* values (out).
* @param aStyleType [IN] eSpecified or eComputed.
*
* The nsIContent variant returns aIsSet instead of using an out parameter.
*/
bool IsCSSEquivalentToHTMLInlineStyleSet(nsINode* aContent,
nsIAtom* aProperty,
const nsAString* aAttribute,
const nsAString& aValue,
StyleType aStyleType);
bool IsCSSEquivalentToHTMLInlineStyleSet(nsINode* aContent,
nsIAtom* aProperty,
const nsAString* aAttribute,
nsAString& aValue,
StyleType aStyleType);
nsresult IsCSSEquivalentToHTMLInlineStyleSet(nsIDOMNode* aNode,
nsIAtom* aHTMLProperty,
const nsAString* aAttribute,
bool& aIsSet,
nsAString& aValueString,
StyleType aStyleType);
/**
* Adds to the node the CSS inline styles equivalent to the HTML style
* and return the number of CSS properties set by the call.
*
* @param aNode [IN] A DOM node.
* @param aHTMLProperty [IN] An atom containing an HTML property.
* @param aAttribute [IN] A pointer to an attribute name or nullptr if
* irrelevant.
* @param aValue [IN] The attribute value.
* @param aCount [OUT] The number of CSS properties set by the call.
* @param aSuppressTransaction [IN] A boolean indicating, when true,
* that no transaction should be recorded.
*
* aCount is returned by the dom::Element variant instead of being an out
* parameter.
*/
int32_t SetCSSEquivalentToHTMLStyle(dom::Element* aElement,
nsIAtom* aProperty,
const nsAString* aAttribute,
const nsAString* aValue,
bool aSuppressTransaction);
nsresult SetCSSEquivalentToHTMLStyle(nsIDOMNode* aNode,
nsIAtom* aHTMLProperty,
const nsAString* aAttribute,
const nsAString* aValue,
int32_t* aCount,
bool aSuppressTransaction);
/**
* Removes from the node the CSS inline styles equivalent to the HTML style.
*
* @param aNode [IN] A DOM node.
* @param aHTMLProperty [IN] An atom containing an HTML property.
* @param aAttribute [IN] A pointer to an attribute name or nullptr if
* irrelevant.
* @param aValue [IN] The attribute value.
* @param aSuppressTransaction [IN] A boolean indicating, when true,
* that no transaction should be recorded.
*/
nsresult RemoveCSSEquivalentToHTMLStyle(nsIDOMNode* aNode,
nsIAtom* aHTMLProperty,
const nsAString* aAttribute,
const nsAString* aValue,
bool aSuppressTransaction);
/**
* Removes from the node the CSS inline styles equivalent to the HTML style.
*
* @param aElement [IN] A DOM Element (must not be null).
* @param aHTMLProperty [IN] An atom containing an HTML property.
* @param aAttribute [IN] A pointer to an attribute name or nullptr if
* irrelevant.
* @param aValue [IN] The attribute value.
* @param aSuppressTransaction [IN] A boolean indicating, when true,
* that no transaction should be recorded.
*/
nsresult RemoveCSSEquivalentToHTMLStyle(dom::Element* aElement,
nsIAtom* aHTMLProperty,
const nsAString* aAttribute,
const nsAString* aValue,
bool aSuppressTransaction);
/**
* Parses a "xxxx.xxxxxuuu" string where x is a digit and u an alpha char
* we need such a parser because
* nsIDOMCSSStyleDeclaration::GetPropertyCSSValue() is not implemented.
*
* @param aString [IN] Input string to parse.
* @param aValue [OUT] Numeric part.
* @param aUnit [OUT] Unit part.
*/
void ParseLength(const nsAString& aString, float* aValue, nsIAtom** aUnit);
/**
* Sets the mIsCSSPrefChecked private member; used as callback from observer
* when the CSS pref state is changed.
*
* @param aIsCSSPrefChecked [IN] The new boolean state for the pref.
*/
void SetCSSEnabled(bool aIsCSSPrefChecked);
/**
* Retrieves the mIsCSSPrefChecked private member, true if the CSS pref is
* checked, false if it is not.
*
* @return the boolean value of the CSS pref.
*/
bool IsCSSPrefChecked();
/**
* ElementsSameStyle compares two elements and checks if they have the same
* specified CSS declarations in the STYLE attribute.
* The answer is always false if at least one of them carries an ID or a
* class.
*
* @param aFirstNode [IN] A DOM node.
* @param aSecondNode [IN] A DOM node.
* @return true if the two elements are considered to
* have same styles.
*/
bool ElementsSameStyle(dom::Element* aFirstNode,
dom::Element* aSecondNode);
bool ElementsSameStyle(nsIDOMNode* aFirstNode, nsIDOMNode* aSecondNode);
/**
* Get the specified inline styles (style attribute) for an element.
*
* @param aElement [IN] The element node.
* @param aCssDecl [OUT] The CSS declaration corresponding to the
* style attribute.
* @param aLength [OUT] The number of declarations in aCssDecl.
*/
nsresult GetInlineStyles(dom::Element* aElement,
nsIDOMCSSStyleDeclaration** aCssDecl,
uint32_t* aLength);
nsresult GetInlineStyles(nsIDOMElement* aElement,
nsIDOMCSSStyleDeclaration** aCssDecl,
uint32_t* aLength);
private:
nsresult GetInlineStyles(nsISupports* aElement,
nsIDOMCSSStyleDeclaration** aCssDecl,
uint32_t* aLength);
public:
/**
* Returns aNode itself if it is an element node, or the first ancestors
* being an element node if aNode is not one itself.
*
* @param aNode [IN] A node
* @param aElement [OUT] The deepest element node containing aNode
* (possibly aNode itself)
*/
dom::Element* GetElementContainerOrSelf(nsINode* aNode);
already_AddRefed<nsIDOMElement> GetElementContainerOrSelf(nsIDOMNode* aNode);
/**
* Gets the computed style for a given element. Can return null.
*/
already_AddRefed<nsComputedDOMStyle> GetComputedStyle(dom::Element* aElement);
private:
/**
* Retrieves the CSS property atom from an enum.
*
* @param aProperty [IN] The enum value for the property.
* @param aAtom [OUT] The corresponding atom.
*/
void GetCSSPropertyAtom(nsCSSEditableProperty aProperty, nsIAtom** aAtom);
/**
* Retrieves the CSS declarations equivalent to a HTML style value for
* a given equivalence table.
*
* @param aPropertyArray [OUT] The array of css properties.
* @param aValueArray [OUT] The array of values for the CSS properties
* above.
* @param aEquivTable [IN] The equivalence table.
* @param aValue [IN] The HTML style value.
* @param aGetOrRemoveRequest [IN] A boolean value being true if the call to
* the current method is made for
* GetCSSEquivalentToHTMLInlineStyleSet() or
* RemoveCSSEquivalentToHTMLInlineStyleSet().
*/
void BuildCSSDeclarations(nsTArray<nsIAtom*>& aPropertyArray,
nsTArray<nsString>& cssValueArray,
const CSSEquivTable* aEquivTable,
const nsAString* aValue,
bool aGetOrRemoveRequest);
/**
* Retrieves the CSS declarations equivalent to the given HTML
* property/attribute/value for a given node.
*
* @param aNode [IN] The DOM node.
* @param aHTMLProperty [IN] An atom containing an HTML property.
* @param aAttribute [IN] A pointer to an attribute name or nullptr
* if irrelevant
* @param aValue [IN] The attribute value.
* @param aPropertyArray [OUT] The array of CSS properties.
* @param aValueArray [OUT] The array of values for the CSS properties
* above.
* @param aGetOrRemoveRequest [IN] A boolean value being true if the call to
* the current method is made for
* GetCSSEquivalentToHTMLInlineStyleSet() or
* RemoveCSSEquivalentToHTMLInlineStyleSet().
*/
void GenerateCSSDeclarationsFromHTMLStyle(dom::Element* aNode,
nsIAtom* aHTMLProperty,
const nsAString* aAttribute,
const nsAString* aValue,
nsTArray<nsIAtom*>& aPropertyArray,
nsTArray<nsString>& aValueArray,
bool aGetOrRemoveRequest);
/**
* Creates a Transaction for setting or removing a CSS property. Never
* returns null.
*
* @param aElement [IN] A DOM element.
* @param aProperty [IN] A CSS property.
* @param aValue [IN] The value to set for this CSS property.
* @param aChangeType [IN] eSet to set, eRemove to remove.
*/
already_AddRefed<ChangeStyleTransaction>
CreateCSSPropertyTxn(dom::Element& aElement,
nsIAtom& aProperty, const nsAString& aValue,
ChangeStyleTransaction::EChangeType aChangeType);
/**
* Back-end for GetSpecifiedProperty and GetComputedProperty.
*
* @param aNode [IN] A DOM node.
* @param aProperty [IN] A CSS property.
* @param aValue [OUT] The retrieved value for this property.
* @param aStyleType [IN] eSpecified or eComputed.
*/
nsresult GetCSSInlinePropertyBase(nsINode* aNode, nsIAtom* aProperty,
nsAString& aValue, StyleType aStyleType);
private:
HTMLEditor* mHTMLEditor;
bool mIsCSSPrefChecked;
};
#define NS_EDITOR_INDENT_INCREMENT_IN 0.4134f
#define NS_EDITOR_INDENT_INCREMENT_CM 1.05f
#define NS_EDITOR_INDENT_INCREMENT_MM 10.5f
#define NS_EDITOR_INDENT_INCREMENT_PT 29.76f
#define NS_EDITOR_INDENT_INCREMENT_PC 2.48f
#define NS_EDITOR_INDENT_INCREMENT_EM 3
#define NS_EDITOR_INDENT_INCREMENT_EX 6
#define NS_EDITOR_INDENT_INCREMENT_PX 40
#define NS_EDITOR_INDENT_INCREMENT_PERCENT 4
} // namespace mozilla
#endif // #ifndef mozilla_CSSEditUtils_h

View file

@ -0,0 +1,98 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ChangeAttributeTransaction.h"
#include "mozilla/dom/Element.h" // for Element
#include "nsAString.h"
#include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc.
namespace mozilla {
using namespace dom;
ChangeAttributeTransaction::ChangeAttributeTransaction(Element& aElement,
nsIAtom& aAttribute,
const nsAString* aValue)
: EditTransactionBase()
, mElement(&aElement)
, mAttribute(&aAttribute)
, mValue(aValue ? *aValue : EmptyString())
, mRemoveAttribute(!aValue)
, mAttributeWasSet(false)
, mUndoValue()
{
}
ChangeAttributeTransaction::~ChangeAttributeTransaction()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeAttributeTransaction,
EditTransactionBase,
mElement)
NS_IMPL_ADDREF_INHERITED(ChangeAttributeTransaction, EditTransactionBase)
NS_IMPL_RELEASE_INHERITED(ChangeAttributeTransaction, EditTransactionBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeAttributeTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
ChangeAttributeTransaction::DoTransaction()
{
// Need to get the current value of the attribute and save it, and set
// mAttributeWasSet
mAttributeWasSet = mElement->GetAttr(kNameSpaceID_None, mAttribute,
mUndoValue);
// XXX: hack until attribute-was-set code is implemented
if (!mUndoValue.IsEmpty()) {
mAttributeWasSet = true;
}
// XXX: end hack
// Now set the attribute to the new value
if (mRemoveAttribute) {
return mElement->UnsetAttr(kNameSpaceID_None, mAttribute, true);
}
return mElement->SetAttr(kNameSpaceID_None, mAttribute, mValue, true);
}
NS_IMETHODIMP
ChangeAttributeTransaction::UndoTransaction()
{
if (mAttributeWasSet) {
return mElement->SetAttr(kNameSpaceID_None, mAttribute, mUndoValue, true);
}
return mElement->UnsetAttr(kNameSpaceID_None, mAttribute, true);
}
NS_IMETHODIMP
ChangeAttributeTransaction::RedoTransaction()
{
if (mRemoveAttribute) {
return mElement->UnsetAttr(kNameSpaceID_None, mAttribute, true);
}
return mElement->SetAttr(kNameSpaceID_None, mAttribute, mValue, true);
}
NS_IMETHODIMP
ChangeAttributeTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("ChangeAttributeTransaction: [mRemoveAttribute == ");
if (mRemoveAttribute) {
aString.AppendLiteral("true] ");
} else {
aString.AppendLiteral("false] ");
}
aString += nsDependentAtomString(mAttribute);
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,72 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ChangeAttributeTransaction_h
#define ChangeAttributeTransaction_h
#include "mozilla/Attributes.h" // override
#include "mozilla/EditTransactionBase.h" // base class
#include "nsCOMPtr.h" // nsCOMPtr members
#include "nsCycleCollectionParticipant.h" // NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED
#include "nsISupportsImpl.h" // NS_DECL_ISUPPORTS_INHERITED
#include "nsString.h" // nsString members
class nsIAtom;
namespace mozilla {
namespace dom {
class Element;
} // namespace dom
/**
* A transaction that changes an attribute of a content node. This transaction
* covers add, remove, and change attribute.
*/
class ChangeAttributeTransaction final : public EditTransactionBase
{
public:
/**
* @param aElement the element whose attribute will be changed
* @param aAttribute the name of the attribute to change
* @param aValue the new value for aAttribute, or null to remove
*/
ChangeAttributeTransaction(dom::Element& aElement,
nsIAtom& aAttribute,
const nsAString* aValue);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ChangeAttributeTransaction,
EditTransactionBase)
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD RedoTransaction() override;
private:
virtual ~ChangeAttributeTransaction();
// The element to operate upon
nsCOMPtr<dom::Element> mElement;
// The attribute to change
nsCOMPtr<nsIAtom> mAttribute;
// The value to set the attribute to (ignored if mRemoveAttribute==true)
nsString mValue;
// True if the operation is to remove mAttribute from mElement
bool mRemoveAttribute;
// True if the mAttribute was set on mElement at the time of execution
bool mAttributeWasSet;
// The value to set the attribute to for undo
nsString mUndoValue;
};
} // namespace mozilla
#endif // #ifndef ChangeAttributeTransaction_h

View file

@ -0,0 +1,287 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ChangeStyleTransaction.h"
#include "mozilla/dom/Element.h" // for Element
#include "nsAString.h" // for nsAString_internal::Append, etc.
#include "nsCRT.h" // for nsCRT::IsAsciiSpace
#include "nsDebug.h" // for NS_ENSURE_SUCCESS, etc.
#include "nsError.h" // for NS_ERROR_NULL_POINTER, etc.
#include "nsGkAtoms.h" // for nsGkAtoms, etc.
#include "nsICSSDeclaration.h" // for nsICSSDeclaration.
#include "nsLiteralString.h" // for NS_LITERAL_STRING, etc.
#include "nsReadableUtils.h" // for ToNewUnicode
#include "nsString.h" // for nsAutoString, nsString, etc.
#include "nsStyledElement.h" // for nsStyledElement.
#include "nsUnicharUtils.h" // for nsCaseInsensitiveStringComparator
namespace mozilla {
using namespace dom;
#define kNullCh (char16_t('\0'))
NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeStyleTransaction, EditTransactionBase,
mElement)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeStyleTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMPL_ADDREF_INHERITED(ChangeStyleTransaction, EditTransactionBase)
NS_IMPL_RELEASE_INHERITED(ChangeStyleTransaction, EditTransactionBase)
ChangeStyleTransaction::~ChangeStyleTransaction()
{
}
// Answers true if aValue is in the string list of white-space separated values
// aValueList.
bool
ChangeStyleTransaction::ValueIncludes(const nsAString& aValueList,
const nsAString& aValue)
{
nsAutoString valueList(aValueList);
bool result = false;
// put an extra null at the end
valueList.Append(kNullCh);
char16_t* value = ToNewUnicode(aValue);
char16_t* start = valueList.BeginWriting();
char16_t* end = start;
while (kNullCh != *start) {
while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) {
// skip leading space
start++;
}
end = start;
while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) {
// look for space or end
end++;
}
// end string here
*end = kNullCh;
if (start < end) {
if (nsDependentString(value).Equals(nsDependentString(start),
nsCaseInsensitiveStringComparator())) {
result = true;
break;
}
}
start = ++end;
}
free(value);
return result;
}
// Removes the value aRemoveValue from the string list of white-space separated
// values aValueList
void
ChangeStyleTransaction::RemoveValueFromListOfValues(
nsAString& aValues,
const nsAString& aRemoveValue)
{
nsAutoString classStr(aValues);
nsAutoString outString;
// put an extra null at the end
classStr.Append(kNullCh);
char16_t* start = classStr.BeginWriting();
char16_t* end = start;
while (kNullCh != *start) {
while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) {
// skip leading space
start++;
}
end = start;
while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) {
// look for space or end
end++;
}
// end string here
*end = kNullCh;
if (start < end && !aRemoveValue.Equals(start)) {
outString.Append(start);
outString.Append(char16_t(' '));
}
start = ++end;
}
aValues.Assign(outString);
}
ChangeStyleTransaction::ChangeStyleTransaction(Element& aElement,
nsIAtom& aProperty,
const nsAString& aValue,
EChangeType aChangeType)
: EditTransactionBase()
, mElement(&aElement)
, mProperty(&aProperty)
, mValue(aValue)
, mRemoveProperty(aChangeType == eRemove)
, mUndoValue()
, mRedoValue()
, mUndoAttributeWasSet(false)
, mRedoAttributeWasSet(false)
{
}
NS_IMETHODIMP
ChangeStyleTransaction::DoTransaction()
{
nsCOMPtr<nsStyledElement> inlineStyles = do_QueryInterface(mElement);
NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsICSSDeclaration> cssDecl = inlineStyles->Style();
nsAutoString propertyNameString;
mProperty->ToString(propertyNameString);
mUndoAttributeWasSet = mElement->HasAttr(kNameSpaceID_None,
nsGkAtoms::style);
nsAutoString values;
nsresult rv = cssDecl->GetPropertyValue(propertyNameString, values);
NS_ENSURE_SUCCESS(rv, rv);
mUndoValue.Assign(values);
// Does this property accept more than one value? (bug 62682)
bool multiple = AcceptsMoreThanOneValue(*mProperty);
if (mRemoveProperty) {
nsAutoString returnString;
if (multiple) {
// Let's remove only the value we have to remove and not the others
// The two lines below are a workaround because
// nsDOMCSSDeclaration::GetPropertyCSSValue is not yet implemented (bug
// 62682)
RemoveValueFromListOfValues(values, NS_LITERAL_STRING("none"));
RemoveValueFromListOfValues(values, mValue);
if (values.IsEmpty()) {
rv = cssDecl->RemoveProperty(propertyNameString, returnString);
NS_ENSURE_SUCCESS(rv, rv);
} else {
nsAutoString priority;
cssDecl->GetPropertyPriority(propertyNameString, priority);
rv = cssDecl->SetProperty(propertyNameString, values, priority);
NS_ENSURE_SUCCESS(rv, rv);
}
} else {
rv = cssDecl->RemoveProperty(propertyNameString, returnString);
NS_ENSURE_SUCCESS(rv, rv);
}
} else {
nsAutoString priority;
cssDecl->GetPropertyPriority(propertyNameString, priority);
if (multiple) {
// Let's add the value we have to add to the others
// The line below is a workaround because
// nsDOMCSSDeclaration::GetPropertyCSSValue is not yet implemented (bug
// 62682)
AddValueToMultivalueProperty(values, mValue);
} else {
values.Assign(mValue);
}
rv = cssDecl->SetProperty(propertyNameString, values, priority);
NS_ENSURE_SUCCESS(rv, rv);
}
// Let's be sure we don't keep an empty style attribute
uint32_t length;
rv = cssDecl->GetLength(&length);
NS_ENSURE_SUCCESS(rv, rv);
if (!length) {
rv = mElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true);
NS_ENSURE_SUCCESS(rv, rv);
} else {
mRedoAttributeWasSet = true;
}
return cssDecl->GetPropertyValue(propertyNameString, mRedoValue);
}
nsresult
ChangeStyleTransaction::SetStyle(bool aAttributeWasSet,
nsAString& aValue)
{
if (aAttributeWasSet) {
// The style attribute was not empty, let's recreate the declaration
nsAutoString propertyNameString;
mProperty->ToString(propertyNameString);
nsCOMPtr<nsStyledElement> inlineStyles = do_QueryInterface(mElement);
NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsICSSDeclaration> cssDecl = inlineStyles->Style();
if (aValue.IsEmpty()) {
// An empty value means we have to remove the property
nsAutoString returnString;
return cssDecl->RemoveProperty(propertyNameString, returnString);
}
// Let's recreate the declaration as it was
nsAutoString priority;
cssDecl->GetPropertyPriority(propertyNameString, priority);
return cssDecl->SetProperty(propertyNameString, aValue, priority);
}
return mElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true);
}
NS_IMETHODIMP
ChangeStyleTransaction::UndoTransaction()
{
return SetStyle(mUndoAttributeWasSet, mUndoValue);
}
NS_IMETHODIMP
ChangeStyleTransaction::RedoTransaction()
{
return SetStyle(mRedoAttributeWasSet, mRedoValue);
}
NS_IMETHODIMP
ChangeStyleTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("ChangeStyleTransaction: [mRemoveProperty == ");
if (mRemoveProperty) {
aString.AppendLiteral("true] ");
} else {
aString.AppendLiteral("false] ");
}
aString += nsDependentAtomString(mProperty);
return NS_OK;
}
// True if the CSS property accepts more than one value
bool
ChangeStyleTransaction::AcceptsMoreThanOneValue(nsIAtom& aCSSProperty)
{
return &aCSSProperty == nsGkAtoms::text_decoration;
}
// Adds the value aNewValue to the list of white-space separated values aValues
void
ChangeStyleTransaction::AddValueToMultivalueProperty(nsAString& aValues,
const nsAString& aNewValue)
{
if (aValues.IsEmpty() || aValues.LowerCaseEqualsLiteral("none")) {
aValues.Assign(aNewValue);
} else if (!ValueIncludes(aValues, aNewValue)) {
// We already have another value but not this one; add it
aValues.Append(char16_t(' '));
aValues.Append(aNewValue);
}
}
} // namespace mozilla

View file

@ -0,0 +1,123 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ChangeStyleTransaction_h
#define mozilla_ChangeStyleTransaction_h
#include "mozilla/EditTransactionBase.h" // base class
#include "nsCOMPtr.h" // nsCOMPtr members
#include "nsCycleCollectionParticipant.h" // various macros
#include "nsString.h" // nsString members
class nsAString;
class nsIAtom;
namespace mozilla {
namespace dom {
class Element;
} // namespace dom
/**
* A transaction that changes the value of a CSS inline style of a content
* node. This transaction covers add, remove, and change a property's value.
*/
class ChangeStyleTransaction final : public EditTransactionBase
{
public:
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ChangeStyleTransaction,
EditTransactionBase)
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD RedoTransaction() override;
enum EChangeType { eSet, eRemove };
/**
* @param aNode [IN] the node whose style attribute will be changed
* @param aProperty [IN] the name of the property to change
* @param aValue [IN] new value for aProperty, or value to remove
* @param aChangeType [IN] whether to set or remove
*/
ChangeStyleTransaction(dom::Element& aElement,
nsIAtom& aProperty,
const nsAString& aValue,
EChangeType aChangeType);
/**
* Returns true if the list of white-space separated values contains aValue
*
* @param aValueList [IN] a list of white-space separated values
* @param aValue [IN] the value to look for in the list
* @return true if the value is in the list of values
*/
static bool ValueIncludes(const nsAString& aValueList,
const nsAString& aValue);
private:
virtual ~ChangeStyleTransaction();
/*
* Adds the value aNewValue to list of white-space separated values aValues.
*
* @param aValues [IN/OUT] a list of wite-space separated values
* @param aNewValue [IN] a value this code adds to aValues if it is not
* already in
*/
void AddValueToMultivalueProperty(nsAString& aValues,
const nsAString& aNewValue);
/**
* Returns true if the property accepts more than one value.
*
* @param aCSSProperty [IN] the CSS property
* @return true if the property accepts more than one value
*/
bool AcceptsMoreThanOneValue(nsIAtom& aCSSProperty);
/**
* Remove a value from a list of white-space separated values.
* @param aValues [IN] a list of white-space separated values
* @param aRemoveValue [IN] the value to remove from the list
*/
void RemoveValueFromListOfValues(nsAString& aValues,
const nsAString& aRemoveValue);
/**
* If the boolean is true and if the value is not the empty string,
* set the property in the transaction to that value; if the value
* is empty, remove the property from element's styles. If the boolean
* is false, just remove the style attribute.
*/
nsresult SetStyle(bool aAttributeWasSet, nsAString& aValue);
// The element to operate upon.
nsCOMPtr<dom::Element> mElement;
// The CSS property to change.
nsCOMPtr<nsIAtom> mProperty;
// The value to set the property to (ignored if mRemoveProperty==true).
nsString mValue;
// true if the operation is to remove mProperty from mElement.
bool mRemoveProperty;
// The value to set the property to for undo.
nsString mUndoValue;
// The value to set the property to for redo.
nsString mRedoValue;
// True if the style attribute was present and not empty before DoTransaction.
bool mUndoAttributeWasSet;
// True if the style attribute is present and not empty after DoTransaction.
bool mRedoAttributeWasSet;
};
} // namespace mozilla
#endif // #ifndef mozilla_ChangeStyleTransaction_h

View file

@ -0,0 +1,332 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CompositionTransaction.h"
#include "mozilla/EditorBase.h" // mEditorBase
#include "mozilla/SelectionState.h" // RangeUpdater
#include "mozilla/dom/Selection.h" // local var
#include "mozilla/dom/Text.h" // mTextNode
#include "nsAString.h" // params
#include "nsDebug.h" // for NS_ASSERTION, etc
#include "nsError.h" // for NS_SUCCEEDED, NS_FAILED, etc
#include "nsIPresShell.h" // nsISelectionController constants
#include "nsRange.h" // local var
#include "nsQueryObject.h" // for do_QueryObject
namespace mozilla {
using namespace dom;
CompositionTransaction::CompositionTransaction(
Text& aTextNode,
uint32_t aOffset,
uint32_t aReplaceLength,
TextRangeArray* aTextRangeArray,
const nsAString& aStringToInsert,
EditorBase& aEditorBase,
RangeUpdater* aRangeUpdater)
: mTextNode(&aTextNode)
, mOffset(aOffset)
, mReplaceLength(aReplaceLength)
, mRanges(aTextRangeArray)
, mStringToInsert(aStringToInsert)
, mEditorBase(aEditorBase)
, mRangeUpdater(aRangeUpdater)
, mFixed(false)
{
MOZ_ASSERT(mTextNode->TextLength() >= mOffset);
}
CompositionTransaction::~CompositionTransaction()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(CompositionTransaction, EditTransactionBase,
mTextNode)
// mRangeList can't lead to cycles
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompositionTransaction)
if (aIID.Equals(NS_GET_IID(CompositionTransaction))) {
foundInterface = static_cast<nsITransaction*>(this);
} else
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMPL_ADDREF_INHERITED(CompositionTransaction, EditTransactionBase)
NS_IMPL_RELEASE_INHERITED(CompositionTransaction, EditTransactionBase)
NS_IMETHODIMP
CompositionTransaction::DoTransaction()
{
// Fail before making any changes if there's no selection controller
nsCOMPtr<nsISelectionController> selCon;
mEditorBase.GetSelectionController(getter_AddRefs(selCon));
NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
// Advance caret: This requires the presentation shell to get the selection.
if (mReplaceLength == 0) {
nsresult rv = mTextNode->InsertData(mOffset, mStringToInsert);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
} else {
uint32_t replaceableLength = mTextNode->TextLength() - mOffset;
nsresult rv =
mTextNode->ReplaceData(mOffset, mReplaceLength, mStringToInsert);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mRangeUpdater->SelAdjDeleteText(mTextNode, mOffset, mReplaceLength);
mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
// If IME text node is multiple node, ReplaceData doesn't remove all IME
// text. So we need remove remained text into other text node.
if (replaceableLength < mReplaceLength) {
int32_t remainLength = mReplaceLength - replaceableLength;
nsCOMPtr<nsINode> node = mTextNode->GetNextSibling();
while (node && node->IsNodeOfType(nsINode::eTEXT) &&
remainLength > 0) {
Text* text = static_cast<Text*>(node.get());
uint32_t textLength = text->TextLength();
text->DeleteData(0, remainLength);
mRangeUpdater->SelAdjDeleteText(text, 0, remainLength);
remainLength -= textLength;
node = node->GetNextSibling();
}
}
}
nsresult rv = SetSelectionForRanges();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
CompositionTransaction::UndoTransaction()
{
// Get the selection first so we'll fail before making any changes if we
// can't get it
RefPtr<Selection> selection = mEditorBase.GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
nsresult rv = mTextNode->DeleteData(mOffset, mStringToInsert.Length());
NS_ENSURE_SUCCESS(rv, rv);
// set the selection to the insertion point where the string was removed
rv = selection->Collapse(mTextNode, mOffset);
NS_ASSERTION(NS_SUCCEEDED(rv),
"Selection could not be collapsed after undo of IME insert.");
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
CompositionTransaction::Merge(nsITransaction* aTransaction,
bool* aDidMerge)
{
NS_ENSURE_ARG_POINTER(aTransaction && aDidMerge);
// Check to make sure we aren't fixed, if we are then nothing gets absorbed
if (mFixed) {
*aDidMerge = false;
return NS_OK;
}
// If aTransaction is another CompositionTransaction then absorb it
RefPtr<CompositionTransaction> otherTransaction =
do_QueryObject(aTransaction);
if (otherTransaction) {
// We absorb the next IME transaction by adopting its insert string
mStringToInsert = otherTransaction->mStringToInsert;
mRanges = otherTransaction->mRanges;
*aDidMerge = true;
return NS_OK;
}
*aDidMerge = false;
return NS_OK;
}
void
CompositionTransaction::MarkFixed()
{
mFixed = true;
}
NS_IMETHODIMP
CompositionTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("CompositionTransaction: ");
aString += mStringToInsert;
return NS_OK;
}
/* ============ private methods ================== */
nsresult
CompositionTransaction::SetSelectionForRanges()
{
return SetIMESelection(mEditorBase, mTextNode, mOffset,
mStringToInsert.Length(), mRanges);
}
// static
nsresult
CompositionTransaction::SetIMESelection(EditorBase& aEditorBase,
Text* aTextNode,
uint32_t aOffsetInNode,
uint32_t aLengthOfCompositionString,
const TextRangeArray* aRanges)
{
RefPtr<Selection> selection = aEditorBase.GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
nsresult rv = selection->StartBatchChanges();
NS_ENSURE_SUCCESS(rv, rv);
// First, remove all selections of IME composition.
static const RawSelectionType kIMESelections[] = {
nsISelectionController::SELECTION_IME_RAWINPUT,
nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT,
nsISelectionController::SELECTION_IME_CONVERTEDTEXT,
nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
};
nsCOMPtr<nsISelectionController> selCon;
aEditorBase.GetSelectionController(getter_AddRefs(selCon));
NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
for (uint32_t i = 0; i < ArrayLength(kIMESelections); ++i) {
nsCOMPtr<nsISelection> selectionOfIME;
if (NS_FAILED(selCon->GetSelection(kIMESelections[i],
getter_AddRefs(selectionOfIME)))) {
continue;
}
rv = selectionOfIME->RemoveAllRanges();
NS_ASSERTION(NS_SUCCEEDED(rv),
"Failed to remove all ranges of IME selection");
}
// Set caret position and selection of IME composition with TextRangeArray.
bool setCaret = false;
uint32_t countOfRanges = aRanges ? aRanges->Length() : 0;
#ifdef DEBUG
// Bounds-checking on debug builds
uint32_t maxOffset = aTextNode->Length();
#endif
// NOTE: composition string may be truncated when it's committed and
// maxlength attribute value doesn't allow input of all text of this
// composition.
for (uint32_t i = 0; i < countOfRanges; ++i) {
const TextRange& textRange = aRanges->ElementAt(i);
// Caret needs special handling since its length may be 0 and if it's not
// specified explicitly, we need to handle it ourselves later.
if (textRange.mRangeType == TextRangeType::eCaret) {
NS_ASSERTION(!setCaret, "The ranges already has caret position");
NS_ASSERTION(!textRange.Length(),
"EditorBase doesn't support wide caret");
int32_t caretOffset = static_cast<int32_t>(
aOffsetInNode +
std::min(textRange.mStartOffset, aLengthOfCompositionString));
MOZ_ASSERT(caretOffset >= 0 &&
static_cast<uint32_t>(caretOffset) <= maxOffset);
rv = selection->Collapse(aTextNode, caretOffset);
setCaret = setCaret || NS_SUCCEEDED(rv);
if (NS_WARN_IF(!setCaret)) {
continue;
}
// If caret range is specified explicitly, we should show the caret if
// it should be so.
aEditorBase.HideCaret(false);
continue;
}
// If the clause length is 0, it should be a bug.
if (!textRange.Length()) {
NS_WARNING("Any clauses must not be empty");
continue;
}
RefPtr<nsRange> clauseRange;
int32_t startOffset = static_cast<int32_t>(
aOffsetInNode +
std::min(textRange.mStartOffset, aLengthOfCompositionString));
MOZ_ASSERT(startOffset >= 0 &&
static_cast<uint32_t>(startOffset) <= maxOffset);
int32_t endOffset = static_cast<int32_t>(
aOffsetInNode +
std::min(textRange.mEndOffset, aLengthOfCompositionString));
MOZ_ASSERT(endOffset >= startOffset &&
static_cast<uint32_t>(endOffset) <= maxOffset);
rv = nsRange::CreateRange(aTextNode, startOffset,
aTextNode, endOffset,
getter_AddRefs(clauseRange));
if (NS_FAILED(rv)) {
NS_WARNING("Failed to create a DOM range for a clause of composition");
break;
}
// Set the range of the clause to selection.
nsCOMPtr<nsISelection> selectionOfIME;
rv = selCon->GetSelection(ToRawSelectionType(textRange.mRangeType),
getter_AddRefs(selectionOfIME));
if (NS_FAILED(rv)) {
NS_WARNING("Failed to get IME selection");
break;
}
rv = selectionOfIME->AddRange(clauseRange);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to add selection range for a clause of composition");
break;
}
// Set the style of the clause.
nsCOMPtr<nsISelectionPrivate> selectionOfIMEPriv =
do_QueryInterface(selectionOfIME);
if (!selectionOfIMEPriv) {
NS_WARNING("Failed to get nsISelectionPrivate interface from selection");
continue; // Since this is additional feature, we can continue this job.
}
rv = selectionOfIMEPriv->SetTextRangeStyle(clauseRange,
textRange.mRangeStyle);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to set selection style");
break; // but this is unexpected...
}
}
// If the ranges doesn't include explicit caret position, let's set the
// caret to the end of composition string.
if (!setCaret) {
int32_t caretOffset =
static_cast<int32_t>(aOffsetInNode + aLengthOfCompositionString);
MOZ_ASSERT(caretOffset >= 0 &&
static_cast<uint32_t>(caretOffset) <= maxOffset);
rv = selection->Collapse(aTextNode, caretOffset);
NS_ASSERTION(NS_SUCCEEDED(rv),
"Failed to set caret at the end of composition string");
// If caret range isn't specified explicitly, we should hide the caret.
// Hiding the caret benefits a Windows build (see bug 555642 comment #6).
// However, when there is no range, we should keep showing caret.
if (countOfRanges) {
aEditorBase.HideCaret(true);
}
}
rv = selection->EndBatchChangesInternal();
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes");
return rv;
}
} // namespace mozilla

View file

@ -0,0 +1,104 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef CompositionTransaction_h
#define CompositionTransaction_h
#include "mozilla/EditTransactionBase.h" // base class
#include "nsCycleCollectionParticipant.h" // various macros
#include "nsString.h" // mStringToInsert
#define NS_IMETEXTTXN_IID \
{ 0xb391355d, 0x346c, 0x43d1, \
{ 0x85, 0xed, 0x9e, 0x65, 0xbe, 0xe7, 0x7e, 0x48 } }
namespace mozilla {
class EditorBase;
class RangeUpdater;
class TextRangeArray;
namespace dom {
class Text;
} // namespace dom
/**
* CompositionTransaction stores all edit for a composition, i.e.,
* from compositionstart event to compositionend event. E.g., inserting a
* composition string, modifying the composition string or its IME selection
* ranges and commit or cancel the composition.
*/
class CompositionTransaction final : public EditTransactionBase
{
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMETEXTTXN_IID)
/**
* @param aTextNode The start node of text content.
* @param aOffset The location in aTextNode to do the insertion.
* @param aReplaceLength The length of text to replace. 0 means not
* replacing existing text.
* @param aTextRangeArray Clauses and/or caret information. This may be
* null.
* @param aString The new text to insert.
* @param aEditorBase Used to get and set the selection.
* @param aRangeUpdater The range updater
*/
CompositionTransaction(dom::Text& aTextNode,
uint32_t aOffset, uint32_t aReplaceLength,
TextRangeArray* aTextRangeArray,
const nsAString& aString,
EditorBase& aEditorBase,
RangeUpdater* aRangeUpdater);
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CompositionTransaction,
EditTransactionBase)
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aDidMerge) override;
void MarkFixed();
static nsresult SetIMESelection(EditorBase& aEditorBase,
dom::Text* aTextNode,
uint32_t aOffsetInNode,
uint32_t aLengthOfCompositionString,
const TextRangeArray* aRanges);
private:
~CompositionTransaction();
nsresult SetSelectionForRanges();
// The text element to operate upon.
RefPtr<dom::Text> mTextNode;
// The offsets into mTextNode where the insertion should be placed.
uint32_t mOffset;
uint32_t mReplaceLength;
// The range list.
RefPtr<TextRangeArray> mRanges;
// The text to insert into mTextNode at mOffset.
nsString mStringToInsert;
// The editor, which is used to get the selection controller.
EditorBase& mEditorBase;
RangeUpdater* mRangeUpdater;
bool mFixed;
};
NS_DEFINE_STATIC_IID_ACCESSOR(CompositionTransaction, NS_IMETEXTTXN_IID)
} // namespace mozilla
#endif // #ifndef CompositionTransaction_h

View file

@ -0,0 +1,147 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CreateElementTransaction.h"
#include <algorithm>
#include <stdio.h>
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/Casting.h"
#include "mozilla/EditorBase.h"
#include "nsAlgorithm.h"
#include "nsAString.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIContent.h"
#include "nsIDOMCharacterData.h"
#include "nsIEditor.h"
#include "nsINode.h"
#include "nsISupportsUtils.h"
#include "nsMemory.h"
#include "nsReadableUtils.h"
#include "nsStringFwd.h"
#include "nsString.h"
namespace mozilla {
using namespace dom;
CreateElementTransaction::CreateElementTransaction(EditorBase& aEditorBase,
nsIAtom& aTag,
nsINode& aParent,
int32_t aOffsetInParent)
: EditTransactionBase()
, mEditorBase(&aEditorBase)
, mTag(&aTag)
, mParent(&aParent)
, mOffsetInParent(aOffsetInParent)
{
}
CreateElementTransaction::~CreateElementTransaction()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(CreateElementTransaction,
EditTransactionBase,
mParent,
mNewNode,
mRefNode)
NS_IMPL_ADDREF_INHERITED(CreateElementTransaction, EditTransactionBase)
NS_IMPL_RELEASE_INHERITED(CreateElementTransaction, EditTransactionBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CreateElementTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
CreateElementTransaction::DoTransaction()
{
MOZ_ASSERT(mEditorBase && mTag && mParent);
mNewNode = mEditorBase->CreateHTMLContent(mTag);
NS_ENSURE_STATE(mNewNode);
// Try to insert formatting whitespace for the new node:
mEditorBase->MarkNodeDirty(GetAsDOMNode(mNewNode));
// Insert the new node
ErrorResult rv;
if (mOffsetInParent == -1) {
mParent->AppendChild(*mNewNode, rv);
return rv.StealNSResult();
}
mOffsetInParent = std::min(mOffsetInParent,
static_cast<int32_t>(mParent->GetChildCount()));
// Note, it's ok for mRefNode to be null. That means append
mRefNode = mParent->GetChildAt(mOffsetInParent);
nsCOMPtr<nsIContent> refNode = mRefNode;
mParent->InsertBefore(*mNewNode, refNode, rv);
NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
// Only set selection to insertion point if editor gives permission
if (!mEditorBase->GetShouldTxnSetSelection()) {
// Do nothing - DOM range gravity will adjust selection
return NS_OK;
}
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
rv = selection->CollapseNative(mParent, mParent->IndexOf(mNewNode) + 1);
NS_ASSERTION(!rv.Failed(),
"selection could not be collapsed after insert");
return NS_OK;
}
NS_IMETHODIMP
CreateElementTransaction::UndoTransaction()
{
MOZ_ASSERT(mEditorBase && mParent);
ErrorResult rv;
mParent->RemoveChild(*mNewNode, rv);
return rv.StealNSResult();
}
NS_IMETHODIMP
CreateElementTransaction::RedoTransaction()
{
MOZ_ASSERT(mEditorBase && mParent);
// First, reset mNewNode so it has no attributes or content
// XXX We never actually did this, we only cleared mNewNode's contents if it
// was a CharacterData node (which it's not, it's an Element)
// Now, reinsert mNewNode
ErrorResult rv;
nsCOMPtr<nsIContent> refNode = mRefNode;
mParent->InsertBefore(*mNewNode, refNode, rv);
return rv.StealNSResult();
}
NS_IMETHODIMP
CreateElementTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("CreateElementTransaction: ");
aString += nsDependentAtomString(mTag);
return NS_OK;
}
already_AddRefed<Element>
CreateElementTransaction::GetNewNode()
{
return nsCOMPtr<Element>(mNewNode).forget();
}
} // namespace mozilla

View file

@ -0,0 +1,80 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef CreateElementTransaction_h
#define CreateElementTransaction_h
#include "mozilla/EditTransactionBase.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISupportsImpl.h"
class nsIAtom;
class nsIContent;
class nsINode;
/**
* A transaction that creates a new node in the content tree.
*/
namespace mozilla {
class EditorBase;
namespace dom {
class Element;
} // namespace dom
class CreateElementTransaction final : public EditTransactionBase
{
public:
/**
* Initialize the transaction.
* @param aEditorBase The provider of basic editing functionality.
* @param aTag The tag (P, HR, TABLE, etc.) for the new element.
* @param aParent The node into which the new element will be
* inserted.
* @param aOffsetInParent The location in aParent to insert the new element.
* If eAppend, the new element is appended as the last
* child.
*/
CreateElementTransaction(EditorBase& aEditorBase,
nsIAtom& aTag,
nsINode& aParent,
int32_t aOffsetInParent);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CreateElementTransaction,
EditTransactionBase)
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD RedoTransaction() override;
already_AddRefed<dom::Element> GetNewNode();
protected:
virtual ~CreateElementTransaction();
// The document into which the new node will be inserted.
EditorBase* mEditorBase;
// The tag (mapping to object type) for the new element.
nsCOMPtr<nsIAtom> mTag;
// The node into which the new node will be inserted.
nsCOMPtr<nsINode> mParent;
// The index in mParent for the new node.
int32_t mOffsetInParent;
// The new node to insert.
nsCOMPtr<dom::Element> mNewNode;
// The node we will insert mNewNode before. We compute this ourselves.
nsCOMPtr<nsIContent> mRefNode;
};
} // namespace mozilla
#endif // #ifndef CreateElementTransaction_h

View file

@ -0,0 +1,123 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DeleteNodeTransaction.h"
#include "mozilla/EditorBase.h"
#include "mozilla/SelectionState.h" // RangeUpdater
#include "nsDebug.h"
#include "nsError.h"
#include "nsAString.h"
namespace mozilla {
DeleteNodeTransaction::DeleteNodeTransaction()
: mEditorBase(nullptr)
, mRangeUpdater(nullptr)
{
}
DeleteNodeTransaction::~DeleteNodeTransaction()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteNodeTransaction, EditTransactionBase,
mNode,
mParent,
mRefNode)
NS_IMPL_ADDREF_INHERITED(DeleteNodeTransaction, EditTransactionBase)
NS_IMPL_RELEASE_INHERITED(DeleteNodeTransaction, EditTransactionBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteNodeTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
nsresult
DeleteNodeTransaction::Init(EditorBase* aEditorBase,
nsINode* aNode,
RangeUpdater* aRangeUpdater)
{
NS_ENSURE_TRUE(aEditorBase && aNode, NS_ERROR_NULL_POINTER);
mEditorBase = aEditorBase;
mNode = aNode;
mParent = aNode->GetParentNode();
// do nothing if the node has a parent and it's read-only
NS_ENSURE_TRUE(!mParent || mEditorBase->IsModifiableNode(mParent),
NS_ERROR_FAILURE);
mRangeUpdater = aRangeUpdater;
return NS_OK;
}
NS_IMETHODIMP
DeleteNodeTransaction::DoTransaction()
{
NS_ENSURE_TRUE(mNode, NS_ERROR_NOT_INITIALIZED);
if (!mParent) {
// this is a no-op, there's no parent to delete mNode from
return NS_OK;
}
// remember which child mNode was (by remembering which child was next);
// mRefNode can be null
mRefNode = mNode->GetNextSibling();
// give range updater a chance. SelAdjDeleteNode() needs to be called
// *before* we do the action, unlike some of the other RangeItem update
// methods.
if (mRangeUpdater) {
mRangeUpdater->SelAdjDeleteNode(mNode->AsDOMNode());
}
ErrorResult error;
mParent->RemoveChild(*mNode, error);
return error.StealNSResult();
}
NS_IMETHODIMP
DeleteNodeTransaction::UndoTransaction()
{
if (!mParent) {
// this is a legal state, the txn is a no-op
return NS_OK;
}
if (!mNode) {
return NS_ERROR_NULL_POINTER;
}
ErrorResult error;
nsCOMPtr<nsIContent> refNode = mRefNode;
mParent->InsertBefore(*mNode, refNode, error);
return error.StealNSResult();
}
NS_IMETHODIMP
DeleteNodeTransaction::RedoTransaction()
{
if (!mParent) {
// this is a legal state, the txn is a no-op
return NS_OK;
}
if (!mNode) {
return NS_ERROR_NULL_POINTER;
}
if (mRangeUpdater) {
mRangeUpdater->SelAdjDeleteNode(mNode->AsDOMNode());
}
ErrorResult error;
mParent->RemoveChild(*mNode, error);
return error.StealNSResult();
}
NS_IMETHODIMP
DeleteNodeTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("DeleteNodeTransaction");
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,66 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DeleteNodeTransaction_h
#define DeleteNodeTransaction_h
#include "mozilla/EditTransactionBase.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIContent.h"
#include "nsINode.h"
#include "nsISupportsImpl.h"
#include "nscore.h"
namespace mozilla {
class EditorBase;
class RangeUpdater;
/**
* A transaction that deletes a single element
*/
class DeleteNodeTransaction final : public EditTransactionBase
{
public:
/**
* Initialize the transaction.
* @param aElement The node to delete.
*/
nsresult Init(EditorBase* aEditorBase, nsINode* aNode,
RangeUpdater* aRangeUpdater);
DeleteNodeTransaction();
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteNodeTransaction,
EditTransactionBase)
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD RedoTransaction() override;
protected:
virtual ~DeleteNodeTransaction();
// The element to delete.
nsCOMPtr<nsINode> mNode;
// Parent of node to delete.
nsCOMPtr<nsINode> mParent;
// Next sibling to remember for undo/redo purposes.
nsCOMPtr<nsIContent> mRefNode;
// The editor for this transaction.
EditorBase* mEditorBase;
// Range updater object.
RangeUpdater* mRangeUpdater;
};
} // namespace mozilla
#endif // #ifndef DeleteNodeTransaction_h

View file

@ -0,0 +1,239 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DeleteRangeTransaction.h"
#include "DeleteNodeTransaction.h"
#include "DeleteTextTransaction.h"
#include "mozilla/Assertions.h"
#include "mozilla/EditorBase.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/mozalloc.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIContent.h"
#include "nsIContentIterator.h"
#include "nsIDOMCharacterData.h"
#include "nsINode.h"
#include "nsAString.h"
namespace mozilla {
using namespace dom;
// note that aEditorBase is not refcounted
DeleteRangeTransaction::DeleteRangeTransaction()
: mEditorBase(nullptr)
, mRangeUpdater(nullptr)
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteRangeTransaction,
EditAggregateTransaction,
mRange)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteRangeTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
nsresult
DeleteRangeTransaction::Init(EditorBase* aEditorBase,
nsRange* aRange,
RangeUpdater* aRangeUpdater)
{
MOZ_ASSERT(aEditorBase && aRange);
mEditorBase = aEditorBase;
mRange = aRange->CloneRange();
mRangeUpdater = aRangeUpdater;
NS_ENSURE_TRUE(mEditorBase->IsModifiableNode(mRange->GetStartParent()),
NS_ERROR_FAILURE);
NS_ENSURE_TRUE(mEditorBase->IsModifiableNode(mRange->GetEndParent()),
NS_ERROR_FAILURE);
NS_ENSURE_TRUE(mEditorBase->IsModifiableNode(mRange->GetCommonAncestor()),
NS_ERROR_FAILURE);
return NS_OK;
}
NS_IMETHODIMP
DeleteRangeTransaction::DoTransaction()
{
MOZ_ASSERT(mRange && mEditorBase);
// build the child transactions
nsCOMPtr<nsINode> startParent = mRange->GetStartParent();
int32_t startOffset = mRange->StartOffset();
nsCOMPtr<nsINode> endParent = mRange->GetEndParent();
int32_t endOffset = mRange->EndOffset();
MOZ_ASSERT(startParent && endParent);
if (startParent == endParent) {
// the selection begins and ends in the same node
nsresult rv =
CreateTxnsToDeleteBetween(startParent, startOffset, endOffset);
NS_ENSURE_SUCCESS(rv, rv);
} else {
// the selection ends in a different node from where it started. delete
// the relevant content in the start node
nsresult rv =
CreateTxnsToDeleteContent(startParent, startOffset, nsIEditor::eNext);
NS_ENSURE_SUCCESS(rv, rv);
// delete the intervening nodes
rv = CreateTxnsToDeleteNodesBetween();
NS_ENSURE_SUCCESS(rv, rv);
// delete the relevant content in the end node
rv = CreateTxnsToDeleteContent(endParent, endOffset, nsIEditor::ePrevious);
NS_ENSURE_SUCCESS(rv, rv);
}
// if we've successfully built this aggregate transaction, then do it.
nsresult rv = EditAggregateTransaction::DoTransaction();
NS_ENSURE_SUCCESS(rv, rv);
// only set selection to deletion point if editor gives permission
bool bAdjustSelection;
mEditorBase->ShouldTxnSetSelection(&bAdjustSelection);
if (bAdjustSelection) {
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
rv = selection->Collapse(startParent, startOffset);
NS_ENSURE_SUCCESS(rv, rv);
}
// else do nothing - dom range gravity will adjust selection
return NS_OK;
}
NS_IMETHODIMP
DeleteRangeTransaction::UndoTransaction()
{
MOZ_ASSERT(mRange && mEditorBase);
return EditAggregateTransaction::UndoTransaction();
}
NS_IMETHODIMP
DeleteRangeTransaction::RedoTransaction()
{
MOZ_ASSERT(mRange && mEditorBase);
return EditAggregateTransaction::RedoTransaction();
}
NS_IMETHODIMP
DeleteRangeTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("DeleteRangeTransaction");
return NS_OK;
}
nsresult
DeleteRangeTransaction::CreateTxnsToDeleteBetween(nsINode* aNode,
int32_t aStartOffset,
int32_t aEndOffset)
{
// see what kind of node we have
if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
// if the node is a chardata node, then delete chardata content
int32_t numToDel;
if (aStartOffset == aEndOffset) {
numToDel = 1;
} else {
numToDel = aEndOffset - aStartOffset;
}
RefPtr<nsGenericDOMDataNode> charDataNode =
static_cast<nsGenericDOMDataNode*>(aNode);
RefPtr<DeleteTextTransaction> transaction =
new DeleteTextTransaction(*mEditorBase, *charDataNode, aStartOffset,
numToDel, mRangeUpdater);
nsresult rv = transaction->Init();
NS_ENSURE_SUCCESS(rv, rv);
AppendChild(transaction);
return NS_OK;
}
nsCOMPtr<nsIContent> child = aNode->GetChildAt(aStartOffset);
NS_ENSURE_STATE(child);
// XXX This looks odd. Only when the last transaction causes error at
// calling Init(), the result becomes error. Otherwise, always NS_OK.
nsresult rv = NS_OK;
for (int32_t i = aStartOffset; i < aEndOffset; ++i) {
RefPtr<DeleteNodeTransaction> transaction = new DeleteNodeTransaction();
rv = transaction->Init(mEditorBase, child, mRangeUpdater);
if (NS_SUCCEEDED(rv)) {
AppendChild(transaction);
}
child = child->GetNextSibling();
}
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
DeleteRangeTransaction::CreateTxnsToDeleteContent(nsINode* aNode,
int32_t aOffset,
nsIEditor::EDirection aAction)
{
// see what kind of node we have
if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
// if the node is a chardata node, then delete chardata content
uint32_t start, numToDelete;
if (nsIEditor::eNext == aAction) {
start = aOffset;
numToDelete = aNode->Length() - aOffset;
} else {
start = 0;
numToDelete = aOffset;
}
if (numToDelete) {
RefPtr<nsGenericDOMDataNode> dataNode =
static_cast<nsGenericDOMDataNode*>(aNode);
RefPtr<DeleteTextTransaction> transaction =
new DeleteTextTransaction(*mEditorBase, *dataNode, start, numToDelete,
mRangeUpdater);
nsresult rv = transaction->Init();
NS_ENSURE_SUCCESS(rv, rv);
AppendChild(transaction);
}
}
return NS_OK;
}
nsresult
DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween()
{
nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
nsresult rv = iter->Init(mRange);
NS_ENSURE_SUCCESS(rv, rv);
while (!iter->IsDone()) {
nsCOMPtr<nsINode> node = iter->GetCurrentNode();
NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
RefPtr<DeleteNodeTransaction> transaction = new DeleteNodeTransaction();
rv = transaction->Init(mEditorBase, node, mRangeUpdater);
NS_ENSURE_SUCCESS(rv, rv);
AppendChild(transaction);
iter->Next();
}
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,78 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DeleteRangeTransaction_h
#define DeleteRangeTransaction_h
#include "EditAggregateTransaction.h"
#include "nsCycleCollectionParticipant.h"
#include "nsID.h"
#include "nsIEditor.h"
#include "nsISupportsImpl.h"
#include "nsRange.h"
#include "nscore.h"
class nsINode;
namespace mozilla {
class EditorBase;
class RangeUpdater;
/**
* A transaction that deletes an entire range in the content tree
*/
class DeleteRangeTransaction final : public EditAggregateTransaction
{
public:
/**
* Initialize the transaction.
* @param aEditorBase The object providing basic editing operations.
* @param aRange The range to delete.
*/
nsresult Init(EditorBase* aEditorBase,
nsRange* aRange,
RangeUpdater* aRangeUpdater);
DeleteRangeTransaction();
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteRangeTransaction,
EditAggregateTransaction)
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD RedoTransaction() override;
virtual void LastRelease() override
{
mRange = nullptr;
EditAggregateTransaction::LastRelease();
}
protected:
nsresult CreateTxnsToDeleteBetween(nsINode* aNode,
int32_t aStartOffset,
int32_t aEndOffset);
nsresult CreateTxnsToDeleteNodesBetween();
nsresult CreateTxnsToDeleteContent(nsINode* aParent,
int32_t aOffset,
nsIEditor::EDirection aAction);
// P1 in the range.
RefPtr<nsRange> mRange;
// The editor for this transaction.
EditorBase* mEditorBase;
// Range updater object.
RangeUpdater* mRangeUpdater;
};
} // namespace mozilla
#endif // #ifndef DeleteRangeTransaction_h

View file

@ -0,0 +1,102 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DeleteTextTransaction.h"
#include "mozilla/Assertions.h"
#include "mozilla/EditorBase.h"
#include "mozilla/SelectionState.h"
#include "mozilla/dom/Selection.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIEditor.h"
#include "nsISupportsImpl.h"
#include "nsAString.h"
namespace mozilla {
using namespace dom;
DeleteTextTransaction::DeleteTextTransaction(
EditorBase& aEditorBase,
nsGenericDOMDataNode& aCharData,
uint32_t aOffset,
uint32_t aNumCharsToDelete,
RangeUpdater* aRangeUpdater)
: mEditorBase(aEditorBase)
, mCharData(&aCharData)
, mOffset(aOffset)
, mNumCharsToDelete(aNumCharsToDelete)
, mRangeUpdater(aRangeUpdater)
{
NS_ASSERTION(mCharData->Length() >= aOffset + aNumCharsToDelete,
"Trying to delete more characters than in node");
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteTextTransaction, EditTransactionBase,
mCharData)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteTextTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
nsresult
DeleteTextTransaction::Init()
{
// Do nothing if the node is read-only
if (!mEditorBase.IsModifiableNode(mCharData)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
DeleteTextTransaction::DoTransaction()
{
MOZ_ASSERT(mCharData);
// Get the text that we're about to delete
nsresult rv = mCharData->SubstringData(mOffset, mNumCharsToDelete,
mDeletedText);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = mCharData->DeleteData(mOffset, mNumCharsToDelete);
NS_ENSURE_SUCCESS(rv, rv);
if (mRangeUpdater) {
mRangeUpdater->SelAdjDeleteText(mCharData, mOffset, mNumCharsToDelete);
}
// Only set selection to deletion point if editor gives permission
if (mEditorBase.GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase.GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
rv = selection->Collapse(mCharData, mOffset);
NS_ASSERTION(NS_SUCCEEDED(rv),
"Selection could not be collapsed after undo of deletetext");
NS_ENSURE_SUCCESS(rv, rv);
}
// Else do nothing - DOM Range gravity will adjust selection
return NS_OK;
}
//XXX: We may want to store the selection state and restore it properly. Was
// it an insertion point or an extended selection?
NS_IMETHODIMP
DeleteTextTransaction::UndoTransaction()
{
MOZ_ASSERT(mCharData);
return mCharData->InsertData(mOffset, mDeletedText);
}
NS_IMETHODIMP
DeleteTextTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("DeleteTextTransaction: ");
aString += mDeletedText;
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,76 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DeleteTextTransaction_h
#define DeleteTextTransaction_h
#include "mozilla/EditTransactionBase.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsGenericDOMDataNode.h"
#include "nsID.h"
#include "nsString.h"
#include "nscore.h"
namespace mozilla {
class EditorBase;
class RangeUpdater;
/**
* A transaction that removes text from a content node.
*/
class DeleteTextTransaction final : public EditTransactionBase
{
public:
/**
* Initialize the transaction.
* @param aEditorBase The provider of basic editing operations.
* @param aElement The content node to remove text from.
* @param aOffset The location in aElement to begin the deletion.
* @param aNumCharsToDelete The number of characters to delete. Not the
* number of bytes!
*/
DeleteTextTransaction(EditorBase& aEditorBase,
nsGenericDOMDataNode& aCharData,
uint32_t aOffset,
uint32_t aNumCharsToDelete,
RangeUpdater* aRangeUpdater);
nsresult Init();
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteTextTransaction,
EditTransactionBase)
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
NS_DECL_EDITTRANSACTIONBASE
uint32_t GetOffset() { return mOffset; }
uint32_t GetNumCharsToDelete() { return mNumCharsToDelete; }
protected:
// The provider of basic editing operations.
EditorBase& mEditorBase;
// The CharacterData node to operate upon.
RefPtr<nsGenericDOMDataNode> mCharData;
// The offset into mCharData where the deletion is to take place.
uint32_t mOffset;
// The number of characters to delete.
uint32_t mNumCharsToDelete;
// The text that was deleted.
nsString mDeletedText;
// Range updater object.
RangeUpdater* mRangeUpdater;
};
} // namespace mozilla
#endif // #ifndef DeleteTextTransaction_h

View file

@ -0,0 +1,17 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __editActionListener_h__
#define __editActionListener_h__
class EditActionListener
{
public:
virtual void EditAction() = 0;
};
#endif /* __editActionListener_h__ */

View file

@ -0,0 +1,145 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "EditAggregateTransaction.h"
#include "nsAString.h"
#include "nsCOMPtr.h" // for nsCOMPtr
#include "nsError.h" // for NS_OK, etc.
#include "nsISupportsUtils.h" // for NS_ADDREF
#include "nsITransaction.h" // for nsITransaction
#include "nsString.h" // for nsAutoString
namespace mozilla {
EditAggregateTransaction::EditAggregateTransaction()
{
}
EditAggregateTransaction::~EditAggregateTransaction()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(EditAggregateTransaction,
EditTransactionBase,
mChildren)
NS_IMPL_ADDREF_INHERITED(EditAggregateTransaction, EditTransactionBase)
NS_IMPL_RELEASE_INHERITED(EditAggregateTransaction, EditTransactionBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditAggregateTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
EditAggregateTransaction::DoTransaction()
{
// FYI: It's legal (but not very useful) to have an empty child list.
for (uint32_t i = 0, length = mChildren.Length(); i < length; ++i) {
nsITransaction *txn = mChildren[i];
if (!txn) {
return NS_ERROR_NULL_POINTER;
}
nsresult rv = txn->DoTransaction();
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
NS_IMETHODIMP
EditAggregateTransaction::UndoTransaction()
{
// FYI: It's legal (but not very useful) to have an empty child list.
// Undo goes through children backwards.
for (uint32_t i = mChildren.Length(); i--; ) {
nsITransaction *txn = mChildren[i];
if (!txn) {
return NS_ERROR_NULL_POINTER;
}
nsresult rv = txn->UndoTransaction();
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
NS_IMETHODIMP
EditAggregateTransaction::RedoTransaction()
{
// It's legal (but not very useful) to have an empty child list.
for (uint32_t i = 0, length = mChildren.Length(); i < length; ++i) {
nsITransaction *txn = mChildren[i];
if (!txn) {
return NS_ERROR_NULL_POINTER;
}
nsresult rv = txn->RedoTransaction();
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
NS_IMETHODIMP
EditAggregateTransaction::Merge(nsITransaction* aTransaction,
bool* aDidMerge)
{
if (aDidMerge) {
*aDidMerge = false;
}
if (mChildren.IsEmpty()) {
return NS_OK;
}
// FIXME: Is this really intended not to loop? It looks like the code
// that used to be here sort of intended to loop, but didn't.
nsITransaction *txn = mChildren[0];
if (!txn) {
return NS_ERROR_NULL_POINTER;
}
return txn->Merge(aTransaction, aDidMerge);
}
NS_IMETHODIMP
EditAggregateTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("EditAggregateTransaction: ");
if (mName) {
nsAutoString name;
mName->ToString(name);
aString += name;
}
return NS_OK;
}
NS_IMETHODIMP
EditAggregateTransaction::AppendChild(EditTransactionBase* aTransaction)
{
if (!aTransaction) {
return NS_ERROR_NULL_POINTER;
}
RefPtr<EditTransactionBase>* slot = mChildren.AppendElement();
if (!slot) {
return NS_ERROR_OUT_OF_MEMORY;
}
*slot = aTransaction;
return NS_OK;
}
NS_IMETHODIMP
EditAggregateTransaction::GetName(nsIAtom** aName)
{
if (aName && mName) {
*aName = mName;
NS_ADDREF(*aName);
return NS_OK;
}
return NS_ERROR_NULL_POINTER;
}
} // namespace mozilla

View file

@ -0,0 +1,58 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef EditAggregateTransaction_h
#define EditAggregateTransaction_h
#include "mozilla/EditTransactionBase.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIAtom.h"
#include "nsISupportsImpl.h"
#include "nsTArray.h"
#include "nscore.h"
class nsITransaction;
namespace mozilla {
/**
* base class for all document editing transactions that require aggregation.
* provides a list of child transactions.
*/
class EditAggregateTransaction : public EditTransactionBase
{
public:
EditAggregateTransaction();
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(EditAggregateTransaction,
EditTransactionBase)
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD RedoTransaction() override;
NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aDidMerge) override;
/**
* Append a transaction to this aggregate.
*/
NS_IMETHOD AppendChild(EditTransactionBase* aTransaction);
/**
* Get the name assigned to this transaction.
*/
NS_IMETHOD GetName(nsIAtom** aName);
protected:
virtual ~EditAggregateTransaction();
nsTArray<RefPtr<EditTransactionBase>> mChildren;
nsCOMPtr<nsIAtom> mName;
};
} // namespace mozilla
#endif // #ifndef EditAggregateTransaction_h

View file

@ -0,0 +1,55 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/EditTransactionBase.h"
#include "nsError.h"
#include "nsISupportsBase.h"
namespace mozilla {
NS_IMPL_CYCLE_COLLECTION_CLASS(EditTransactionBase)
NS_IMPL_CYCLE_COLLECTION_UNLINK_0(EditTransactionBase)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EditTransactionBase)
// We don't have anything to traverse, but some of our subclasses do.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditTransactionBase)
NS_INTERFACE_MAP_ENTRY(nsITransaction)
NS_INTERFACE_MAP_ENTRY(nsPIEditorTransaction)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransaction)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(EditTransactionBase)
NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(EditTransactionBase,
LastRelease())
EditTransactionBase::~EditTransactionBase()
{
}
NS_IMETHODIMP
EditTransactionBase::RedoTransaction()
{
return DoTransaction();
}
NS_IMETHODIMP
EditTransactionBase::GetIsTransient(bool* aIsTransient)
{
*aIsTransient = false;
return NS_OK;
}
NS_IMETHODIMP
EditTransactionBase::Merge(nsITransaction* aTransaction, bool* aDidMerge)
{
*aDidMerge = false;
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,44 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_EditTransactionBase_h
#define mozilla_EditTransactionBase_h
#include "nsCycleCollectionParticipant.h"
#include "nsISupportsImpl.h"
#include "nsITransaction.h"
#include "nsPIEditorTransaction.h"
#include "nscore.h"
namespace mozilla {
/**
* Base class for all document editing transactions.
*/
class EditTransactionBase : public nsITransaction
, public nsPIEditorTransaction
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(EditTransactionBase, nsITransaction)
virtual void LastRelease() {}
NS_IMETHOD RedoTransaction(void) override;
NS_IMETHOD GetIsTransient(bool* aIsTransient) override;
NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aDidMerge) override;
protected:
virtual ~EditTransactionBase();
};
} // namespace mozilla
#define NS_DECL_EDITTRANSACTIONBASE \
NS_IMETHOD DoTransaction() override; \
NS_IMETHOD UndoTransaction() override; \
NS_IMETHOD GetTxnDescription(nsAString& aTransactionDescription) override;
#endif // #ifndef mozilla_EditTransactionBase_h

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,106 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef EditorCommands_h_
#define EditorCommands_h_
#include "nsIControllerCommand.h"
#include "nsISupportsImpl.h"
#include "nscore.h"
class nsICommandParams;
class nsISupports;
namespace mozilla {
/**
* This is a virtual base class for commands registered with the editor
* controller. Note that such commands can be shared by more than on editor
* instance, so MUST be stateless. Any state must be stored via the refCon
* (an nsIEditor).
*/
class EditorCommandBase : public nsIControllerCommand
{
public:
EditorCommandBase();
NS_DECL_ISUPPORTS
NS_IMETHOD IsCommandEnabled(const char* aCommandName,
nsISupports* aCommandRefCon,
bool* aIsEnabled) override = 0;
NS_IMETHOD DoCommand(const char* aCommandName,
nsISupports* aCommandRefCon) override = 0;
protected:
virtual ~EditorCommandBase() {}
};
#define NS_DECL_EDITOR_COMMAND(_cmd) \
class _cmd final : public EditorCommandBase \
{ \
public: \
NS_IMETHOD IsCommandEnabled(const char* aCommandName, \
nsISupports* aCommandRefCon, \
bool* aIsEnabled) override; \
NS_IMETHOD DoCommand(const char* aCommandName, \
nsISupports* aCommandRefCon) override; \
NS_IMETHOD DoCommandParams(const char* aCommandName, \
nsICommandParams* aParams, \
nsISupports* aCommandRefCon) override; \
NS_IMETHOD GetCommandStateParams(const char* aCommandName, \
nsICommandParams* aParams, \
nsISupports* aCommandRefCon) override; \
};
// basic editor commands
NS_DECL_EDITOR_COMMAND(UndoCommand)
NS_DECL_EDITOR_COMMAND(RedoCommand)
NS_DECL_EDITOR_COMMAND(ClearUndoCommand)
NS_DECL_EDITOR_COMMAND(CutCommand)
NS_DECL_EDITOR_COMMAND(CutOrDeleteCommand)
NS_DECL_EDITOR_COMMAND(CopyCommand)
NS_DECL_EDITOR_COMMAND(CopyOrDeleteCommand)
NS_DECL_EDITOR_COMMAND(CopyAndCollapseToEndCommand)
NS_DECL_EDITOR_COMMAND(PasteCommand)
NS_DECL_EDITOR_COMMAND(PasteTransferableCommand)
NS_DECL_EDITOR_COMMAND(SwitchTextDirectionCommand)
NS_DECL_EDITOR_COMMAND(DeleteCommand)
NS_DECL_EDITOR_COMMAND(SelectAllCommand)
NS_DECL_EDITOR_COMMAND(SelectionMoveCommands)
// Insert content commands
NS_DECL_EDITOR_COMMAND(InsertPlaintextCommand)
NS_DECL_EDITOR_COMMAND(InsertParagraphCommand)
NS_DECL_EDITOR_COMMAND(InsertLineBreakCommand)
NS_DECL_EDITOR_COMMAND(PasteQuotationCommand)
#if 0
// template for new command
NS_IMETHODIMP
FooCommand::IsCommandEnabled(const char* aCommandName,
nsISupports* aCommandRefCon,
bool* aIsEnabled)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
FooCommand::DoCommand(const char* aCommandName,
const nsAString& aCommandParams,
nsISupports* aCommandRefCon)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
#endif
} // namespace mozilla
#endif // #ifndef EditorCommands_h_

View file

@ -0,0 +1,146 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/EditorController.h"
#include "EditorCommands.h"
#include "mozilla/mozalloc.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIControllerCommandTable.h"
class nsIControllerCommand;
namespace mozilla {
#define NS_REGISTER_ONE_COMMAND(_cmdClass, _cmdName) \
{ \
_cmdClass* theCmd = new _cmdClass(); \
NS_ENSURE_TRUE(theCmd, NS_ERROR_OUT_OF_MEMORY); \
aCommandTable->RegisterCommand( \
_cmdName, \
static_cast<nsIControllerCommand *>(theCmd)); \
}
#define NS_REGISTER_FIRST_COMMAND(_cmdClass, _cmdName) \
{ \
_cmdClass* theCmd = new _cmdClass(); \
NS_ENSURE_TRUE(theCmd, NS_ERROR_OUT_OF_MEMORY); \
aCommandTable->RegisterCommand( \
_cmdName, \
static_cast<nsIControllerCommand *>(theCmd));
#define NS_REGISTER_NEXT_COMMAND(_cmdClass, _cmdName) \
aCommandTable->RegisterCommand( \
_cmdName, \
static_cast<nsIControllerCommand *>(theCmd));
#define NS_REGISTER_LAST_COMMAND(_cmdClass, _cmdName) \
aCommandTable->RegisterCommand( \
_cmdName, \
static_cast<nsIControllerCommand *>(theCmd)); \
}
// static
nsresult
EditorController::RegisterEditingCommands(
nsIControllerCommandTable* aCommandTable)
{
// now register all our commands
// These are commands that will be used in text widgets, and in composer
NS_REGISTER_ONE_COMMAND(UndoCommand, "cmd_undo");
NS_REGISTER_ONE_COMMAND(RedoCommand, "cmd_redo");
NS_REGISTER_ONE_COMMAND(ClearUndoCommand, "cmd_clearUndo");
NS_REGISTER_ONE_COMMAND(CutCommand, "cmd_cut");
NS_REGISTER_ONE_COMMAND(CutOrDeleteCommand, "cmd_cutOrDelete");
NS_REGISTER_ONE_COMMAND(CopyCommand, "cmd_copy");
NS_REGISTER_ONE_COMMAND(CopyOrDeleteCommand, "cmd_copyOrDelete");
NS_REGISTER_ONE_COMMAND(CopyAndCollapseToEndCommand,
"cmd_copyAndCollapseToEnd");
NS_REGISTER_ONE_COMMAND(SelectAllCommand, "cmd_selectAll");
NS_REGISTER_ONE_COMMAND(PasteCommand, "cmd_paste");
NS_REGISTER_ONE_COMMAND(PasteTransferableCommand, "cmd_pasteTransferable");
NS_REGISTER_ONE_COMMAND(SwitchTextDirectionCommand,
"cmd_switchTextDirection");
NS_REGISTER_FIRST_COMMAND(DeleteCommand, "cmd_delete");
NS_REGISTER_NEXT_COMMAND(DeleteCommand, "cmd_deleteCharBackward");
NS_REGISTER_NEXT_COMMAND(DeleteCommand, "cmd_deleteCharForward");
NS_REGISTER_NEXT_COMMAND(DeleteCommand, "cmd_deleteWordBackward");
NS_REGISTER_NEXT_COMMAND(DeleteCommand, "cmd_deleteWordForward");
NS_REGISTER_NEXT_COMMAND(DeleteCommand, "cmd_deleteToBeginningOfLine");
NS_REGISTER_LAST_COMMAND(DeleteCommand, "cmd_deleteToEndOfLine");
// Insert content
NS_REGISTER_ONE_COMMAND(InsertPlaintextCommand, "cmd_insertText");
NS_REGISTER_ONE_COMMAND(InsertParagraphCommand, "cmd_insertParagraph");
NS_REGISTER_ONE_COMMAND(InsertLineBreakCommand, "cmd_insertLineBreak");
NS_REGISTER_ONE_COMMAND(PasteQuotationCommand, "cmd_pasteQuote");
return NS_OK;
}
// static
nsresult
EditorController::RegisterEditorCommands(
nsIControllerCommandTable* aCommandTable)
{
// These are commands that will be used in text widgets only.
NS_REGISTER_FIRST_COMMAND(SelectionMoveCommands, "cmd_scrollTop");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_scrollBottom");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_moveTop");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_moveBottom");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectTop");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectBottom");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_lineNext");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_linePrevious");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectLineNext");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectLinePrevious");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_charPrevious");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_charNext");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectCharPrevious");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectCharNext");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_beginLine");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_endLine");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectBeginLine");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectEndLine");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_wordPrevious");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_wordNext");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectWordPrevious");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectWordNext");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_scrollPageUp");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_scrollPageDown");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_scrollLineUp");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_scrollLineDown");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_movePageUp");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_movePageDown");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectPageUp");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectPageDown");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_moveLeft");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_moveRight");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_moveUp");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_moveDown");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_moveLeft2");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_moveRight2");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_moveUp2");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_moveDown2");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectLeft");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectRight");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectUp");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectDown");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectLeft2");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectRight2");
NS_REGISTER_NEXT_COMMAND(SelectionMoveCommands, "cmd_selectUp2");
NS_REGISTER_LAST_COMMAND(SelectionMoveCommands, "cmd_selectDown2");
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,37 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_EditorController_h
#define mozilla_EditorController_h
#include "nscore.h"
#define NS_EDITORCONTROLLER_CID \
{ 0x26fb965c, 0x9de6, 0x11d3, \
{ 0xbc, 0xcc, 0x0, 0x60, 0xb0, 0xfc, 0x76, 0xbd } }
#define NS_EDITINGCONTROLLER_CID \
{ 0x2c5a5cdd, 0xe742, 0x4dfe, \
{ 0x86, 0xb8, 0x06, 0x93, 0x09, 0xbf, 0x6c, 0x91 } }
class nsIControllerCommandTable;
namespace mozilla {
// the editor controller is used for both text widgets, and basic text editing
// commands in composer. The refCon that gets passed to its commands is an nsIEditor.
class EditorController final
{
public:
static nsresult RegisterEditorCommands(
nsIControllerCommandTable* aCommandTable);
static nsresult RegisterEditingCommands(
nsIControllerCommandTable* aCommandTable);
};
} // namespace mozilla
#endif // #ifndef mozilla_EditorController_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,102 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef EditorEventListener_h
#define EditorEventListener_h
#include "nsCOMPtr.h"
#include "nsError.h"
#include "nsIDOMEventListener.h"
#include "nsISupportsImpl.h"
#include "nscore.h"
class nsCaret;
class nsIContent;
class nsIDOMDragEvent;
class nsIDOMEvent;
class nsIDOMKeyEvent;
class nsIDOMMouseEvent;
class nsIPresShell;
class nsPresContext;
// X.h defines KeyPress
#ifdef KeyPress
#undef KeyPress
#endif
#ifdef XP_WIN
// On Windows, we support switching the text direction by pressing Ctrl+Shift
#define HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
#endif
namespace mozilla {
class EditorBase;
class EditorEventListener : public nsIDOMEventListener
{
public:
EditorEventListener();
virtual nsresult Connect(EditorBase* aEditorBase);
void Disconnect();
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
void SpellCheckIfNeeded();
protected:
virtual ~EditorEventListener();
nsresult InstallToEditor();
void UninstallFromEditor();
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
nsresult KeyDown(nsIDOMKeyEvent* aKeyEvent);
nsresult KeyUp(nsIDOMKeyEvent* aKeyEvent);
#endif
nsresult KeyPress(nsIDOMKeyEvent* aKeyEvent);
nsresult HandleText(nsIDOMEvent* aTextEvent);
nsresult HandleStartComposition(nsIDOMEvent* aCompositionEvent);
void HandleEndComposition(nsIDOMEvent* aCompositionEvent);
virtual nsresult MouseDown(nsIDOMMouseEvent* aMouseEvent);
virtual nsresult MouseUp(nsIDOMMouseEvent* aMouseEvent) { return NS_OK; }
virtual nsresult MouseClick(nsIDOMMouseEvent* aMouseEvent);
nsresult Focus(nsIDOMEvent* aEvent);
nsresult Blur(nsIDOMEvent* aEvent);
nsresult DragEnter(nsIDOMDragEvent* aDragEvent);
nsresult DragOver(nsIDOMDragEvent* aDragEvent);
nsresult DragExit(nsIDOMDragEvent* aDragEvent);
nsresult Drop(nsIDOMDragEvent* aDragEvent);
bool CanDrop(nsIDOMDragEvent* aEvent);
void CleanupDragDropCaret();
already_AddRefed<nsIPresShell> GetPresShell();
nsPresContext* GetPresContext();
nsIContent* GetFocusedRootContent();
// Returns true if IME consumes the mouse event.
bool NotifyIMEOfMouseButtonEvent(nsIDOMMouseEvent* aMouseEvent);
bool EditorHasFocus();
bool IsFileControlTextBox();
bool ShouldHandleNativeKeyBindings(nsIDOMKeyEvent* aKeyEvent);
nsresult HandleMiddleClickPaste(nsIDOMMouseEvent* aMouseEvent);
EditorBase* mEditorBase; // weak
RefPtr<nsCaret> mCaret;
bool mCommitText;
bool mInTransaction;
bool mMouseDownOrUpConsumedByIME;
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
bool mHaveBidiKeyboards;
bool mShouldSwitchTextDirection;
bool mSwitchToRTL;
#endif
};
} // namespace mozilla
#endif // #ifndef EditorEventListener_h

View file

@ -0,0 +1,227 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/EditorUtils.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/dom/Selection.h"
#include "nsComponentManagerUtils.h"
#include "nsError.h"
#include "nsIClipboardDragDropHookList.h"
// hooks
#include "nsIClipboardDragDropHooks.h"
#include "nsIContent.h"
#include "nsIContentIterator.h"
#include "nsIDOMDocument.h"
#include "nsIDocShell.h"
#include "nsIDocument.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsINode.h"
#include "nsISimpleEnumerator.h"
class nsISupports;
class nsRange;
namespace mozilla {
using namespace dom;
/******************************************************************************
* AutoSelectionRestorer
*****************************************************************************/
AutoSelectionRestorer::AutoSelectionRestorer(
Selection* aSelection,
EditorBase* aEditorBase
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: mEditorBase(nullptr)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (NS_WARN_IF(!aSelection) || NS_WARN_IF(!aEditorBase)) {
return;
}
if (aEditorBase->ArePreservingSelection()) {
// We already have initialized mSavedSel, so this must be nested call.
return;
}
mSelection = aSelection;
mEditorBase = aEditorBase;
mEditorBase->PreserveSelectionAcrossActions(mSelection);
}
AutoSelectionRestorer::~AutoSelectionRestorer()
{
NS_ASSERTION(!mSelection || mEditorBase,
"mEditorBase should be non-null when mSelection is");
// mSelection will be null if this was nested call.
if (mSelection && mEditorBase->ArePreservingSelection()) {
mEditorBase->RestorePreservedSelection(mSelection);
}
}
void
AutoSelectionRestorer::Abort()
{
NS_ASSERTION(!mSelection || mEditorBase,
"mEditorBase should be non-null when mSelection is");
if (mSelection) {
mEditorBase->StopPreservingSelection();
}
}
/******************************************************************************
* some helper classes for iterating the dom tree
*****************************************************************************/
DOMIterator::DOMIterator(nsINode& aNode MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
mIter = NS_NewContentIterator();
DebugOnly<nsresult> rv = mIter->Init(&aNode);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
nsresult
DOMIterator::Init(nsRange& aRange)
{
mIter = NS_NewContentIterator();
return mIter->Init(&aRange);
}
DOMIterator::DOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
DOMIterator::~DOMIterator()
{
}
void
DOMIterator::AppendList(const BoolDomIterFunctor& functor,
nsTArray<OwningNonNull<nsINode>>& arrayOfNodes) const
{
// Iterate through dom and build list
for (; !mIter->IsDone(); mIter->Next()) {
nsCOMPtr<nsINode> node = mIter->GetCurrentNode();
if (functor(node)) {
arrayOfNodes.AppendElement(*node);
}
}
}
DOMSubtreeIterator::DOMSubtreeIterator(
MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
: DOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT)
{
}
nsresult
DOMSubtreeIterator::Init(nsRange& aRange)
{
mIter = NS_NewContentSubtreeIterator();
return mIter->Init(&aRange);
}
DOMSubtreeIterator::~DOMSubtreeIterator()
{
}
/******************************************************************************
* some general purpose editor utils
*****************************************************************************/
bool
EditorUtils::IsDescendantOf(nsINode* aNode,
nsINode* aParent,
int32_t* aOffset)
{
MOZ_ASSERT(aNode && aParent);
if (aNode == aParent) {
return false;
}
for (nsCOMPtr<nsINode> node = aNode; node; node = node->GetParentNode()) {
if (node->GetParentNode() == aParent) {
if (aOffset) {
*aOffset = aParent->IndexOf(node);
}
return true;
}
}
return false;
}
bool
EditorUtils::IsDescendantOf(nsIDOMNode* aNode,
nsIDOMNode* aParent,
int32_t* aOffset)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
NS_ENSURE_TRUE(node && parent, false);
return IsDescendantOf(node, parent, aOffset);
}
bool
EditorUtils::IsLeafNode(nsIDOMNode* aNode)
{
bool hasChildren = false;
if (aNode)
aNode->HasChildNodes(&hasChildren);
return !hasChildren;
}
/******************************************************************************
* utility methods for drag/drop/copy/paste hooks
*****************************************************************************/
nsresult
EditorHookUtils::GetHookEnumeratorFromDocument(nsIDOMDocument* aDoc,
nsISimpleEnumerator** aResult)
{
nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
nsCOMPtr<nsIClipboardDragDropHookList> hookObj = do_GetInterface(docShell);
NS_ENSURE_TRUE(hookObj, NS_ERROR_FAILURE);
return hookObj->GetHookEnumerator(aResult);
}
bool
EditorHookUtils::DoInsertionHook(nsIDOMDocument* aDoc,
nsIDOMEvent* aDropEvent,
nsITransferable *aTrans)
{
nsCOMPtr<nsISimpleEnumerator> enumerator;
GetHookEnumeratorFromDocument(aDoc, getter_AddRefs(enumerator));
NS_ENSURE_TRUE(enumerator, true);
bool hasMoreHooks = false;
while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreHooks)) &&
hasMoreHooks) {
nsCOMPtr<nsISupports> isupp;
if (NS_FAILED(enumerator->GetNext(getter_AddRefs(isupp)))) {
break;
}
nsCOMPtr<nsIClipboardDragDropHooks> override = do_QueryInterface(isupp);
if (override) {
bool doInsert = true;
DebugOnly<nsresult> hookResult =
override->OnPasteOrDrop(aDropEvent, aTrans, &doInsert);
NS_ASSERTION(NS_SUCCEEDED(hookResult), "hook failure in OnPasteOrDrop");
NS_ENSURE_TRUE(doInsert, false);
}
}
return true;
}
} // namespace mozilla

View file

@ -0,0 +1,315 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_EditorUtils_h
#define mozilla_EditorUtils_h
#include "mozilla/EditorBase.h"
#include "mozilla/GuardObjects.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsIDOMNode.h"
#include "nsIEditor.h"
#include "nscore.h"
class nsIAtom;
class nsIContentIterator;
class nsIDOMDocument;
class nsIDOMEvent;
class nsISimpleEnumerator;
class nsITransferable;
class nsRange;
namespace mozilla {
template <class T> class OwningNonNull;
namespace dom {
class Selection;
} // namespace dom
/***************************************************************************
* stack based helper class for batching a collection of txns inside a
* placeholder txn.
*/
class MOZ_RAII AutoPlaceHolderBatch
{
private:
nsCOMPtr<nsIEditor> mEditor;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
public:
AutoPlaceHolderBatch(nsIEditor* aEditor,
nsIAtom* aAtom
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mEditor(aEditor)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (mEditor) {
mEditor->BeginPlaceHolderTransaction(aAtom);
}
}
~AutoPlaceHolderBatch()
{
if (mEditor) {
mEditor->EndPlaceHolderTransaction();
}
}
};
/***************************************************************************
* stack based helper class for batching a collection of txns.
* Note: I changed this to use placeholder batching so that we get
* proper selection save/restore across undo/redo.
*/
class MOZ_RAII AutoEditBatch final : public AutoPlaceHolderBatch
{
private:
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
public:
explicit AutoEditBatch(nsIEditor* aEditor
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: AutoPlaceHolderBatch(aEditor, nullptr)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
~AutoEditBatch() {}
};
/***************************************************************************
* stack based helper class for saving/restoring selection. Note that this
* assumes that the nodes involved are still around afterwards!
*/
class MOZ_RAII AutoSelectionRestorer final
{
private:
// Ref-counted reference to the selection that we are supposed to restore.
RefPtr<dom::Selection> mSelection;
EditorBase* mEditorBase; // Non-owning ref to EditorBase.
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
public:
/**
* Constructor responsible for remembering all state needed to restore
* aSelection.
*/
AutoSelectionRestorer(dom::Selection* aSelection,
EditorBase* aEditorBase
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
/**
* Destructor restores mSelection to its former state
*/
~AutoSelectionRestorer();
/**
* Abort() cancels to restore the selection.
*/
void Abort();
};
/***************************************************************************
* stack based helper class for StartOperation()/EndOperation() sandwich
*/
class MOZ_RAII AutoRules final
{
public:
AutoRules(EditorBase* aEditorBase, EditAction aAction,
nsIEditor::EDirection aDirection
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mEditorBase(aEditorBase)
, mDoNothing(false)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
// mAction will already be set if this is nested call
if (mEditorBase && !mEditorBase->mAction) {
mEditorBase->StartOperation(aAction, aDirection);
} else {
mDoNothing = true; // nested calls will end up here
}
}
~AutoRules()
{
if (mEditorBase && !mDoNothing) {
mEditorBase->EndOperation();
}
}
protected:
EditorBase* mEditorBase;
bool mDoNothing;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
/***************************************************************************
* stack based helper class for turning off active selection adjustment
* by low level transactions
*/
class MOZ_RAII AutoTransactionsConserveSelection final
{
public:
explicit AutoTransactionsConserveSelection(EditorBase* aEditorBase
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mEditorBase(aEditorBase)
, mOldState(true)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (mEditorBase) {
mOldState = mEditorBase->GetShouldTxnSetSelection();
mEditorBase->SetShouldTxnSetSelection(false);
}
}
~AutoTransactionsConserveSelection()
{
if (mEditorBase) {
mEditorBase->SetShouldTxnSetSelection(mOldState);
}
}
protected:
EditorBase* mEditorBase;
bool mOldState;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
/***************************************************************************
* stack based helper class for batching reflow and paint requests.
*/
class MOZ_RAII AutoUpdateViewBatch final
{
public:
explicit AutoUpdateViewBatch(EditorBase* aEditorBase
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mEditorBase(aEditorBase)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
NS_ASSERTION(mEditorBase, "null mEditorBase pointer!");
if (mEditorBase) {
mEditorBase->BeginUpdateViewBatch();
}
}
~AutoUpdateViewBatch()
{
if (mEditorBase) {
mEditorBase->EndUpdateViewBatch();
}
}
protected:
EditorBase* mEditorBase;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
/******************************************************************************
* some helper classes for iterating the dom tree
*****************************************************************************/
class BoolDomIterFunctor
{
public:
virtual bool operator()(nsINode* aNode) const = 0;
};
class MOZ_RAII DOMIterator
{
public:
explicit DOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
explicit DOMIterator(nsINode& aNode MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
virtual ~DOMIterator();
nsresult Init(nsRange& aRange);
void AppendList(
const BoolDomIterFunctor& functor,
nsTArray<mozilla::OwningNonNull<nsINode>>& arrayOfNodes) const;
protected:
nsCOMPtr<nsIContentIterator> mIter;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
class MOZ_RAII DOMSubtreeIterator final : public DOMIterator
{
public:
explicit DOMSubtreeIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
virtual ~DOMSubtreeIterator();
nsresult Init(nsRange& aRange);
};
class TrivialFunctor final : public BoolDomIterFunctor
{
public:
// Used to build list of all nodes iterator covers
virtual bool operator()(nsINode* aNode) const
{
return true;
}
};
/******************************************************************************
* general dom point utility struct
*****************************************************************************/
struct MOZ_STACK_CLASS EditorDOMPoint final
{
nsCOMPtr<nsINode> node;
int32_t offset;
EditorDOMPoint()
: node(nullptr)
, offset(-1)
{}
EditorDOMPoint(nsINode* aNode, int32_t aOffset)
: node(aNode)
, offset(aOffset)
{}
EditorDOMPoint(nsIDOMNode* aNode, int32_t aOffset)
: node(do_QueryInterface(aNode))
, offset(aOffset)
{}
void SetPoint(nsINode* aNode, int32_t aOffset)
{
node = aNode;
offset = aOffset;
}
void SetPoint(nsIDOMNode* aNode, int32_t aOffset)
{
node = do_QueryInterface(aNode);
offset = aOffset;
}
};
class EditorUtils final
{
public:
static bool IsDescendantOf(nsINode* aNode, nsINode* aParent,
int32_t* aOffset = 0);
static bool IsDescendantOf(nsIDOMNode* aNode, nsIDOMNode* aParent,
int32_t* aOffset = 0);
static bool IsLeafNode(nsIDOMNode* aNode);
};
class EditorHookUtils final
{
public:
static bool DoInsertionHook(nsIDOMDocument* aDoc, nsIDOMEvent* aEvent,
nsITransferable* aTrans);
private:
static nsresult GetHookEnumeratorFromDocument(
nsIDOMDocument*aDoc,
nsISimpleEnumerator** aEnumerator);
};
} // namespace mozilla
#endif // #ifndef mozilla_EditorUtils_h

View file

@ -0,0 +1,34 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
"use strict";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const EDITORUTILS_CID = Components.ID('{12e63991-86ac-4dff-bb1a-703495d67d17}');
function EditorUtils() {
}
EditorUtils.prototype = {
classID: EDITORUTILS_CID,
QueryInterface: XPCOMUtils.generateQI([ Ci.nsIEditorUtils ]),
slurpBlob(aBlob, aScope, aListener) {
let reader = new aScope.FileReader();
reader.addEventListener("load", (event) => {
aListener.onResult(event.target.result);
});
reader.addEventListener("error", (event) => {
aListener.onError(event.target.error.message);
});
reader.readAsBinaryString(aBlob);
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EditorUtils]);

View file

@ -0,0 +1,2 @@
component {12e63991-86ac-4dff-bb1a-703495d67d17} EditorUtils.js
contract @mozilla.org/editor-utils;1 {12e63991-86ac-4dff-bb1a-703495d67d17}

View file

@ -0,0 +1,682 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/HTMLEditor.h"
#include <math.h>
#include "HTMLEditorObjectResizerUtils.h"
#include "HTMLEditRules.h"
#include "HTMLEditUtils.h"
#include "TextEditUtils.h"
#include "mozilla/EditorUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Element.h"
#include "mozilla/mozalloc.h"
#include "nsAString.h"
#include "nsAlgorithm.h"
#include "nsCOMPtr.h"
#include "nsComputedDOMStyle.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsGkAtoms.h"
#include "nsIContent.h"
#include "nsROCSSPrimitiveValue.h"
#include "nsIDOMCSSStyleDeclaration.h"
#include "nsIDOMElement.h"
#include "nsIDOMEventListener.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMNode.h"
#include "nsDOMCSSRGBColor.h"
#include "nsIDOMWindow.h"
#include "nsIEditor.h"
#include "nsIEditRules.h"
#include "nsIHTMLEditor.h"
#include "nsIHTMLObjectResizer.h"
#include "nsINode.h"
#include "nsIPresShell.h"
#include "nsISupportsImpl.h"
#include "nsISupportsUtils.h"
#include "nsLiteralString.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nscore.h"
#include <algorithm>
namespace mozilla {
using namespace dom;
#define BLACK_BG_RGB_TRIGGER 0xd0
NS_IMETHODIMP
HTMLEditor::AbsolutePositionSelection(bool aEnabled)
{
AutoEditBatch beginBatching(this);
AutoRules beginRulesSniffing(this,
aEnabled ? EditAction::setAbsolutePosition :
EditAction::removeAbsolutePosition,
nsIEditor::eNext);
// the line below does not match the code; should it be removed?
// Find out if the selection is collapsed:
RefPtr<Selection> selection = GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
TextRulesInfo ruleInfo(aEnabled ? EditAction::setAbsolutePosition :
EditAction::removeAbsolutePosition);
bool cancel, handled;
// Protect the edit rules object from dying
nsCOMPtr<nsIEditRules> rules(mRules);
nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
if (NS_FAILED(rv) || cancel) {
return rv;
}
return rules->DidDoAction(selection, &ruleInfo, rv);
}
NS_IMETHODIMP
HTMLEditor::GetAbsolutelyPositionedSelectionContainer(nsIDOMElement** _retval)
{
nsAutoString positionStr;
nsCOMPtr<nsINode> node = GetSelectionContainer();
nsCOMPtr<nsIDOMNode> resultNode;
while (!resultNode && node && !node->IsHTMLElement(nsGkAtoms::html)) {
nsresult rv =
mCSSEditUtils->GetComputedProperty(*node, *nsGkAtoms::position,
positionStr);
NS_ENSURE_SUCCESS(rv, rv);
if (positionStr.EqualsLiteral("absolute"))
resultNode = GetAsDOMNode(node);
else {
node = node->GetParentNode();
}
}
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(resultNode);
element.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::GetSelectionContainerAbsolutelyPositioned(
bool* aIsSelectionContainerAbsolutelyPositioned)
{
*aIsSelectionContainerAbsolutelyPositioned = (mAbsolutelyPositionedObject != nullptr);
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::GetAbsolutePositioningEnabled(bool* aIsEnabled)
{
*aIsEnabled = mIsAbsolutelyPositioningEnabled;
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::SetAbsolutePositioningEnabled(bool aIsEnabled)
{
mIsAbsolutelyPositioningEnabled = aIsEnabled;
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::RelativeChangeElementZIndex(nsIDOMElement* aElement,
int32_t aChange,
int32_t* aReturn)
{
NS_ENSURE_ARG_POINTER(aElement);
NS_ENSURE_ARG_POINTER(aReturn);
if (!aChange) // early way out, no change
return NS_OK;
int32_t zIndex;
nsresult rv = GetElementZIndex(aElement, &zIndex);
NS_ENSURE_SUCCESS(rv, rv);
zIndex = std::max(zIndex + aChange, 0);
SetElementZIndex(aElement, zIndex);
*aReturn = zIndex;
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::SetElementZIndex(nsIDOMElement* aElement,
int32_t aZindex)
{
nsCOMPtr<Element> element = do_QueryInterface(aElement);
NS_ENSURE_ARG_POINTER(element);
nsAutoString zIndexStr;
zIndexStr.AppendInt(aZindex);
mCSSEditUtils->SetCSSProperty(*element, *nsGkAtoms::z_index, zIndexStr);
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::RelativeChangeZIndex(int32_t aChange)
{
AutoEditBatch beginBatching(this);
AutoRules beginRulesSniffing(this,
(aChange < 0) ? EditAction::decreaseZIndex :
EditAction::increaseZIndex,
nsIEditor::eNext);
// brade: can we get rid of this comment?
// Find out if the selection is collapsed:
RefPtr<Selection> selection = GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
TextRulesInfo ruleInfo(aChange < 0 ? EditAction::decreaseZIndex :
EditAction::increaseZIndex);
bool cancel, handled;
// Protect the edit rules object from dying
nsCOMPtr<nsIEditRules> rules(mRules);
nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
if (cancel || NS_FAILED(rv)) {
return rv;
}
return rules->DidDoAction(selection, &ruleInfo, rv);
}
NS_IMETHODIMP
HTMLEditor::GetElementZIndex(nsIDOMElement* aElement,
int32_t* aZindex)
{
nsCOMPtr<Element> element = do_QueryInterface(aElement);
NS_ENSURE_STATE(element || !aElement);
nsAutoString zIndexStr;
*aZindex = 0;
nsresult rv =
mCSSEditUtils->GetSpecifiedProperty(*element, *nsGkAtoms::z_index,
zIndexStr);
NS_ENSURE_SUCCESS(rv, rv);
if (zIndexStr.EqualsLiteral("auto")) {
// we have to look at the positioned ancestors
// cf. CSS 2 spec section 9.9.1
nsCOMPtr<nsIDOMNode> parentNode;
rv = aElement->GetParentNode(getter_AddRefs(parentNode));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsINode> node = do_QueryInterface(parentNode);
nsAutoString positionStr;
while (node && zIndexStr.EqualsLiteral("auto") &&
!node->IsHTMLElement(nsGkAtoms::body)) {
rv = mCSSEditUtils->GetComputedProperty(*node, *nsGkAtoms::position,
positionStr);
NS_ENSURE_SUCCESS(rv, rv);
if (positionStr.EqualsLiteral("absolute")) {
// ah, we found one, what's its z-index ? If its z-index is auto,
// we have to continue climbing the document's tree
rv = mCSSEditUtils->GetComputedProperty(*node, *nsGkAtoms::z_index,
zIndexStr);
NS_ENSURE_SUCCESS(rv, rv);
}
node = node->GetParentNode();
}
}
if (!zIndexStr.EqualsLiteral("auto")) {
nsresult errorCode;
*aZindex = zIndexStr.ToInteger(&errorCode);
}
return NS_OK;
}
already_AddRefed<Element>
HTMLEditor::CreateGrabber(nsINode* aParentNode)
{
// let's create a grabber through the element factory
nsCOMPtr<nsIDOMElement> retDOM;
CreateAnonymousElement(NS_LITERAL_STRING("span"), GetAsDOMNode(aParentNode),
NS_LITERAL_STRING("mozGrabber"), false,
getter_AddRefs(retDOM));
NS_ENSURE_TRUE(retDOM, nullptr);
// add the mouse listener so we can detect a click on a resizer
nsCOMPtr<nsIDOMEventTarget> evtTarget(do_QueryInterface(retDOM));
evtTarget->AddEventListener(NS_LITERAL_STRING("mousedown"),
mEventListener, false);
nsCOMPtr<Element> ret = do_QueryInterface(retDOM);
return ret.forget();
}
NS_IMETHODIMP
HTMLEditor::RefreshGrabber()
{
NS_ENSURE_TRUE(mAbsolutelyPositionedObject, NS_ERROR_NULL_POINTER);
nsresult rv =
GetPositionAndDimensions(
static_cast<nsIDOMElement*>(GetAsDOMNode(mAbsolutelyPositionedObject)),
mPositionedObjectX,
mPositionedObjectY,
mPositionedObjectWidth,
mPositionedObjectHeight,
mPositionedObjectBorderLeft,
mPositionedObjectBorderTop,
mPositionedObjectMarginLeft,
mPositionedObjectMarginTop);
NS_ENSURE_SUCCESS(rv, rv);
SetAnonymousElementPosition(mPositionedObjectX+12,
mPositionedObjectY-14,
static_cast<nsIDOMElement*>(GetAsDOMNode(mGrabber)));
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::HideGrabber()
{
nsresult rv = mAbsolutelyPositionedObject->UnsetAttr(kNameSpaceID_None,
nsGkAtoms::_moz_abspos,
true);
NS_ENSURE_SUCCESS(rv, rv);
mAbsolutelyPositionedObject = nullptr;
NS_ENSURE_TRUE(mGrabber, NS_ERROR_NULL_POINTER);
// get the presshell's document observer interface.
nsCOMPtr<nsIPresShell> ps = GetPresShell();
// We allow the pres shell to be null; when it is, we presume there
// are no document observers to notify, but we still want to
// UnbindFromTree.
nsCOMPtr<nsIContent> parentContent = mGrabber->GetParent();
NS_ENSURE_TRUE(parentContent, NS_ERROR_NULL_POINTER);
DeleteRefToAnonymousNode(static_cast<nsIDOMElement*>(GetAsDOMNode(mGrabber)), parentContent, ps);
mGrabber = nullptr;
DeleteRefToAnonymousNode(static_cast<nsIDOMElement*>(GetAsDOMNode(mPositioningShadow)), parentContent, ps);
mPositioningShadow = nullptr;
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::ShowGrabberOnElement(nsIDOMElement* aElement)
{
nsCOMPtr<Element> element = do_QueryInterface(aElement);
NS_ENSURE_ARG_POINTER(element);
if (NS_WARN_IF(!IsDescendantOfEditorRoot(element))) {
return NS_ERROR_UNEXPECTED;
}
if (mGrabber) {
NS_ERROR("call HideGrabber first");
return NS_ERROR_UNEXPECTED;
}
nsAutoString classValue;
nsresult rv = CheckPositionedElementBGandFG(aElement, classValue);
NS_ENSURE_SUCCESS(rv, rv);
rv = element->SetAttr(kNameSpaceID_None, nsGkAtoms::_moz_abspos,
classValue, true);
NS_ENSURE_SUCCESS(rv, rv);
// first, let's keep track of that element...
mAbsolutelyPositionedObject = element;
mGrabber = CreateGrabber(element->GetParentNode());
NS_ENSURE_TRUE(mGrabber, NS_ERROR_FAILURE);
// and set its position
return RefreshGrabber();
}
nsresult
HTMLEditor::StartMoving(nsIDOMElement* aHandle)
{
nsCOMPtr<nsINode> parentNode = mGrabber->GetParentNode();
// now, let's create the resizing shadow
mPositioningShadow = CreateShadow(GetAsDOMNode(parentNode),
static_cast<nsIDOMElement*>(GetAsDOMNode(mAbsolutelyPositionedObject)));
NS_ENSURE_TRUE(mPositioningShadow, NS_ERROR_FAILURE);
nsresult rv = SetShadowPosition(mPositioningShadow,
mAbsolutelyPositionedObject,
mPositionedObjectX, mPositionedObjectY);
NS_ENSURE_SUCCESS(rv, rv);
// make the shadow appear
mPositioningShadow->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class, true);
// position it
mCSSEditUtils->SetCSSPropertyPixels(*mPositioningShadow, *nsGkAtoms::width,
mPositionedObjectWidth);
mCSSEditUtils->SetCSSPropertyPixels(*mPositioningShadow, *nsGkAtoms::height,
mPositionedObjectHeight);
mIsMoving = true;
return NS_OK; // XXX Looks like nobody refers this result
}
void
HTMLEditor::SnapToGrid(int32_t& newX, int32_t& newY)
{
if (mSnapToGridEnabled && mGridSize) {
newX = (int32_t) floor( ((float)newX / (float)mGridSize) + 0.5f ) * mGridSize;
newY = (int32_t) floor( ((float)newY / (float)mGridSize) + 0.5f ) * mGridSize;
}
}
nsresult
HTMLEditor::GrabberClicked()
{
// add a mouse move listener to the editor
nsresult rv = NS_OK;
if (!mMouseMotionListenerP) {
mMouseMotionListenerP = new ResizerMouseMotionListener(this);
if (!mMouseMotionListenerP) {return NS_ERROR_NULL_POINTER;}
nsCOMPtr<nsIDOMEventTarget> piTarget = GetDOMEventTarget();
NS_ENSURE_TRUE(piTarget, NS_ERROR_FAILURE);
rv = piTarget->AddEventListener(NS_LITERAL_STRING("mousemove"),
mMouseMotionListenerP,
false, false);
NS_ASSERTION(NS_SUCCEEDED(rv),
"failed to register mouse motion listener");
}
mGrabberClicked = true;
return rv;
}
nsresult
HTMLEditor::EndMoving()
{
if (mPositioningShadow) {
nsCOMPtr<nsIPresShell> ps = GetPresShell();
NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
nsCOMPtr<nsIContent> parentContent = mGrabber->GetParent();
NS_ENSURE_TRUE(parentContent, NS_ERROR_FAILURE);
DeleteRefToAnonymousNode(static_cast<nsIDOMElement*>(GetAsDOMNode(mPositioningShadow)),
parentContent, ps);
mPositioningShadow = nullptr;
}
nsCOMPtr<nsIDOMEventTarget> piTarget = GetDOMEventTarget();
if (piTarget && mMouseMotionListenerP) {
DebugOnly<nsresult> rv =
piTarget->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
mMouseMotionListenerP,
false);
NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove mouse motion listener");
}
mMouseMotionListenerP = nullptr;
mGrabberClicked = false;
mIsMoving = false;
RefPtr<Selection> selection = GetSelection();
if (!selection) {
return NS_ERROR_NOT_INITIALIZED;
}
return CheckSelectionStateForAnonymousButtons(selection);
}
nsresult
HTMLEditor::SetFinalPosition(int32_t aX,
int32_t aY)
{
nsresult rv = EndMoving();
NS_ENSURE_SUCCESS(rv, rv);
// we have now to set the new width and height of the resized object
// we don't set the x and y position because we don't control that in
// a normal HTML layout
int32_t newX = mPositionedObjectX + aX - mOriginalX - (mPositionedObjectBorderLeft+mPositionedObjectMarginLeft);
int32_t newY = mPositionedObjectY + aY - mOriginalY - (mPositionedObjectBorderTop+mPositionedObjectMarginTop);
SnapToGrid(newX, newY);
nsAutoString x, y;
x.AppendInt(newX);
y.AppendInt(newY);
// we want one transaction only from a user's point of view
AutoEditBatch batchIt(this);
nsCOMPtr<Element> absolutelyPositionedObject =
do_QueryInterface(mAbsolutelyPositionedObject);
NS_ENSURE_STATE(absolutelyPositionedObject);
mCSSEditUtils->SetCSSPropertyPixels(*absolutelyPositionedObject,
*nsGkAtoms::top, newY);
mCSSEditUtils->SetCSSPropertyPixels(*absolutelyPositionedObject,
*nsGkAtoms::left, newX);
// keep track of that size
mPositionedObjectX = newX;
mPositionedObjectY = newY;
return RefreshResizers();
}
void
HTMLEditor::AddPositioningOffset(int32_t& aX,
int32_t& aY)
{
// Get the positioning offset
int32_t positioningOffset =
Preferences::GetInt("editor.positioning.offset", 0);
aX += positioningOffset;
aY += positioningOffset;
}
NS_IMETHODIMP
HTMLEditor::AbsolutelyPositionElement(nsIDOMElement* aElement,
bool aEnabled)
{
nsCOMPtr<Element> element = do_QueryInterface(aElement);
NS_ENSURE_ARG_POINTER(element);
nsAutoString positionStr;
mCSSEditUtils->GetComputedProperty(*element, *nsGkAtoms::position,
positionStr);
bool isPositioned = (positionStr.EqualsLiteral("absolute"));
// nothing to do if the element is already in the state we want
if (isPositioned == aEnabled)
return NS_OK;
AutoEditBatch batchIt(this);
if (aEnabled) {
int32_t x, y;
GetElementOrigin(aElement, x, y);
mCSSEditUtils->SetCSSProperty(*element, *nsGkAtoms::position,
NS_LITERAL_STRING("absolute"));
AddPositioningOffset(x, y);
SnapToGrid(x, y);
SetElementPosition(*element, x, y);
// we may need to create a br if the positioned element is alone in its
// container
nsCOMPtr<nsINode> element = do_QueryInterface(aElement);
NS_ENSURE_STATE(element);
nsINode* parentNode = element->GetParentNode();
if (parentNode->GetChildCount() == 1) {
nsCOMPtr<nsIDOMNode> brNode;
nsresult rv = CreateBR(parentNode->AsDOMNode(), 0, address_of(brNode));
NS_ENSURE_SUCCESS(rv, rv);
}
}
else {
mCSSEditUtils->RemoveCSSProperty(*element, *nsGkAtoms::position,
EmptyString());
mCSSEditUtils->RemoveCSSProperty(*element, *nsGkAtoms::top,
EmptyString());
mCSSEditUtils->RemoveCSSProperty(*element, *nsGkAtoms::left,
EmptyString());
mCSSEditUtils->RemoveCSSProperty(*element, *nsGkAtoms::z_index,
EmptyString());
if (!HTMLEditUtils::IsImage(aElement)) {
mCSSEditUtils->RemoveCSSProperty(*element, *nsGkAtoms::width,
EmptyString());
mCSSEditUtils->RemoveCSSProperty(*element, *nsGkAtoms::height,
EmptyString());
}
nsCOMPtr<dom::Element> element = do_QueryInterface(aElement);
if (element && element->IsHTMLElement(nsGkAtoms::div) &&
!HasStyleOrIdOrClass(element)) {
RefPtr<HTMLEditRules> htmlRules =
static_cast<HTMLEditRules*>(mRules.get());
NS_ENSURE_TRUE(htmlRules, NS_ERROR_FAILURE);
nsresult rv = htmlRules->MakeSureElemStartsOrEndsOnCR(aElement);
NS_ENSURE_SUCCESS(rv, rv);
rv = RemoveContainer(element);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::SetSnapToGridEnabled(bool aEnabled)
{
mSnapToGridEnabled = aEnabled;
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::GetSnapToGridEnabled(bool* aIsEnabled)
{
*aIsEnabled = mSnapToGridEnabled;
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::SetGridSize(uint32_t aSize)
{
mGridSize = aSize;
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::GetGridSize(uint32_t* aSize)
{
*aSize = mGridSize;
return NS_OK;
}
// self-explanatory
NS_IMETHODIMP
HTMLEditor::SetElementPosition(nsIDOMElement* aElement,
int32_t aX,
int32_t aY)
{
nsCOMPtr<Element> element = do_QueryInterface(aElement);
NS_ENSURE_STATE(element);
SetElementPosition(*element, aX, aY);
return NS_OK;
}
void
HTMLEditor::SetElementPosition(Element& aElement,
int32_t aX,
int32_t aY)
{
AutoEditBatch batchIt(this);
mCSSEditUtils->SetCSSPropertyPixels(aElement, *nsGkAtoms::left, aX);
mCSSEditUtils->SetCSSPropertyPixels(aElement, *nsGkAtoms::top, aY);
}
// self-explanatory
NS_IMETHODIMP
HTMLEditor::GetPositionedElement(nsIDOMElement** aReturn)
{
nsCOMPtr<nsIDOMElement> ret =
static_cast<nsIDOMElement*>(GetAsDOMNode(mAbsolutelyPositionedObject));
ret.forget(aReturn);
return NS_OK;
}
nsresult
HTMLEditor::CheckPositionedElementBGandFG(nsIDOMElement* aElement,
nsAString& aReturn)
{
// we are going to outline the positioned element and bring it to the
// front to overlap any other element intersecting with it. But
// first, let's see what's the background and foreground colors of the
// positioned element.
// if background-image computed value is 'none,
// If the background color is 'auto' and R G B values of the foreground are
// each above #d0, use a black background
// If the background color is 'auto' and at least one of R G B values of
// the foreground is below #d0, use a white background
// Otherwise don't change background/foreground
nsCOMPtr<Element> element = do_QueryInterface(aElement);
NS_ENSURE_STATE(element || !aElement);
aReturn.Truncate();
nsAutoString bgImageStr;
nsresult rv =
mCSSEditUtils->GetComputedProperty(*element, *nsGkAtoms::background_image,
bgImageStr);
NS_ENSURE_SUCCESS(rv, rv);
if (bgImageStr.EqualsLiteral("none")) {
nsAutoString bgColorStr;
rv =
mCSSEditUtils->GetComputedProperty(*element, *nsGkAtoms::backgroundColor,
bgColorStr);
NS_ENSURE_SUCCESS(rv, rv);
if (bgColorStr.EqualsLiteral("transparent")) {
RefPtr<nsComputedDOMStyle> cssDecl =
mCSSEditUtils->GetComputedStyle(element);
NS_ENSURE_STATE(cssDecl);
// from these declarations, get the one we want and that one only
ErrorResult error;
RefPtr<dom::CSSValue> cssVal = cssDecl->GetPropertyCSSValue(NS_LITERAL_STRING("color"), error);
NS_ENSURE_TRUE(!error.Failed(), error.StealNSResult());
nsROCSSPrimitiveValue* val = cssVal->AsPrimitiveValue();
NS_ENSURE_TRUE(val, NS_ERROR_FAILURE);
if (nsIDOMCSSPrimitiveValue::CSS_RGBCOLOR == val->PrimitiveType()) {
nsDOMCSSRGBColor* rgbVal = val->GetRGBColorValue(error);
NS_ENSURE_TRUE(!error.Failed(), error.StealNSResult());
float r = rgbVal->Red()->
GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_NUMBER, error);
NS_ENSURE_TRUE(!error.Failed(), error.StealNSResult());
float g = rgbVal->Green()->
GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_NUMBER, error);
NS_ENSURE_TRUE(!error.Failed(), error.StealNSResult());
float b = rgbVal->Blue()->
GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_NUMBER, error);
NS_ENSURE_TRUE(!error.Failed(), error.StealNSResult());
if (r >= BLACK_BG_RGB_TRIGGER &&
g >= BLACK_BG_RGB_TRIGGER &&
b >= BLACK_BG_RGB_TRIGGER)
aReturn.AssignLiteral("black");
else
aReturn.AssignLiteral("white");
return NS_OK;
}
}
}
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,548 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/HTMLEditor.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/Element.h"
#include "mozilla/mozalloc.h"
#include "nsAString.h"
#include "nsCOMPtr.h"
#include "nsComputedDOMStyle.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsGkAtoms.h"
#include "nsIAtom.h"
#include "nsIContent.h"
#include "nsID.h"
#include "nsIDOMCSSPrimitiveValue.h"
#include "nsIDOMCSSStyleDeclaration.h"
#include "nsIDOMCSSValue.h"
#include "nsIDOMElement.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMHTMLElement.h"
#include "nsIDOMNode.h"
#include "nsIDOMWindow.h"
#include "nsIDocument.h"
#include "nsIDocumentObserver.h"
#include "nsIHTMLAbsPosEditor.h"
#include "nsIHTMLInlineTableEditor.h"
#include "nsIHTMLObjectResizer.h"
#include "nsStubMutationObserver.h"
#include "nsINode.h"
#include "nsIPresShell.h"
#include "nsISupportsImpl.h"
#include "nsISupportsUtils.h"
#include "nsLiteralString.h"
#include "nsPresContext.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsUnicharUtils.h"
#include "nscore.h"
#include "nsContentUtils.h" // for nsAutoScriptBlocker
class nsIDOMEventListener;
class nsISelection;
namespace mozilla {
using namespace dom;
// retrieve an integer stored into a CSS computed float value
static int32_t GetCSSFloatValue(nsIDOMCSSStyleDeclaration * aDecl,
const nsAString & aProperty)
{
MOZ_ASSERT(aDecl);
nsCOMPtr<nsIDOMCSSValue> value;
// get the computed CSSValue of the property
nsresult rv = aDecl->GetPropertyCSSValue(aProperty, getter_AddRefs(value));
if (NS_FAILED(rv) || !value) {
return 0;
}
// check the type of the returned CSSValue; we handle here only
// pixel and enum types
nsCOMPtr<nsIDOMCSSPrimitiveValue> val = do_QueryInterface(value);
uint16_t type;
val->GetPrimitiveType(&type);
float f = 0;
switch (type) {
case nsIDOMCSSPrimitiveValue::CSS_PX:
// the value is in pixels, just get it
rv = val->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX, &f);
NS_ENSURE_SUCCESS(rv, 0);
break;
case nsIDOMCSSPrimitiveValue::CSS_IDENT: {
// the value is keyword, we have to map these keywords into
// numeric values
nsAutoString str;
val->GetStringValue(str);
if (str.EqualsLiteral("thin")) {
f = 1;
} else if (str.EqualsLiteral("medium")) {
f = 3;
} else if (str.EqualsLiteral("thick")) {
f = 5;
}
break;
}
}
return (int32_t) f;
}
class ElementDeletionObserver final : public nsStubMutationObserver
{
public:
ElementDeletionObserver(nsIContent* aNativeAnonNode,
nsIContent* aObservedNode)
: mNativeAnonNode(aNativeAnonNode)
, mObservedNode(aObservedNode)
{}
NS_DECL_ISUPPORTS
NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
protected:
~ElementDeletionObserver() {}
nsIContent* mNativeAnonNode;
nsIContent* mObservedNode;
};
NS_IMPL_ISUPPORTS(ElementDeletionObserver, nsIMutationObserver)
void
ElementDeletionObserver::ParentChainChanged(nsIContent* aContent)
{
// If the native anonymous content has been unbound already in
// DeleteRefToAnonymousNode, mNativeAnonNode's parentNode is null.
if (aContent == mObservedNode && mNativeAnonNode &&
mNativeAnonNode->GetParentNode() == aContent) {
// If the observed node has been moved to another document, there isn't much
// we can do easily. But at least be safe and unbind the native anonymous
// content and stop observing changes.
if (mNativeAnonNode->OwnerDoc() != mObservedNode->OwnerDoc()) {
mObservedNode->RemoveMutationObserver(this);
mObservedNode = nullptr;
mNativeAnonNode->RemoveMutationObserver(this);
mNativeAnonNode->UnbindFromTree();
mNativeAnonNode = nullptr;
NS_RELEASE_THIS();
return;
}
// We're staying in the same document, just rebind the native anonymous
// node so that the subtree root points to the right object etc.
mNativeAnonNode->UnbindFromTree();
mNativeAnonNode->BindToTree(mObservedNode->GetUncomposedDoc(), mObservedNode,
mObservedNode, true);
}
}
void
ElementDeletionObserver::NodeWillBeDestroyed(const nsINode* aNode)
{
NS_ASSERTION(aNode == mNativeAnonNode || aNode == mObservedNode,
"Wrong aNode!");
if (aNode == mNativeAnonNode) {
mObservedNode->RemoveMutationObserver(this);
mObservedNode = nullptr;
} else {
mNativeAnonNode->RemoveMutationObserver(this);
mNativeAnonNode->UnbindFromTree();
mNativeAnonNode = nullptr;
}
NS_RELEASE_THIS();
}
// Returns in *aReturn an anonymous nsDOMElement of type aTag,
// child of aParentNode. If aIsCreatedHidden is true, the class
// "hidden" is added to the created element. If aAnonClass is not
// the empty string, it becomes the value of the attribute "_moz_anonclass"
nsresult
HTMLEditor::CreateAnonymousElement(const nsAString& aTag,
nsIDOMNode* aParentNode,
const nsAString& aAnonClass,
bool aIsCreatedHidden,
nsIDOMElement** aReturn)
{
NS_ENSURE_ARG_POINTER(aParentNode);
NS_ENSURE_ARG_POINTER(aReturn);
*aReturn = nullptr;
nsCOMPtr<nsIContent> parentContent( do_QueryInterface(aParentNode) );
NS_ENSURE_TRUE(parentContent, NS_OK);
nsCOMPtr<nsIDocument> doc = GetDocument();
NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
// Get the pres shell
nsCOMPtr<nsIPresShell> ps = GetPresShell();
NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
// Create a new node through the element factory
nsCOMPtr<nsIAtom> tagAtom = NS_Atomize(aTag);
nsCOMPtr<Element> newContent = CreateHTMLContent(tagAtom);
NS_ENSURE_STATE(newContent);
nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newContent);
NS_ENSURE_TRUE(newElement, NS_ERROR_FAILURE);
// add the "hidden" class if needed
if (aIsCreatedHidden) {
nsresult rv = newElement->SetAttribute(NS_LITERAL_STRING("class"),
NS_LITERAL_STRING("hidden"));
NS_ENSURE_SUCCESS(rv, rv);
}
// add an _moz_anonclass attribute if needed
if (!aAnonClass.IsEmpty()) {
nsresult rv = newElement->SetAttribute(NS_LITERAL_STRING("_moz_anonclass"),
aAnonClass);
NS_ENSURE_SUCCESS(rv, rv);
}
{
nsAutoScriptBlocker scriptBlocker;
// establish parenthood of the element
newContent->SetIsNativeAnonymousRoot();
nsresult rv =
newContent->BindToTree(doc, parentContent, parentContent, true);
if (NS_FAILED(rv)) {
newContent->UnbindFromTree();
return rv;
}
}
ElementDeletionObserver* observer =
new ElementDeletionObserver(newContent, parentContent);
NS_ADDREF(observer); // NodeWillBeDestroyed releases.
parentContent->AddMutationObserver(observer);
newContent->AddMutationObserver(observer);
#ifdef DEBUG
// Editor anonymous content gets passed to RecreateFramesFor... which can't
// _really_ deal with anonymous content (because it can't get the frame tree
// ordering right). But for us the ordering doesn't matter so this is sort of
// ok.
newContent->SetProperty(nsGkAtoms::restylableAnonymousNode,
reinterpret_cast<void*>(true));
#endif // DEBUG
// display the element
ps->RecreateFramesFor(newContent);
newElement.forget(aReturn);
return NS_OK;
}
// Removes event listener and calls DeleteRefToAnonymousNode.
void
HTMLEditor::RemoveListenerAndDeleteRef(const nsAString& aEvent,
nsIDOMEventListener* aListener,
bool aUseCapture,
Element* aElement,
nsIContent* aParentContent,
nsIPresShell* aShell)
{
nsCOMPtr<nsIDOMEventTarget> evtTarget(do_QueryInterface(aElement));
if (evtTarget) {
evtTarget->RemoveEventListener(aEvent, aListener, aUseCapture);
}
DeleteRefToAnonymousNode(static_cast<nsIDOMElement*>(GetAsDOMNode(aElement)), aParentContent, aShell);
}
// Deletes all references to an anonymous element
void
HTMLEditor::DeleteRefToAnonymousNode(nsIDOMElement* aElement,
nsIContent* aParentContent,
nsIPresShell* aShell)
{
// call ContentRemoved() for the anonymous content
// node so its references get removed from the frame manager's
// undisplay map, and its layout frames get destroyed!
if (aElement) {
nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
if (content) {
nsAutoScriptBlocker scriptBlocker;
// Need to check whether aShell has been destroyed (but not yet deleted).
// In that case presContext->GetPresShell() returns nullptr.
// See bug 338129.
if (content->IsInComposedDoc() && aShell && aShell->GetPresContext() &&
aShell->GetPresContext()->GetPresShell() == aShell) {
nsCOMPtr<nsIDocumentObserver> docObserver = do_QueryInterface(aShell);
if (docObserver) {
// Call BeginUpdate() so that the nsCSSFrameConstructor/PresShell
// knows we're messing with the frame tree.
nsCOMPtr<nsIDocument> document = GetDocument();
if (document) {
docObserver->BeginUpdate(document, UPDATE_CONTENT_MODEL);
}
// XXX This is wrong (bug 439258). Once it's fixed, the NS_WARNING
// in RestyleManager::RestyleForRemove should be changed back
// to an assertion.
docObserver->ContentRemoved(content->GetComposedDoc(),
aParentContent, content, -1,
content->GetPreviousSibling());
if (document) {
docObserver->EndUpdate(document, UPDATE_CONTENT_MODEL);
}
}
}
content->UnbindFromTree();
}
}
}
// The following method is mostly called by a selection listener. When a
// selection change is notified, the method is called to check if resizing
// handles, a grabber and/or inline table editing UI need to be displayed
// or refreshed
NS_IMETHODIMP
HTMLEditor::CheckSelectionStateForAnonymousButtons(nsISelection* aSelection)
{
NS_ENSURE_ARG_POINTER(aSelection);
// early way out if all contextual UI extensions are disabled
NS_ENSURE_TRUE(mIsObjectResizingEnabled ||
mIsAbsolutelyPositioningEnabled ||
mIsInlineTableEditingEnabled, NS_OK);
// Don't change selection state if we're moving.
if (mIsMoving) {
return NS_OK;
}
nsCOMPtr<nsIDOMElement> focusElement;
// let's get the containing element of the selection
nsresult rv = GetSelectionContainer(getter_AddRefs(focusElement));
NS_ENSURE_TRUE(focusElement, NS_OK);
NS_ENSURE_SUCCESS(rv, rv);
// If we're not in a document, don't try to add resizers
nsCOMPtr<dom::Element> focusElementNode = do_QueryInterface(focusElement);
NS_ENSURE_STATE(focusElementNode);
if (!focusElementNode->IsInUncomposedDoc()) {
return NS_OK;
}
// what's its tag?
nsAutoString focusTagName;
rv = focusElement->GetTagName(focusTagName);
NS_ENSURE_SUCCESS(rv, rv);
ToLowerCase(focusTagName);
nsCOMPtr<nsIAtom> focusTagAtom = NS_Atomize(focusTagName);
nsCOMPtr<nsIDOMElement> absPosElement;
if (mIsAbsolutelyPositioningEnabled) {
// Absolute Positioning support is enabled, is the selection contained
// in an absolutely positioned element ?
rv =
GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(absPosElement));
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIDOMElement> cellElement;
if (mIsObjectResizingEnabled || mIsInlineTableEditingEnabled) {
// Resizing or Inline Table Editing is enabled, we need to check if the
// selection is contained in a table cell
rv = GetElementOrParentByTagName(NS_LITERAL_STRING("td"),
nullptr,
getter_AddRefs(cellElement));
NS_ENSURE_SUCCESS(rv, rv);
}
if (mIsObjectResizingEnabled && cellElement) {
// we are here because Resizing is enabled AND selection is contained in
// a cell
// get the enclosing table
if (nsGkAtoms::img != focusTagAtom) {
// the element container of the selection is not an image, so we'll show
// the resizers around the table
nsCOMPtr<nsIDOMNode> tableNode = GetEnclosingTable(cellElement);
focusElement = do_QueryInterface(tableNode);
focusTagAtom = nsGkAtoms::table;
}
}
// we allow resizers only around images, tables, and absolutely positioned
// elements. If we don't have image/table, let's look at the latter case.
if (nsGkAtoms::img != focusTagAtom && nsGkAtoms::table != focusTagAtom) {
focusElement = absPosElement;
}
// at this point, focusElement contains the element for Resizing,
// cellElement contains the element for InlineTableEditing
// absPosElement contains the element for Positioning
// Note: All the Hide/Show methods below may change attributes on real
// content which means a DOMAttrModified handler may cause arbitrary
// side effects while this code runs (bug 420439).
if (mIsAbsolutelyPositioningEnabled && mAbsolutelyPositionedObject &&
absPosElement != GetAsDOMNode(mAbsolutelyPositionedObject)) {
rv = HideGrabber();
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(!mAbsolutelyPositionedObject, "HideGrabber failed");
}
if (mIsObjectResizingEnabled && mResizedObject &&
GetAsDOMNode(mResizedObject) != focusElement) {
rv = HideResizers();
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(!mResizedObject, "HideResizers failed");
}
if (mIsInlineTableEditingEnabled && mInlineEditedCell &&
mInlineEditedCell != cellElement) {
rv = HideInlineTableEditingUI();
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(!mInlineEditedCell, "HideInlineTableEditingUI failed");
}
// now, let's display all contextual UI for good
nsIContent* hostContent = GetActiveEditingHost();
nsCOMPtr<nsIDOMNode> hostNode = do_QueryInterface(hostContent);
if (mIsObjectResizingEnabled && focusElement &&
IsModifiableNode(focusElement) && focusElement != hostNode) {
if (nsGkAtoms::img == focusTagAtom) {
mResizedObjectIsAnImage = true;
}
if (mResizedObject) {
nsresult rv = RefreshResizers();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
} else {
nsresult rv = ShowResizers(focusElement);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
if (mIsAbsolutelyPositioningEnabled && absPosElement &&
IsModifiableNode(absPosElement) && absPosElement != hostNode) {
if (mAbsolutelyPositionedObject) {
nsresult rv = RefreshGrabber();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
} else {
nsresult rv = ShowGrabberOnElement(absPosElement);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
if (mIsInlineTableEditingEnabled && cellElement &&
IsModifiableNode(cellElement) && cellElement != hostNode) {
if (mInlineEditedCell) {
nsresult rv = RefreshInlineTableEditingUI();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
} else {
nsresult rv = ShowInlineTableEditingUI(cellElement);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
return NS_OK;
}
// Resizing and Absolute Positioning need to know everything about the
// containing box of the element: position, size, margins, borders
nsresult
HTMLEditor::GetPositionAndDimensions(nsIDOMElement* aElement,
int32_t& aX,
int32_t& aY,
int32_t& aW,
int32_t& aH,
int32_t& aBorderLeft,
int32_t& aBorderTop,
int32_t& aMarginLeft,
int32_t& aMarginTop)
{
nsCOMPtr<Element> element = do_QueryInterface(aElement);
NS_ENSURE_ARG_POINTER(element);
// Is the element positioned ? let's check the cheap way first...
bool isPositioned = false;
nsresult rv =
aElement->HasAttribute(NS_LITERAL_STRING("_moz_abspos"), &isPositioned);
NS_ENSURE_SUCCESS(rv, rv);
if (!isPositioned) {
// hmmm... the expensive way now...
nsAutoString positionStr;
mCSSEditUtils->GetComputedProperty(*element, *nsGkAtoms::position,
positionStr);
isPositioned = positionStr.EqualsLiteral("absolute");
}
if (isPositioned) {
// Yes, it is absolutely positioned
mResizedObjectIsAbsolutelyPositioned = true;
// Get the all the computed css styles attached to the element node
RefPtr<nsComputedDOMStyle> cssDecl =
mCSSEditUtils->GetComputedStyle(element);
NS_ENSURE_STATE(cssDecl);
aBorderLeft = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("border-left-width"));
aBorderTop = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("border-top-width"));
aMarginLeft = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("margin-left"));
aMarginTop = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("margin-top"));
aX = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("left")) +
aMarginLeft + aBorderLeft;
aY = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("top")) +
aMarginTop + aBorderTop;
aW = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("width"));
aH = GetCSSFloatValue(cssDecl, NS_LITERAL_STRING("height"));
} else {
mResizedObjectIsAbsolutelyPositioned = false;
nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aElement);
if (!htmlElement) {
return NS_ERROR_NULL_POINTER;
}
GetElementOrigin(aElement, aX, aY);
if (NS_WARN_IF(NS_FAILED(htmlElement->GetOffsetWidth(&aW))) ||
NS_WARN_IF(NS_FAILED(htmlElement->GetOffsetHeight(&aH)))) {
return rv;
}
aBorderLeft = 0;
aBorderTop = 0;
aMarginLeft = 0;
aMarginTop = 0;
}
return NS_OK;
}
// self-explanatory
void
HTMLEditor::SetAnonymousElementPosition(int32_t aX,
int32_t aY,
nsIDOMElement* aElement)
{
mCSSEditUtils->SetCSSPropertyPixels(aElement, NS_LITERAL_STRING("left"), aX);
mCSSEditUtils->SetCSSPropertyPixels(aElement, NS_LITERAL_STRING("top"), aY);
}
} // namespace mozilla

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,377 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef HTMLEditRules_h
#define HTMLEditRules_h
#include "TypeInState.h"
#include "mozilla/SelectionState.h"
#include "mozilla/TextEditRules.h"
#include "nsCOMPtr.h"
#include "nsIEditActionListener.h"
#include "nsIEditor.h"
#include "nsIHTMLEditor.h"
#include "nsISupportsImpl.h"
#include "nsTArray.h"
#include "nscore.h"
class nsIAtom;
class nsIDOMCharacterData;
class nsIDOMDocument;
class nsIDOMElement;
class nsIDOMNode;
class nsIEditor;
class nsINode;
class nsRange;
namespace mozilla {
class HTMLEditor;
class RulesInfo;
class TextEditor;
struct EditorDOMPoint;
namespace dom {
class Element;
class Selection;
} // namespace dom
struct StyleCache final : public PropItem
{
bool mPresent;
StyleCache()
: PropItem()
, mPresent(false)
{
MOZ_COUNT_CTOR(StyleCache);
}
StyleCache(nsIAtom* aTag,
const nsAString& aAttr,
const nsAString& aValue)
: PropItem(aTag, aAttr, aValue)
, mPresent(false)
{
MOZ_COUNT_CTOR(StyleCache);
}
~StyleCache()
{
MOZ_COUNT_DTOR(StyleCache);
}
};
#define SIZE_STYLE_TABLE 19
class HTMLEditRules : public TextEditRules
, public nsIEditActionListener
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLEditRules, TextEditRules)
HTMLEditRules();
// nsIEditRules methods
NS_IMETHOD Init(TextEditor* aTextEditor) override;
NS_IMETHOD DetachEditor() override;
NS_IMETHOD BeforeEdit(EditAction action,
nsIEditor::EDirection aDirection) override;
NS_IMETHOD AfterEdit(EditAction action,
nsIEditor::EDirection aDirection) override;
NS_IMETHOD WillDoAction(Selection* aSelection, RulesInfo* aInfo,
bool* aCancel, bool* aHandled) override;
NS_IMETHOD DidDoAction(Selection* aSelection, RulesInfo* aInfo,
nsresult aResult) override;
NS_IMETHOD DocumentModified() override;
nsresult GetListState(bool* aMixed, bool* aOL, bool* aUL, bool* aDL);
nsresult GetListItemState(bool* aMixed, bool* aLI, bool* aDT, bool* aDD);
nsresult GetIndentState(bool* aCanIndent, bool* aCanOutdent);
nsresult GetAlignment(bool* aMixed, nsIHTMLEditor::EAlignment* aAlign);
nsresult GetParagraphState(bool* aMixed, nsAString& outFormat);
nsresult MakeSureElemStartsOrEndsOnCR(nsIDOMNode* aNode);
// nsIEditActionListener methods
NS_IMETHOD WillCreateNode(const nsAString& aTag, nsIDOMNode* aParent,
int32_t aPosition) override;
NS_IMETHOD DidCreateNode(const nsAString& aTag, nsIDOMNode* aNode,
nsIDOMNode* aParent, int32_t aPosition,
nsresult aResult) override;
NS_IMETHOD WillInsertNode(nsIDOMNode* aNode, nsIDOMNode* aParent,
int32_t aPosition) override;
NS_IMETHOD DidInsertNode(nsIDOMNode* aNode, nsIDOMNode* aParent,
int32_t aPosition, nsresult aResult) override;
NS_IMETHOD WillDeleteNode(nsIDOMNode* aChild) override;
NS_IMETHOD DidDeleteNode(nsIDOMNode* aChild, nsresult aResult) override;
NS_IMETHOD WillSplitNode(nsIDOMNode* aExistingRightNode,
int32_t aOffset) override;
NS_IMETHOD DidSplitNode(nsIDOMNode* aExistingRightNode, int32_t aOffset,
nsIDOMNode* aNewLeftNode, nsresult aResult) override;
NS_IMETHOD WillJoinNodes(nsIDOMNode* aLeftNode, nsIDOMNode* aRightNode,
nsIDOMNode* aParent) override;
NS_IMETHOD DidJoinNodes(nsIDOMNode* aLeftNode, nsIDOMNode* aRightNode,
nsIDOMNode* aParent, nsresult aResult) override;
NS_IMETHOD WillInsertText(nsIDOMCharacterData* aTextNode, int32_t aOffset,
const nsAString &aString) override;
NS_IMETHOD DidInsertText(nsIDOMCharacterData* aTextNode, int32_t aOffset,
const nsAString &aString, nsresult aResult) override;
NS_IMETHOD WillDeleteText(nsIDOMCharacterData* aTextNode, int32_t aOffset,
int32_t aLength) override;
NS_IMETHOD DidDeleteText(nsIDOMCharacterData* aTextNode, int32_t aOffset,
int32_t aLength, nsresult aResult) override;
NS_IMETHOD WillDeleteSelection(nsISelection* aSelection) override;
NS_IMETHOD DidDeleteSelection(nsISelection* aSelection) override;
void DeleteNodeIfCollapsedText(nsINode& aNode);
protected:
virtual ~HTMLEditRules();
enum RulesEndpoint
{
kStart,
kEnd
};
void InitFields();
void WillInsert(Selection& aSelection, bool* aCancel);
nsresult WillInsertText(EditAction aAction,
Selection* aSelection,
bool* aCancel,
bool* aHandled,
const nsAString* inString,
nsAString* outString,
int32_t aMaxLength);
nsresult WillLoadHTML(Selection* aSelection, bool* aCancel);
nsresult WillInsertBreak(Selection& aSelection, bool* aCancel,
bool* aHandled);
nsresult StandardBreakImpl(nsINode& aNode, int32_t aOffset,
Selection& aSelection);
nsresult DidInsertBreak(Selection* aSelection, nsresult aResult);
nsresult SplitMailCites(Selection* aSelection, bool* aHandled);
nsresult WillDeleteSelection(Selection* aSelection,
nsIEditor::EDirection aAction,
nsIEditor::EStripWrappers aStripWrappers,
bool* aCancel, bool* aHandled);
nsresult DidDeleteSelection(Selection* aSelection,
nsIEditor::EDirection aDir,
nsresult aResult);
nsresult InsertBRIfNeeded(Selection* aSelection);
mozilla::EditorDOMPoint GetGoodSelPointForNode(nsINode& aNode,
nsIEditor::EDirection aAction);
nsresult JoinBlocks(nsIContent& aLeftNode, nsIContent& aRightNode,
bool* aCanceled);
nsresult MoveBlock(Element& aLeftBlock, Element& aRightBlock,
int32_t aLeftOffset, int32_t aRightOffset);
nsresult MoveNodeSmart(nsIContent& aNode, Element& aDestElement,
int32_t* aOffset);
nsresult MoveContents(Element& aElement, Element& aDestElement,
int32_t* aOffset);
nsresult DeleteNonTableElements(nsINode* aNode);
nsresult WillMakeList(Selection* aSelection,
const nsAString* aListType,
bool aEntireList,
const nsAString* aBulletType,
bool* aCancel, bool* aHandled,
const nsAString* aItemType = nullptr);
nsresult WillRemoveList(Selection* aSelection, bool aOrdered, bool* aCancel,
bool* aHandled);
nsresult WillIndent(Selection* aSelection, bool* aCancel, bool* aHandled);
nsresult WillCSSIndent(Selection* aSelection, bool* aCancel, bool* aHandled);
nsresult WillHTMLIndent(Selection* aSelection, bool* aCancel,
bool* aHandled);
nsresult WillOutdent(Selection& aSelection, bool* aCancel, bool* aHandled);
nsresult WillAlign(Selection& aSelection, const nsAString& aAlignType,
bool* aCancel, bool* aHandled);
nsresult WillAbsolutePosition(Selection& aSelection, bool* aCancel,
bool* aHandled);
nsresult WillRemoveAbsolutePosition(Selection* aSelection, bool* aCancel,
bool* aHandled);
nsresult WillRelativeChangeZIndex(Selection* aSelection, int32_t aChange,
bool* aCancel, bool* aHandled);
nsresult WillMakeDefListItem(Selection* aSelection,
const nsAString* aBlockType, bool aEntireList,
bool* aCancel, bool* aHandled);
nsresult WillMakeBasicBlock(Selection& aSelection,
const nsAString& aBlockType,
bool* aCancel, bool* aHandled);
nsresult DidMakeBasicBlock(Selection* aSelection, RulesInfo* aInfo,
nsresult aResult);
nsresult DidAbsolutePosition();
nsresult AlignInnerBlocks(nsINode& aNode, const nsAString* alignType);
nsresult AlignBlockContents(nsIDOMNode* aNode, const nsAString* alignType);
nsresult AppendInnerFormatNodes(nsTArray<OwningNonNull<nsINode>>& aArray,
nsINode* aNode);
nsresult GetFormatString(nsIDOMNode* aNode, nsAString &outFormat);
enum class Lists { no, yes };
enum class Tables { no, yes };
void GetInnerContent(nsINode& aNode,
nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
int32_t* aIndex, Lists aLists = Lists::yes,
Tables aTables = Tables::yes);
Element* IsInListItem(nsINode* aNode);
nsresult ReturnInHeader(Selection& aSelection, Element& aHeader,
nsINode& aNode, int32_t aOffset);
nsresult ReturnInParagraph(Selection* aSelection, nsIDOMNode* aHeader,
nsIDOMNode* aTextNode, int32_t aOffset,
bool* aCancel, bool* aHandled);
nsresult SplitParagraph(nsIDOMNode* aPara,
nsIContent* aBRNode,
Selection* aSelection,
nsCOMPtr<nsIDOMNode>* aSelNode,
int32_t* aOffset);
nsresult ReturnInListItem(Selection& aSelection, Element& aHeader,
nsINode& aNode, int32_t aOffset);
nsresult AfterEditInner(EditAction action,
nsIEditor::EDirection aDirection);
nsresult RemovePartOfBlock(Element& aBlock, nsIContent& aStartChild,
nsIContent& aEndChild);
void SplitBlock(Element& aBlock,
nsIContent& aStartChild,
nsIContent& aEndChild,
nsIContent** aOutLeftNode = nullptr,
nsIContent** aOutRightNode = nullptr,
nsIContent** aOutMiddleNode = nullptr);
nsresult OutdentPartOfBlock(Element& aBlock,
nsIContent& aStartChild,
nsIContent& aEndChild,
bool aIsBlockIndentedWithCSS,
nsIContent** aOutLeftNode,
nsIContent** aOutRightNode);
nsresult ConvertListType(Element* aList, Element** aOutList,
nsIAtom* aListType, nsIAtom* aItemType);
nsresult CreateStyleForInsertText(Selection& aSelection, nsIDocument& aDoc);
enum class MozBRCounts { yes, no };
nsresult IsEmptyBlock(Element& aNode, bool* aOutIsEmptyBlock,
MozBRCounts aMozBRCounts = MozBRCounts::yes);
nsresult CheckForEmptyBlock(nsINode* aStartNode, Element* aBodyNode,
Selection* aSelection,
nsIEditor::EDirection aAction, bool* aHandled);
enum class BRLocation { beforeBlock, blockEnd };
Element* CheckForInvisibleBR(Element& aBlock, BRLocation aWhere,
int32_t aOffset = 0);
nsresult ExpandSelectionForDeletion(Selection& aSelection);
bool IsFirstNode(nsIDOMNode* aNode);
bool IsLastNode(nsIDOMNode* aNode);
nsresult NormalizeSelection(Selection* aSelection);
void GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode* aNode,
int32_t aOffset, EditAction actionID,
nsCOMPtr<nsIDOMNode>* outNode, int32_t* outOffset);
void GetPromotedRanges(Selection& aSelection,
nsTArray<RefPtr<nsRange>>& outArrayOfRanges,
EditAction inOperationType);
void PromoteRange(nsRange& aRange, EditAction inOperationType);
enum class TouchContent { no, yes };
nsresult GetNodesForOperation(
nsTArray<RefPtr<nsRange>>& aArrayOfRanges,
nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
EditAction aOperationType,
TouchContent aTouchContent = TouchContent::yes);
void GetChildNodesForOperation(
nsINode& aNode,
nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes);
nsresult GetNodesFromPoint(EditorDOMPoint aPoint,
EditAction aOperation,
nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
TouchContent aTouchContent);
nsresult GetNodesFromSelection(
Selection& aSelection,
EditAction aOperation,
nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
TouchContent aTouchContent = TouchContent::yes);
enum class EntireList { no, yes };
nsresult GetListActionNodes(
nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
EntireList aEntireList,
TouchContent aTouchContent = TouchContent::yes);
void GetDefinitionListItemTypes(Element* aElement, bool* aDT, bool* aDD);
nsresult GetParagraphFormatNodes(
nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
TouchContent aTouchContent = TouchContent::yes);
void LookInsideDivBQandList(nsTArray<OwningNonNull<nsINode>>& aNodeArray);
nsresult BustUpInlinesAtRangeEndpoints(RangeItem& inRange);
nsresult BustUpInlinesAtBRs(
nsIContent& aNode,
nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes);
nsIContent* GetHighestInlineParent(nsINode& aNode);
void MakeTransitionList(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
nsTArray<bool>& aTransitionArray);
nsresult RemoveBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray);
nsresult ApplyBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
nsIAtom& aBlockTag);
nsresult MakeBlockquote(nsTArray<OwningNonNull<nsINode>>& aNodeArray);
nsresult SplitAsNeeded(nsIAtom& aTag, OwningNonNull<nsINode>& inOutParent,
int32_t& inOutOffset);
nsresult SplitAsNeeded(nsIAtom& aTag, nsCOMPtr<nsINode>& inOutParent,
int32_t& inOutOffset);
nsresult AddTerminatingBR(nsIDOMNode *aBlock);
EditorDOMPoint JoinNodesSmart(nsIContent& aNodeLeft,
nsIContent& aNodeRight);
Element* GetTopEnclosingMailCite(nsINode& aNode);
nsresult PopListItem(nsIDOMNode* aListItem, bool* aOutOfList);
nsresult RemoveListStructure(Element& aList);
nsresult CacheInlineStyles(nsIDOMNode* aNode);
nsresult ReapplyCachedStyles();
void ClearCachedStyles();
void AdjustSpecialBreaks();
nsresult AdjustWhitespace(Selection* aSelection);
nsresult PinSelectionToNewBlock(Selection* aSelection);
void CheckInterlinePosition(Selection& aSelection);
nsresult AdjustSelection(Selection* aSelection,
nsIEditor::EDirection aAction);
nsresult FindNearSelectableNode(nsIDOMNode* aSelNode,
int32_t aSelOffset,
nsIEditor::EDirection& aDirection,
nsCOMPtr<nsIDOMNode>* outSelectableNode);
/**
* Returns true if aNode1 or aNode2 or both is the descendant of some type of
* table element, but their nearest table element ancestors differ. "Table
* element" here includes not just <table> but also <td>, <tbody>, <tr>, etc.
* The nodes count as being their own descendants for this purpose, so a
* table element is its own nearest table element ancestor.
*/
bool InDifferentTableElements(nsIDOMNode* aNode1, nsIDOMNode* aNode2);
bool InDifferentTableElements(nsINode* aNode1, nsINode* aNode2);
nsresult RemoveEmptyNodes();
nsresult SelectionEndpointInNode(nsINode* aNode, bool* aResult);
nsresult UpdateDocChangeRange(nsRange* aRange);
nsresult ConfirmSelectionInBody();
nsresult InsertMozBRIfNeeded(nsINode& aNode);
bool IsEmptyInline(nsINode& aNode);
bool ListIsEmptyLine(nsTArray<OwningNonNull<nsINode>>& arrayOfNodes);
nsresult RemoveAlignment(nsIDOMNode* aNode, const nsAString& aAlignType,
bool aChildrenOnly);
nsresult MakeSureElemStartsOrEndsOnCR(nsIDOMNode* aNode, bool aStarts);
enum class ContentsOnly { no, yes };
nsresult AlignBlock(Element& aElement,
const nsAString& aAlignType, ContentsOnly aContentsOnly);
enum class Change { minus, plus };
nsresult ChangeIndentation(Element& aElement, Change aChange);
void DocumentModifiedWorker();
protected:
HTMLEditor* mHTMLEditor;
RefPtr<nsRange> mDocChangeRange;
bool mListenerEnabled;
bool mReturnInEmptyLIKillsList;
bool mDidDeleteSelection;
bool mDidRangedDelete;
bool mRestoreContentEditableCount;
RefPtr<nsRange> mUtilRange;
// Need to remember an int across willJoin/didJoin...
uint32_t mJoinOffset;
nsCOMPtr<Element> mNewBlock;
RefPtr<RangeItem> mRangeItem;
StyleCache mCachedStyles[SIZE_STYLE_TABLE];
};
} // namespace mozilla
#endif // #ifndef HTMLEditRules_h

View file

@ -0,0 +1,842 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "HTMLEditUtils.h"
#include "TextEditUtils.h" // for TextEditUtils
#include "mozilla/ArrayUtils.h" // for ArrayLength
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
#include "mozilla/EditorBase.h" // for EditorBase
#include "mozilla/dom/Element.h" // for Element, nsINode
#include "nsAString.h" // for nsAString_internal::IsEmpty
#include "nsCOMPtr.h" // for nsCOMPtr, operator==, etc.
#include "nsCaseTreatment.h"
#include "nsDebug.h" // for NS_PRECONDITION, etc.
#include "nsError.h" // for NS_SUCCEEDED
#include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::a, etc.
#include "nsHTMLTags.h"
#include "nsIAtom.h" // for nsIAtom
#include "nsIDOMHTMLAnchorElement.h" // for nsIDOMHTMLAnchorElement
#include "nsIDOMNode.h" // for nsIDOMNode
#include "nsNameSpaceManager.h" // for kNameSpaceID_None
#include "nsLiteralString.h" // for NS_LITERAL_STRING
#include "nsString.h" // for nsAutoString
namespace mozilla {
/**
* IsInlineStyle() returns true if aNode is an inline style.
*/
bool
HTMLEditUtils::IsInlineStyle(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsInlineStyle(node);
}
bool
HTMLEditUtils::IsInlineStyle(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsAnyOfHTMLElements(nsGkAtoms::b,
nsGkAtoms::i,
nsGkAtoms::u,
nsGkAtoms::tt,
nsGkAtoms::s,
nsGkAtoms::strike,
nsGkAtoms::big,
nsGkAtoms::small,
nsGkAtoms::sub,
nsGkAtoms::sup,
nsGkAtoms::font);
}
/**
* IsFormatNode() returns true if aNode is a format node.
*/
bool
HTMLEditUtils::IsFormatNode(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsFormatNode(node);
}
bool
HTMLEditUtils::IsFormatNode(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsAnyOfHTMLElements(nsGkAtoms::p,
nsGkAtoms::pre,
nsGkAtoms::h1,
nsGkAtoms::h2,
nsGkAtoms::h3,
nsGkAtoms::h4,
nsGkAtoms::h5,
nsGkAtoms::h6,
nsGkAtoms::address);
}
/**
* IsNodeThatCanOutdent() returns true if aNode is a list, list item or
* blockquote.
*/
bool
HTMLEditUtils::IsNodeThatCanOutdent(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsIAtom> nodeAtom = EditorBase::GetTag(aNode);
return (nodeAtom == nsGkAtoms::ul)
|| (nodeAtom == nsGkAtoms::ol)
|| (nodeAtom == nsGkAtoms::dl)
|| (nodeAtom == nsGkAtoms::li)
|| (nodeAtom == nsGkAtoms::dd)
|| (nodeAtom == nsGkAtoms::dt)
|| (nodeAtom == nsGkAtoms::blockquote);
}
/**
* IsHeader() returns true if aNode is an html header.
*/
bool
HTMLEditUtils::IsHeader(nsINode& aNode)
{
return aNode.IsAnyOfHTMLElements(nsGkAtoms::h1,
nsGkAtoms::h2,
nsGkAtoms::h3,
nsGkAtoms::h4,
nsGkAtoms::h5,
nsGkAtoms::h6);
}
bool
HTMLEditUtils::IsHeader(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
MOZ_ASSERT(node);
return IsHeader(*node);
}
/**
* IsParagraph() returns true if aNode is an html paragraph.
*/
bool
HTMLEditUtils::IsParagraph(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::p);
}
/**
* IsHR() returns true if aNode is an horizontal rule.
*/
bool
HTMLEditUtils::IsHR(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::hr);
}
/**
* IsListItem() returns true if aNode is an html list item.
*/
bool
HTMLEditUtils::IsListItem(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsListItem(node);
}
bool
HTMLEditUtils::IsListItem(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsAnyOfHTMLElements(nsGkAtoms::li,
nsGkAtoms::dd,
nsGkAtoms::dt);
}
/**
* IsTableElement() returns true if aNode is an html table, td, tr, ...
*/
bool
HTMLEditUtils::IsTableElement(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsTableElement(node);
}
bool
HTMLEditUtils::IsTableElement(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsAnyOfHTMLElements(nsGkAtoms::table,
nsGkAtoms::tr,
nsGkAtoms::td,
nsGkAtoms::th,
nsGkAtoms::thead,
nsGkAtoms::tfoot,
nsGkAtoms::tbody,
nsGkAtoms::caption);
}
/**
* IsTableElementButNotTable() returns true if aNode is an html td, tr, ...
* (doesn't include table)
*/
bool
HTMLEditUtils::IsTableElementButNotTable(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsTableElementButNotTable(node);
}
bool
HTMLEditUtils::IsTableElementButNotTable(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsAnyOfHTMLElements(nsGkAtoms::tr,
nsGkAtoms::td,
nsGkAtoms::th,
nsGkAtoms::thead,
nsGkAtoms::tfoot,
nsGkAtoms::tbody,
nsGkAtoms::caption);
}
/**
* IsTable() returns true if aNode is an html table.
*/
bool
HTMLEditUtils::IsTable(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::table);
}
bool
HTMLEditUtils::IsTable(nsINode* aNode)
{
return aNode && aNode->IsHTMLElement(nsGkAtoms::table);
}
/**
* IsTableRow() returns true if aNode is an html tr.
*/
bool
HTMLEditUtils::IsTableRow(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::tr);
}
/**
* IsTableCell() returns true if aNode is an html td or th.
*/
bool
HTMLEditUtils::IsTableCell(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsTableCell(node);
}
bool
HTMLEditUtils::IsTableCell(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th);
}
/**
* IsTableCellOrCaption() returns true if aNode is an html td or th or caption.
*/
bool
HTMLEditUtils::IsTableCellOrCaption(nsINode& aNode)
{
return aNode.IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th,
nsGkAtoms::caption);
}
/**
* IsList() returns true if aNode is an html list.
*/
bool
HTMLEditUtils::IsList(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsList(node);
}
bool
HTMLEditUtils::IsList(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsAnyOfHTMLElements(nsGkAtoms::ul,
nsGkAtoms::ol,
nsGkAtoms::dl);
}
/**
* IsOrderedList() returns true if aNode is an html ordered list.
*/
bool
HTMLEditUtils::IsOrderedList(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::ol);
}
/**
* IsUnorderedList() returns true if aNode is an html unordered list.
*/
bool
HTMLEditUtils::IsUnorderedList(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::ul);
}
/**
* IsBlockquote() returns true if aNode is an html blockquote node.
*/
bool
HTMLEditUtils::IsBlockquote(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::blockquote);
}
/**
* IsPre() returns true if aNode is an html pre node.
*/
bool
HTMLEditUtils::IsPre(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::pre);
}
/**
* IsImage() returns true if aNode is an html image node.
*/
bool
HTMLEditUtils::IsImage(nsINode* aNode)
{
return aNode && aNode->IsHTMLElement(nsGkAtoms::img);
}
bool
HTMLEditUtils::IsImage(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::img);
}
bool
HTMLEditUtils::IsLink(nsIDOMNode *aNode)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsLink(node);
}
bool
HTMLEditUtils::IsLink(nsINode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = do_QueryInterface(aNode);
if (anchor) {
nsAutoString tmpText;
if (NS_SUCCEEDED(anchor->GetHref(tmpText)) && !tmpText.IsEmpty()) {
return true;
}
}
return false;
}
bool
HTMLEditUtils::IsNamedAnchor(nsIDOMNode *aNode)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsNamedAnchor(node);
}
bool
HTMLEditUtils::IsNamedAnchor(nsINode* aNode)
{
MOZ_ASSERT(aNode);
if (!aNode->IsHTMLElement(nsGkAtoms::a)) {
return false;
}
nsAutoString text;
return aNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name,
text) && !text.IsEmpty();
}
/**
* IsDiv() returns true if aNode is an html div node.
*/
bool
HTMLEditUtils::IsDiv(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::div);
}
/**
* IsMozDiv() returns true if aNode is an html div node with |type = _moz|.
*/
bool
HTMLEditUtils::IsMozDiv(nsIDOMNode* aNode)
{
return IsDiv(aNode) && TextEditUtils::HasMozAttr(aNode);
}
bool
HTMLEditUtils::IsMozDiv(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsHTMLElement(nsGkAtoms::div) &&
TextEditUtils::HasMozAttr(GetAsDOMNode(aNode));
}
/**
* IsMailCite() returns true if aNode is an html blockquote with |type=cite|.
*/
bool
HTMLEditUtils::IsMailCite(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsMailCite(node);
}
bool
HTMLEditUtils::IsMailCite(nsINode* aNode)
{
MOZ_ASSERT(aNode);
// don't ask me why, but our html mailcites are id'd by "type=cite"...
if (aNode->IsElement() &&
aNode->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
NS_LITERAL_STRING("cite"),
eIgnoreCase)) {
return true;
}
// ... but our plaintext mailcites by "_moz_quote=true". go figure.
if (aNode->IsElement() &&
aNode->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozquote,
NS_LITERAL_STRING("true"),
eIgnoreCase)) {
return true;
}
return false;
}
/**
* IsFormWidget() returns true if aNode is a form widget of some kind.
*/
bool
HTMLEditUtils::IsFormWidget(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
return node && IsFormWidget(node);
}
bool
HTMLEditUtils::IsFormWidget(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsAnyOfHTMLElements(nsGkAtoms::textarea,
nsGkAtoms::select,
nsGkAtoms::button,
nsGkAtoms::output,
nsGkAtoms::keygen,
nsGkAtoms::progress,
nsGkAtoms::meter,
nsGkAtoms::input);
}
bool
HTMLEditUtils::SupportsAlignAttr(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsIAtom> nodeAtom = EditorBase::GetTag(aNode);
return (nodeAtom == nsGkAtoms::hr)
|| (nodeAtom == nsGkAtoms::table)
|| (nodeAtom == nsGkAtoms::tbody)
|| (nodeAtom == nsGkAtoms::tfoot)
|| (nodeAtom == nsGkAtoms::thead)
|| (nodeAtom == nsGkAtoms::tr)
|| (nodeAtom == nsGkAtoms::td)
|| (nodeAtom == nsGkAtoms::th)
|| (nodeAtom == nsGkAtoms::div)
|| (nodeAtom == nsGkAtoms::p)
|| (nodeAtom == nsGkAtoms::h1)
|| (nodeAtom == nsGkAtoms::h2)
|| (nodeAtom == nsGkAtoms::h3)
|| (nodeAtom == nsGkAtoms::h4)
|| (nodeAtom == nsGkAtoms::h5)
|| (nodeAtom == nsGkAtoms::h6);
}
// We use bitmasks to test containment of elements. Elements are marked to be
// in certain groups by setting the mGroup member of the nsElementInfo struct
// to the corresponding GROUP_ values (OR'ed together). Similarly, elements are
// marked to allow containment of certain groups by setting the
// mCanContainGroups member of the nsElementInfo struct to the corresponding
// GROUP_ values (OR'ed together).
// Testing containment then simply consists of checking whether the
// mCanContainGroups bitmask of an element and the mGroup bitmask of a
// potential child overlap.
#define GROUP_NONE 0
// body, head, html
#define GROUP_TOPLEVEL (1 << 1)
// base, link, meta, script, style, title
#define GROUP_HEAD_CONTENT (1 << 2)
// b, big, i, s, small, strike, tt, u
#define GROUP_FONTSTYLE (1 << 3)
// abbr, acronym, cite, code, datalist, del, dfn, em, ins, kbd, mark, rb, rp
// rt, rtc, ruby, samp, strong, var
#define GROUP_PHRASE (1 << 4)
// a, applet, basefont, bdo, br, font, iframe, img, map, meter, object, output,
// picture, progress, q, script, span, sub, sup
#define GROUP_SPECIAL (1 << 5)
// button, form, input, label, select, textarea
#define GROUP_FORMCONTROL (1 << 6)
// address, applet, article, aside, blockquote, button, center, del, details,
// dir, div, dl, fieldset, figure, footer, form, h1, h2, h3, h4, h5, h6, header,
// hgroup, hr, iframe, ins, main, map, menu, nav, noframes, noscript, object,
// ol, p, pre, table, section, summary, ul
#define GROUP_BLOCK (1 << 7)
// frame, frameset
#define GROUP_FRAME (1 << 8)
// col, tbody
#define GROUP_TABLE_CONTENT (1 << 9)
// tr
#define GROUP_TBODY_CONTENT (1 << 10)
// td, th
#define GROUP_TR_CONTENT (1 << 11)
// col
#define GROUP_COLGROUP_CONTENT (1 << 12)
// param
#define GROUP_OBJECT_CONTENT (1 << 13)
// li
#define GROUP_LI (1 << 14)
// area
#define GROUP_MAP_CONTENT (1 << 15)
// optgroup, option
#define GROUP_SELECT_CONTENT (1 << 16)
// option
#define GROUP_OPTIONS (1 << 17)
// dd, dt
#define GROUP_DL_CONTENT (1 << 18)
// p
#define GROUP_P (1 << 19)
// text, whitespace, newline, comment
#define GROUP_LEAF (1 << 20)
// XXX This is because the editor does sublists illegally.
// ol, ul
#define GROUP_OL_UL (1 << 21)
// h1, h2, h3, h4, h5, h6
#define GROUP_HEADING (1 << 22)
// figcaption
#define GROUP_FIGCAPTION (1 << 23)
// picture members (img, source)
#define GROUP_PICTURE_CONTENT (1 << 24)
#define GROUP_INLINE_ELEMENT \
(GROUP_FONTSTYLE | GROUP_PHRASE | GROUP_SPECIAL | GROUP_FORMCONTROL | \
GROUP_LEAF)
#define GROUP_FLOW_ELEMENT (GROUP_INLINE_ELEMENT | GROUP_BLOCK)
struct ElementInfo final
{
#ifdef DEBUG
eHTMLTags mTag;
#endif
uint32_t mGroup;
uint32_t mCanContainGroups;
bool mIsContainer;
bool mCanContainSelf;
};
#ifdef DEBUG
#define ELEM(_tag, _isContainer, _canContainSelf, _group, _canContainGroups) \
{ eHTMLTag_##_tag, _group, _canContainGroups, _isContainer, _canContainSelf }
#else
#define ELEM(_tag, _isContainer, _canContainSelf, _group, _canContainGroups) \
{ _group, _canContainGroups, _isContainer, _canContainSelf }
#endif
static const ElementInfo kElements[eHTMLTag_userdefined] = {
ELEM(a, true, false, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
ELEM(abbr, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(acronym, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(address, true, true, GROUP_BLOCK,
GROUP_INLINE_ELEMENT | GROUP_P),
ELEM(applet, true, true, GROUP_SPECIAL | GROUP_BLOCK,
GROUP_FLOW_ELEMENT | GROUP_OBJECT_CONTENT),
ELEM(area, false, false, GROUP_MAP_CONTENT, GROUP_NONE),
ELEM(article, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(aside, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(audio, false, false, GROUP_NONE, GROUP_NONE),
ELEM(b, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
ELEM(base, false, false, GROUP_HEAD_CONTENT, GROUP_NONE),
ELEM(basefont, false, false, GROUP_SPECIAL, GROUP_NONE),
ELEM(bdo, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
ELEM(bgsound, false, false, GROUP_NONE, GROUP_NONE),
ELEM(big, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
ELEM(blockquote, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(body, true, true, GROUP_TOPLEVEL, GROUP_FLOW_ELEMENT),
ELEM(br, false, false, GROUP_SPECIAL, GROUP_NONE),
ELEM(button, true, true, GROUP_FORMCONTROL | GROUP_BLOCK,
GROUP_FLOW_ELEMENT),
ELEM(canvas, false, false, GROUP_NONE, GROUP_NONE),
ELEM(caption, true, true, GROUP_NONE, GROUP_INLINE_ELEMENT),
ELEM(center, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(cite, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(code, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(col, false, false, GROUP_TABLE_CONTENT | GROUP_COLGROUP_CONTENT,
GROUP_NONE),
ELEM(colgroup, true, false, GROUP_NONE, GROUP_COLGROUP_CONTENT),
ELEM(content, true, false, GROUP_NONE, GROUP_INLINE_ELEMENT),
ELEM(data, true, false, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(datalist, true, false, GROUP_PHRASE,
GROUP_OPTIONS | GROUP_INLINE_ELEMENT),
ELEM(dd, true, false, GROUP_DL_CONTENT, GROUP_FLOW_ELEMENT),
ELEM(del, true, true, GROUP_PHRASE | GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(details, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(dfn, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(dir, true, false, GROUP_BLOCK, GROUP_LI),
ELEM(div, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(dl, true, false, GROUP_BLOCK, GROUP_DL_CONTENT),
ELEM(dt, true, true, GROUP_DL_CONTENT, GROUP_INLINE_ELEMENT),
ELEM(em, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(embed, false, false, GROUP_NONE, GROUP_NONE),
ELEM(fieldset, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(figcaption, true, false, GROUP_FIGCAPTION, GROUP_FLOW_ELEMENT),
ELEM(figure, true, true, GROUP_BLOCK,
GROUP_FLOW_ELEMENT | GROUP_FIGCAPTION),
ELEM(font, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
ELEM(footer, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(form, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(frame, false, false, GROUP_FRAME, GROUP_NONE),
ELEM(frameset, true, true, GROUP_FRAME, GROUP_FRAME),
ELEM(h1, true, false, GROUP_BLOCK | GROUP_HEADING,
GROUP_INLINE_ELEMENT),
ELEM(h2, true, false, GROUP_BLOCK | GROUP_HEADING,
GROUP_INLINE_ELEMENT),
ELEM(h3, true, false, GROUP_BLOCK | GROUP_HEADING,
GROUP_INLINE_ELEMENT),
ELEM(h4, true, false, GROUP_BLOCK | GROUP_HEADING,
GROUP_INLINE_ELEMENT),
ELEM(h5, true, false, GROUP_BLOCK | GROUP_HEADING,
GROUP_INLINE_ELEMENT),
ELEM(h6, true, false, GROUP_BLOCK | GROUP_HEADING,
GROUP_INLINE_ELEMENT),
ELEM(head, true, false, GROUP_TOPLEVEL, GROUP_HEAD_CONTENT),
ELEM(header, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(hgroup, true, false, GROUP_BLOCK, GROUP_HEADING),
ELEM(hr, false, false, GROUP_BLOCK, GROUP_NONE),
ELEM(html, true, false, GROUP_TOPLEVEL, GROUP_TOPLEVEL),
ELEM(i, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
ELEM(iframe, true, true, GROUP_SPECIAL | GROUP_BLOCK,
GROUP_FLOW_ELEMENT),
ELEM(image, false, false, GROUP_NONE, GROUP_NONE),
ELEM(img, false, false, GROUP_SPECIAL | GROUP_PICTURE_CONTENT, GROUP_NONE),
ELEM(input, false, false, GROUP_FORMCONTROL, GROUP_NONE),
ELEM(ins, true, true, GROUP_PHRASE | GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(kbd, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(keygen, false, false, GROUP_FORMCONTROL, GROUP_NONE),
ELEM(label, true, false, GROUP_FORMCONTROL, GROUP_INLINE_ELEMENT),
ELEM(legend, true, true, GROUP_NONE, GROUP_INLINE_ELEMENT),
ELEM(li, true, false, GROUP_LI, GROUP_FLOW_ELEMENT),
ELEM(link, false, false, GROUP_HEAD_CONTENT, GROUP_NONE),
ELEM(listing, false, false, GROUP_NONE, GROUP_NONE),
ELEM(main, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(map, true, true, GROUP_SPECIAL, GROUP_BLOCK | GROUP_MAP_CONTENT),
ELEM(mark, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(marquee, false, false, GROUP_NONE, GROUP_NONE),
ELEM(menu, true, true, GROUP_BLOCK, GROUP_LI | GROUP_FLOW_ELEMENT),
ELEM(menuitem, false, false, GROUP_NONE, GROUP_NONE),
ELEM(meta, false, false, GROUP_HEAD_CONTENT, GROUP_NONE),
ELEM(meter, true, false, GROUP_SPECIAL, GROUP_FLOW_ELEMENT),
ELEM(multicol, false, false, GROUP_NONE, GROUP_NONE),
ELEM(nav, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(nobr, false, false, GROUP_NONE, GROUP_NONE),
ELEM(noembed, false, false, GROUP_NONE, GROUP_NONE),
ELEM(noframes, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(noscript, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(object, true, true, GROUP_SPECIAL | GROUP_BLOCK,
GROUP_FLOW_ELEMENT | GROUP_OBJECT_CONTENT),
// XXX Can contain self and ul because editor does sublists illegally.
ELEM(ol, true, true, GROUP_BLOCK | GROUP_OL_UL,
GROUP_LI | GROUP_OL_UL),
ELEM(optgroup, true, false, GROUP_SELECT_CONTENT,
GROUP_OPTIONS),
ELEM(option, true, false,
GROUP_SELECT_CONTENT | GROUP_OPTIONS, GROUP_LEAF),
ELEM(output, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
ELEM(p, true, false, GROUP_BLOCK | GROUP_P, GROUP_INLINE_ELEMENT),
ELEM(param, false, false, GROUP_OBJECT_CONTENT, GROUP_NONE),
ELEM(picture, true, false, GROUP_SPECIAL, GROUP_PICTURE_CONTENT),
ELEM(plaintext, false, false, GROUP_NONE, GROUP_NONE),
ELEM(pre, true, true, GROUP_BLOCK, GROUP_INLINE_ELEMENT),
ELEM(progress, true, false, GROUP_SPECIAL, GROUP_FLOW_ELEMENT),
ELEM(q, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
ELEM(rb, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(rp, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(rt, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(rtc, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(ruby, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(s, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
ELEM(samp, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(script, true, false, GROUP_HEAD_CONTENT | GROUP_SPECIAL,
GROUP_LEAF),
ELEM(section, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(select, true, false, GROUP_FORMCONTROL, GROUP_SELECT_CONTENT),
ELEM(shadow, true, false, GROUP_NONE, GROUP_INLINE_ELEMENT),
ELEM(small, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
ELEM(source, false, false, GROUP_PICTURE_CONTENT, GROUP_NONE),
ELEM(span, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
ELEM(strike, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
ELEM(strong, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(style, true, false, GROUP_HEAD_CONTENT, GROUP_LEAF),
ELEM(sub, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
ELEM(summary, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
ELEM(sup, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
ELEM(table, true, false, GROUP_BLOCK, GROUP_TABLE_CONTENT),
ELEM(tbody, true, false, GROUP_TABLE_CONTENT, GROUP_TBODY_CONTENT),
ELEM(td, true, false, GROUP_TR_CONTENT, GROUP_FLOW_ELEMENT),
ELEM(textarea, true, false, GROUP_FORMCONTROL, GROUP_LEAF),
ELEM(tfoot, true, false, GROUP_NONE, GROUP_TBODY_CONTENT),
ELEM(th, true, false, GROUP_TR_CONTENT, GROUP_FLOW_ELEMENT),
ELEM(thead, true, false, GROUP_NONE, GROUP_TBODY_CONTENT),
ELEM(template, false, false, GROUP_NONE, GROUP_NONE),
ELEM(time, true, false, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(title, true, false, GROUP_HEAD_CONTENT, GROUP_LEAF),
ELEM(tr, true, false, GROUP_TBODY_CONTENT, GROUP_TR_CONTENT),
ELEM(track, false, false, GROUP_NONE, GROUP_NONE),
ELEM(tt, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
ELEM(u, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
// XXX Can contain self and ol because editor does sublists illegally.
ELEM(ul, true, true, GROUP_BLOCK | GROUP_OL_UL,
GROUP_LI | GROUP_OL_UL),
ELEM(var, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
ELEM(video, false, false, GROUP_NONE, GROUP_NONE),
ELEM(wbr, false, false, GROUP_NONE, GROUP_NONE),
ELEM(xmp, false, false, GROUP_NONE, GROUP_NONE),
// These aren't elements.
ELEM(text, false, false, GROUP_LEAF, GROUP_NONE),
ELEM(whitespace, false, false, GROUP_LEAF, GROUP_NONE),
ELEM(newline, false, false, GROUP_LEAF, GROUP_NONE),
ELEM(comment, false, false, GROUP_LEAF, GROUP_NONE),
ELEM(entity, false, false, GROUP_NONE, GROUP_NONE),
ELEM(doctypeDecl, false, false, GROUP_NONE, GROUP_NONE),
ELEM(markupDecl, false, false, GROUP_NONE, GROUP_NONE),
ELEM(instruction, false, false, GROUP_NONE, GROUP_NONE),
ELEM(userdefined, true, false, GROUP_NONE, GROUP_FLOW_ELEMENT)
};
bool
HTMLEditUtils::CanContain(int32_t aParent, int32_t aChild)
{
NS_ASSERTION(aParent > eHTMLTag_unknown && aParent <= eHTMLTag_userdefined,
"aParent out of range!");
NS_ASSERTION(aChild > eHTMLTag_unknown && aChild <= eHTMLTag_userdefined,
"aChild out of range!");
#ifdef DEBUG
static bool checked = false;
if (!checked) {
checked = true;
int32_t i;
for (i = 1; i <= eHTMLTag_userdefined; ++i) {
NS_ASSERTION(kElements[i - 1].mTag == i,
"You need to update kElements (missing tags).");
}
}
#endif
// Special-case button.
if (aParent == eHTMLTag_button) {
static const eHTMLTags kButtonExcludeKids[] = {
eHTMLTag_a,
eHTMLTag_fieldset,
eHTMLTag_form,
eHTMLTag_iframe,
eHTMLTag_input,
eHTMLTag_select,
eHTMLTag_textarea
};
uint32_t j;
for (j = 0; j < ArrayLength(kButtonExcludeKids); ++j) {
if (kButtonExcludeKids[j] == aChild) {
return false;
}
}
}
// Deprecated elements.
if (aChild == eHTMLTag_bgsound) {
return false;
}
// Bug #67007, dont strip userdefined tags.
if (aChild == eHTMLTag_userdefined) {
return true;
}
const ElementInfo& parent = kElements[aParent - 1];
if (aParent == aChild) {
return parent.mCanContainSelf;
}
const ElementInfo& child = kElements[aChild - 1];
return (parent.mCanContainGroups & child.mGroup) != 0;
}
bool
HTMLEditUtils::IsContainer(int32_t aTag)
{
NS_ASSERTION(aTag > eHTMLTag_unknown && aTag <= eHTMLTag_userdefined,
"aTag out of range!");
return kElements[aTag - 1].mIsContainer;
}
} // namespace mozilla

View file

@ -0,0 +1,69 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef HTMLEditUtils_h
#define HTMLEditUtils_h
#include <stdint.h>
class nsIDOMNode;
class nsINode;
namespace mozilla {
class HTMLEditUtils final
{
public:
// from nsHTMLEditRules:
static bool IsInlineStyle(nsINode* aNode);
static bool IsInlineStyle(nsIDOMNode *aNode);
static bool IsFormatNode(nsINode* aNode);
static bool IsFormatNode(nsIDOMNode* aNode);
static bool IsNodeThatCanOutdent(nsIDOMNode* aNode);
static bool IsHeader(nsINode& aNode);
static bool IsHeader(nsIDOMNode* aNode);
static bool IsParagraph(nsIDOMNode* aNode);
static bool IsHR(nsIDOMNode* aNode);
static bool IsListItem(nsINode* aNode);
static bool IsListItem(nsIDOMNode* aNode);
static bool IsTable(nsIDOMNode* aNode);
static bool IsTable(nsINode* aNode);
static bool IsTableRow(nsIDOMNode* aNode);
static bool IsTableElement(nsINode* aNode);
static bool IsTableElement(nsIDOMNode* aNode);
static bool IsTableElementButNotTable(nsINode* aNode);
static bool IsTableElementButNotTable(nsIDOMNode* aNode);
static bool IsTableCell(nsINode* node);
static bool IsTableCell(nsIDOMNode* aNode);
static bool IsTableCellOrCaption(nsINode& aNode);
static bool IsList(nsINode* aNode);
static bool IsList(nsIDOMNode* aNode);
static bool IsOrderedList(nsIDOMNode* aNode);
static bool IsUnorderedList(nsIDOMNode* aNode);
static bool IsBlockquote(nsIDOMNode* aNode);
static bool IsPre(nsIDOMNode* aNode);
static bool IsAnchor(nsIDOMNode* aNode);
static bool IsImage(nsINode* aNode);
static bool IsImage(nsIDOMNode* aNode);
static bool IsLink(nsIDOMNode* aNode);
static bool IsLink(nsINode* aNode);
static bool IsNamedAnchor(nsINode* aNode);
static bool IsNamedAnchor(nsIDOMNode* aNode);
static bool IsDiv(nsIDOMNode* aNode);
static bool IsMozDiv(nsINode* aNode);
static bool IsMozDiv(nsIDOMNode* aNode);
static bool IsMailCite(nsINode* aNode);
static bool IsMailCite(nsIDOMNode* aNode);
static bool IsFormWidget(nsINode* aNode);
static bool IsFormWidget(nsIDOMNode* aNode);
static bool SupportsAlignAttr(nsIDOMNode* aNode);
static bool CanContain(int32_t aParent, int32_t aChild);
static bool IsContainer(int32_t aTag);
};
} // namespace mozilla
#endif // #ifndef HTMLEditUtils_h

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,215 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "HTMLEditorEventListener.h"
#include "HTMLEditUtils.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/Selection.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIDOMElement.h"
#include "nsIDOMEvent.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMMouseEvent.h"
#include "nsIDOMNode.h"
#include "nsIEditor.h"
#include "nsIHTMLInlineTableEditor.h"
#include "nsIHTMLObjectResizer.h"
#include "nsISupportsImpl.h"
#include "nsLiteralString.h"
#include "nsQueryObject.h"
#include "nsRange.h"
namespace mozilla {
using namespace dom;
#ifdef DEBUG
nsresult
HTMLEditorEventListener::Connect(EditorBase* aEditorBase)
{
nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryObject(aEditorBase);
nsCOMPtr<nsIHTMLInlineTableEditor> htmlInlineTableEditor =
do_QueryObject(aEditorBase);
NS_PRECONDITION(htmlEditor && htmlInlineTableEditor,
"Set HTMLEditor or its sub class");
return EditorEventListener::Connect(aEditorBase);
}
#endif
HTMLEditor*
HTMLEditorEventListener::GetHTMLEditor()
{
// mEditor must be HTMLEditor or its subclass.
return static_cast<HTMLEditor*>(mEditorBase);
}
nsresult
HTMLEditorEventListener::MouseUp(nsIDOMMouseEvent* aMouseEvent)
{
HTMLEditor* htmlEditor = GetHTMLEditor();
nsCOMPtr<nsIDOMEventTarget> target;
nsresult rv = aMouseEvent->AsEvent()->GetTarget(getter_AddRefs(target));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(target, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(target);
int32_t clientX, clientY;
aMouseEvent->GetClientX(&clientX);
aMouseEvent->GetClientY(&clientY);
htmlEditor->MouseUp(clientX, clientY, element);
return EditorEventListener::MouseUp(aMouseEvent);
}
nsresult
HTMLEditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent)
{
HTMLEditor* htmlEditor = GetHTMLEditor();
// Contenteditable should disregard mousedowns outside it.
// IsAcceptableInputEvent() checks it for a mouse event.
if (!htmlEditor->IsAcceptableInputEvent(aMouseEvent->AsEvent())) {
// If it's not acceptable mousedown event (including when mousedown event
// is fired outside of the active editing host), we need to commit
// composition because it will be change the selection to the clicked
// point. Then, we won't be able to commit the composition.
return EditorEventListener::MouseDown(aMouseEvent);
}
// Detect only "context menu" click
// XXX This should be easier to do!
// But eDOMEvents_contextmenu and eContextMenu is not exposed in any event
// interface :-(
int16_t buttonNumber;
nsresult rv = aMouseEvent->GetButton(&buttonNumber);
NS_ENSURE_SUCCESS(rv, rv);
bool isContextClick = buttonNumber == 2;
int32_t clickCount;
rv = aMouseEvent->GetDetail(&clickCount);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOMEventTarget> target;
rv = aMouseEvent->AsEvent()->GetExplicitOriginalTarget(getter_AddRefs(target));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(target, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(target);
if (isContextClick || (buttonNumber == 0 && clickCount == 2)) {
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_OK);
// Get location of mouse within target node
nsCOMPtr<nsIDOMNode> parent;
rv = aMouseEvent->GetRangeParent(getter_AddRefs(parent));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE);
int32_t offset = 0;
rv = aMouseEvent->GetRangeOffset(&offset);
NS_ENSURE_SUCCESS(rv, rv);
// Detect if mouse point is within current selection for context click
bool nodeIsInSelection = false;
if (isContextClick && !selection->Collapsed()) {
int32_t rangeCount;
rv = selection->GetRangeCount(&rangeCount);
NS_ENSURE_SUCCESS(rv, rv);
for (int32_t i = 0; i < rangeCount; i++) {
RefPtr<nsRange> range = selection->GetRangeAt(i);
if (!range) {
// Don't bail yet, iterate through them all
continue;
}
range->IsPointInRange(parent, offset, &nodeIsInSelection);
// Done when we find a range that we are in
if (nodeIsInSelection) {
break;
}
}
}
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(target);
if (node && !nodeIsInSelection) {
if (!element) {
if (isContextClick) {
// Set the selection to the point under the mouse cursor:
selection->Collapse(parent, offset);
} else {
// Get enclosing link if in text so we can select the link
nsCOMPtr<nsIDOMElement> linkElement;
rv = htmlEditor->GetElementOrParentByTagName(
NS_LITERAL_STRING("href"), node,
getter_AddRefs(linkElement));
NS_ENSURE_SUCCESS(rv, rv);
if (linkElement) {
element = linkElement;
}
}
}
// Select entire element clicked on if NOT within an existing selection
// and not the entire body, or table-related elements
if (element) {
nsCOMPtr<nsIDOMNode> selectAllNode =
htmlEditor->FindUserSelectAllNode(element);
if (selectAllNode) {
nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(selectAllNode);
if (newElement) {
node = selectAllNode;
element = newElement;
}
}
if (isContextClick && !HTMLEditUtils::IsImage(node)) {
selection->Collapse(parent, offset);
} else {
htmlEditor->SelectElement(element);
}
}
}
// HACK !!! Context click places the caret but the context menu consumes
// the event; so we need to check resizing state ourselves
htmlEditor->CheckSelectionStateForAnonymousButtons(selection);
// Prevent bubbling if we changed selection or
// for all context clicks
if (element || isContextClick) {
aMouseEvent->AsEvent()->PreventDefault();
return NS_OK;
}
} else if (!isContextClick && buttonNumber == 0 && clickCount == 1) {
// if the target element is an image, we have to display resizers
int32_t clientX, clientY;
aMouseEvent->GetClientX(&clientX);
aMouseEvent->GetClientY(&clientY);
htmlEditor->MouseDown(clientX, clientY, element, aMouseEvent->AsEvent());
}
return EditorEventListener::MouseDown(aMouseEvent);
}
nsresult
HTMLEditorEventListener::MouseClick(nsIDOMMouseEvent* aMouseEvent)
{
nsCOMPtr<nsIDOMEventTarget> target;
nsresult rv = aMouseEvent->AsEvent()->GetTarget(getter_AddRefs(target));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(target, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(target);
GetHTMLEditor()->DoInlineTableEditingAction(element);
return EditorEventListener::MouseClick(aMouseEvent);
}
} // namespace mozilla

View file

@ -0,0 +1,43 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef HTMLEditorEventListener_h
#define HTMLEditorEventListener_h
#include "EditorEventListener.h"
#include "nscore.h"
namespace mozilla {
class EditorBase;
class HTMLEditor;
class HTMLEditorEventListener final : public EditorEventListener
{
public:
HTMLEditorEventListener()
{
}
virtual ~HTMLEditorEventListener()
{
}
#ifdef DEBUG
// WARNING: You must be use HTMLEditor or its sub class for this class.
virtual nsresult Connect(EditorBase* aEditorBase) override;
#endif
protected:
virtual nsresult MouseDown(nsIDOMMouseEvent* aMouseEvent) override;
virtual nsresult MouseUp(nsIDOMMouseEvent* aMouseEvent) override;
virtual nsresult MouseClick(nsIDOMMouseEvent* aMouseEvent) override;
inline HTMLEditor* GetHTMLEditor();
};
} // namespace mozilla
#endif // #ifndef HTMLEditorEventListener_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,82 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef HTMLEditorObjectResizerUtils_h
#define HTMLEditorObjectResizerUtils_h
#include "nsIDOMEventListener.h"
#include "nsISelectionListener.h"
#include "nsISupportsImpl.h"
#include "nsIWeakReferenceUtils.h"
#include "nsLiteralString.h"
class nsIHTMLEditor;
namespace mozilla {
#define kTopLeft NS_LITERAL_STRING("nw")
#define kTop NS_LITERAL_STRING("n")
#define kTopRight NS_LITERAL_STRING("ne")
#define kLeft NS_LITERAL_STRING("w")
#define kRight NS_LITERAL_STRING("e")
#define kBottomLeft NS_LITERAL_STRING("sw")
#define kBottom NS_LITERAL_STRING("s")
#define kBottomRight NS_LITERAL_STRING("se")
/******************************************************************************
* mozilla::ResizerSelectionListener
******************************************************************************/
class ResizerSelectionListener final : public nsISelectionListener
{
public:
explicit ResizerSelectionListener(nsIHTMLEditor* aEditor);
void Reset();
NS_DECL_ISUPPORTS
NS_DECL_NSISELECTIONLISTENER
protected:
virtual ~ResizerSelectionListener() {}
nsWeakPtr mEditor;
};
/******************************************************************************
* mozilla::ResizerMouseMotionListener
******************************************************************************/
class ResizerMouseMotionListener final : public nsIDOMEventListener
{
public:
explicit ResizerMouseMotionListener(nsIHTMLEditor* aEditor);
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
protected:
virtual ~ResizerMouseMotionListener() {}
nsWeakPtr mEditor;
};
/******************************************************************************
* mozilla::DocumentResizeEventListener
******************************************************************************/
class DocumentResizeEventListener final : public nsIDOMEventListener
{
public:
explicit DocumentResizeEventListener(nsIHTMLEditor* aEditor);
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
protected:
virtual ~DocumentResizeEventListener() {}
nsWeakPtr mEditor;
};
} // namespace mozilla
#endif // #ifndef HTMLEditorObjectResizerUtils_h

View file

@ -0,0 +1,283 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/HTMLEditor.h"
#include "HTMLEditUtils.h"
#include "mozilla/dom/Element.h"
#include "nsAString.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIContent.h"
#include "nsIDOMElement.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMHTMLElement.h"
#include "nsIDOMNode.h"
#include "nsIHTMLObjectResizer.h"
#include "nsIPresShell.h"
#include "nsLiteralString.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nscore.h"
namespace mozilla {
// Uncomment the following line if you want to disable
// table deletion when the only column/row is removed
// #define DISABLE_TABLE_DELETION 1
NS_IMETHODIMP
HTMLEditor::SetInlineTableEditingEnabled(bool aIsEnabled)
{
mIsInlineTableEditingEnabled = aIsEnabled;
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::GetInlineTableEditingEnabled(bool* aIsEnabled)
{
*aIsEnabled = mIsInlineTableEditingEnabled;
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::ShowInlineTableEditingUI(nsIDOMElement* aCell)
{
NS_ENSURE_ARG_POINTER(aCell);
// do nothing if aCell is not a table cell...
nsCOMPtr<Element> cell = do_QueryInterface(aCell);
if (!cell || !HTMLEditUtils::IsTableCell(cell)) {
return NS_OK;
}
if (NS_WARN_IF(!IsDescendantOfEditorRoot(cell))) {
return NS_ERROR_UNEXPECTED;
}
if (mInlineEditedCell) {
NS_ERROR("call HideInlineTableEditingUI first");
return NS_ERROR_UNEXPECTED;
}
// the resizers and the shadow will be anonymous children of the body
nsCOMPtr<nsIDOMElement> bodyElement = do_QueryInterface(GetRoot());
NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER);
CreateAnonymousElement(NS_LITERAL_STRING("a"), bodyElement,
NS_LITERAL_STRING("mozTableAddColumnBefore"),
false, getter_AddRefs(mAddColumnBeforeButton));
CreateAnonymousElement(NS_LITERAL_STRING("a"), bodyElement,
NS_LITERAL_STRING("mozTableRemoveColumn"),
false, getter_AddRefs(mRemoveColumnButton));
CreateAnonymousElement(NS_LITERAL_STRING("a"), bodyElement,
NS_LITERAL_STRING("mozTableAddColumnAfter"),
false, getter_AddRefs(mAddColumnAfterButton));
CreateAnonymousElement(NS_LITERAL_STRING("a"), bodyElement,
NS_LITERAL_STRING("mozTableAddRowBefore"),
false, getter_AddRefs(mAddRowBeforeButton));
CreateAnonymousElement(NS_LITERAL_STRING("a"), bodyElement,
NS_LITERAL_STRING("mozTableRemoveRow"),
false, getter_AddRefs(mRemoveRowButton));
CreateAnonymousElement(NS_LITERAL_STRING("a"), bodyElement,
NS_LITERAL_STRING("mozTableAddRowAfter"),
false, getter_AddRefs(mAddRowAfterButton));
AddMouseClickListener(mAddColumnBeforeButton);
AddMouseClickListener(mRemoveColumnButton);
AddMouseClickListener(mAddColumnAfterButton);
AddMouseClickListener(mAddRowBeforeButton);
AddMouseClickListener(mRemoveRowButton);
AddMouseClickListener(mAddRowAfterButton);
mInlineEditedCell = aCell;
return RefreshInlineTableEditingUI();
}
NS_IMETHODIMP
HTMLEditor::HideInlineTableEditingUI()
{
mInlineEditedCell = nullptr;
RemoveMouseClickListener(mAddColumnBeforeButton);
RemoveMouseClickListener(mRemoveColumnButton);
RemoveMouseClickListener(mAddColumnAfterButton);
RemoveMouseClickListener(mAddRowBeforeButton);
RemoveMouseClickListener(mRemoveRowButton);
RemoveMouseClickListener(mAddRowAfterButton);
// get the presshell's document observer interface.
nsCOMPtr<nsIPresShell> ps = GetPresShell();
// We allow the pres shell to be null; when it is, we presume there
// are no document observers to notify, but we still want to
// UnbindFromTree.
// get the root content node.
nsCOMPtr<nsIContent> bodyContent = GetRoot();
DeleteRefToAnonymousNode(mAddColumnBeforeButton, bodyContent, ps);
mAddColumnBeforeButton = nullptr;
DeleteRefToAnonymousNode(mRemoveColumnButton, bodyContent, ps);
mRemoveColumnButton = nullptr;
DeleteRefToAnonymousNode(mAddColumnAfterButton, bodyContent, ps);
mAddColumnAfterButton = nullptr;
DeleteRefToAnonymousNode(mAddRowBeforeButton, bodyContent, ps);
mAddRowBeforeButton = nullptr;
DeleteRefToAnonymousNode(mRemoveRowButton, bodyContent, ps);
mRemoveRowButton = nullptr;
DeleteRefToAnonymousNode(mAddRowAfterButton, bodyContent, ps);
mAddRowAfterButton = nullptr;
return NS_OK;
}
NS_IMETHODIMP
HTMLEditor::DoInlineTableEditingAction(nsIDOMElement* aElement)
{
NS_ENSURE_ARG_POINTER(aElement);
bool anonElement = false;
if (aElement &&
NS_SUCCEEDED(aElement->HasAttribute(NS_LITERAL_STRING("_moz_anonclass"), &anonElement)) &&
anonElement) {
nsAutoString anonclass;
nsresult rv =
aElement->GetAttribute(NS_LITERAL_STRING("_moz_anonclass"), anonclass);
NS_ENSURE_SUCCESS(rv, rv);
if (!StringBeginsWith(anonclass, NS_LITERAL_STRING("mozTable")))
return NS_OK;
nsCOMPtr<nsIDOMNode> tableNode = GetEnclosingTable(mInlineEditedCell);
nsCOMPtr<nsIDOMElement> tableElement = do_QueryInterface(tableNode);
int32_t rowCount, colCount;
rv = GetTableSize(tableElement, &rowCount, &colCount);
NS_ENSURE_SUCCESS(rv, rv);
bool hideUI = false;
bool hideResizersWithInlineTableUI = (GetAsDOMNode(mResizedObject) == tableElement);
if (anonclass.EqualsLiteral("mozTableAddColumnBefore"))
InsertTableColumn(1, false);
else if (anonclass.EqualsLiteral("mozTableAddColumnAfter"))
InsertTableColumn(1, true);
else if (anonclass.EqualsLiteral("mozTableAddRowBefore"))
InsertTableRow(1, false);
else if (anonclass.EqualsLiteral("mozTableAddRowAfter"))
InsertTableRow(1, true);
else if (anonclass.EqualsLiteral("mozTableRemoveColumn")) {
DeleteTableColumn(1);
#ifndef DISABLE_TABLE_DELETION
hideUI = (colCount == 1);
#endif
}
else if (anonclass.EqualsLiteral("mozTableRemoveRow")) {
DeleteTableRow(1);
#ifndef DISABLE_TABLE_DELETION
hideUI = (rowCount == 1);
#endif
}
else
return NS_OK;
if (hideUI) {
HideInlineTableEditingUI();
if (hideResizersWithInlineTableUI)
HideResizers();
}
}
return NS_OK;
}
void
HTMLEditor::AddMouseClickListener(nsIDOMElement* aElement)
{
nsCOMPtr<nsIDOMEventTarget> evtTarget(do_QueryInterface(aElement));
if (evtTarget) {
evtTarget->AddEventListener(NS_LITERAL_STRING("click"),
mEventListener, true);
}
}
void
HTMLEditor::RemoveMouseClickListener(nsIDOMElement* aElement)
{
nsCOMPtr<nsIDOMEventTarget> evtTarget(do_QueryInterface(aElement));
if (evtTarget) {
evtTarget->RemoveEventListener(NS_LITERAL_STRING("click"),
mEventListener, true);
}
}
NS_IMETHODIMP
HTMLEditor::RefreshInlineTableEditingUI()
{
nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(mInlineEditedCell);
if (!htmlElement) {
return NS_ERROR_NULL_POINTER;
}
int32_t xCell, yCell, wCell, hCell;
GetElementOrigin(mInlineEditedCell, xCell, yCell);
nsresult rv = htmlElement->GetOffsetWidth(&wCell);
NS_ENSURE_SUCCESS(rv, rv);
rv = htmlElement->GetOffsetHeight(&hCell);
NS_ENSURE_SUCCESS(rv, rv);
int32_t xHoriz = xCell + wCell/2;
int32_t yVert = yCell + hCell/2;
nsCOMPtr<nsIDOMNode> tableNode = GetEnclosingTable(mInlineEditedCell);
nsCOMPtr<nsIDOMElement> tableElement = do_QueryInterface(tableNode);
int32_t rowCount, colCount;
rv = GetTableSize(tableElement, &rowCount, &colCount);
NS_ENSURE_SUCCESS(rv, rv);
SetAnonymousElementPosition(xHoriz-10, yCell-7, mAddColumnBeforeButton);
#ifdef DISABLE_TABLE_DELETION
NS_NAMED_LITERAL_STRING(classStr, "class");
if (colCount== 1) {
mRemoveColumnButton->SetAttribute(classStr,
NS_LITERAL_STRING("hidden"));
}
else {
bool hasClass = false;
rv = mRemoveColumnButton->HasAttribute(classStr, &hasClass);
if (NS_SUCCEEDED(rv) && hasClass) {
mRemoveColumnButton->RemoveAttribute(classStr);
}
#endif
SetAnonymousElementPosition(xHoriz-4, yCell-7, mRemoveColumnButton);
#ifdef DISABLE_TABLE_DELETION
}
#endif
SetAnonymousElementPosition(xHoriz+6, yCell-7, mAddColumnAfterButton);
SetAnonymousElementPosition(xCell-7, yVert-10, mAddRowBeforeButton);
#ifdef DISABLE_TABLE_DELETION
if (rowCount== 1) {
mRemoveRowButton->SetAttribute(classStr,
NS_LITERAL_STRING("hidden"));
}
else {
bool hasClass = false;
rv = mRemoveRowButton->HasAttribute(classStr, &hasClass);
if (NS_SUCCEEDED(rv) && hasClass) {
mRemoveRowButton->RemoveAttribute(classStr);
}
#endif
SetAnonymousElementPosition(xCell-7, yVert-4, mRemoveRowButton);
#ifdef DISABLE_TABLE_DELETION
}
#endif
SetAnonymousElementPosition(xCell-7, yVert+6, mAddRowAfterButton);
return NS_OK;
}
} // namespace mozilla

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,262 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Here is the list, from beppe and glazman:
href >> A, AREA, BASE, LINK
src >> FRAME, IFRAME, IMG, INPUT, SCRIPT
<META http-equiv="refresh" content="3,http://www.acme.com/intro.html">
longdesc >> FRAME, IFRAME, IMG
usemap >> IMG, INPUT, OBJECT
action >> FORM
background >> BODY
codebase >> OBJECT, APPLET
classid >> OBJECT
data >> OBJECT
cite >> BLOCKQUOTE, DEL, INS, Q
profile >> HEAD
ARCHIVE attribute on APPLET ; warning, it contains a list of URIs.
Easier way of organizing the list:
a: href
area: href
base: href
body: background
blockquote: cite (not normally rewritable)
link: href
frame: src, longdesc
iframe: src, longdesc
input: src, usemap
form: action
img: src, longdesc, usemap
script: src
applet: codebase, archive <list>
object: codebase, data, classid, usemap
head: profile
del: cite
ins: cite
q: cite
*/
#include "HTMLURIRefObject.h"
#include "mozilla/mozalloc.h"
#include "nsAString.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsID.h"
#include "nsIDOMAttr.h"
#include "nsIDOMElement.h"
#include "nsIDOMMozNamedAttrMap.h"
#include "nsIDOMNode.h"
#include "nsISupportsUtils.h"
#include "nsString.h"
namespace mozilla {
// String classes change too often and I can't keep up.
// Set this macro to this week's approved case-insensitive compare routine.
#define MATCHES(tagName, str) tagName.EqualsIgnoreCase(str)
HTMLURIRefObject::HTMLURIRefObject()
: mCurAttrIndex(0)
, mAttributeCnt(0)
{
}
HTMLURIRefObject::~HTMLURIRefObject()
{
}
//Interfaces for addref and release and queryinterface
NS_IMPL_ISUPPORTS(HTMLURIRefObject, nsIURIRefObject)
NS_IMETHODIMP
HTMLURIRefObject::Reset()
{
mCurAttrIndex = 0;
return NS_OK;
}
NS_IMETHODIMP
HTMLURIRefObject::GetNextURI(nsAString& aURI)
{
NS_ENSURE_TRUE(mNode, NS_ERROR_NOT_INITIALIZED);
// XXX Why don't you use nsIAtom for comparing the tag name a lot?
nsAutoString tagName;
nsresult rv = mNode->GetNodeName(tagName);
NS_ENSURE_SUCCESS(rv, rv);
// Loop over attribute list:
if (!mAttributes) {
nsCOMPtr<nsIDOMElement> element (do_QueryInterface(mNode));
NS_ENSURE_TRUE(element, NS_ERROR_INVALID_ARG);
mCurAttrIndex = 0;
element->GetAttributes(getter_AddRefs(mAttributes));
NS_ENSURE_TRUE(mAttributes, NS_ERROR_NOT_INITIALIZED);
rv = mAttributes->GetLength(&mAttributeCnt);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(mAttributeCnt, NS_ERROR_FAILURE);
mCurAttrIndex = 0;
}
while (mCurAttrIndex < mAttributeCnt) {
nsCOMPtr<nsIDOMAttr> attrNode;
rv = mAttributes->Item(mCurAttrIndex++, getter_AddRefs(attrNode));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG_POINTER(attrNode);
nsString curAttr;
rv = attrNode->GetName(curAttr);
NS_ENSURE_SUCCESS(rv, rv);
// href >> A, AREA, BASE, LINK
if (MATCHES(curAttr, "href")) {
if (!MATCHES(tagName, "a") && !MATCHES(tagName, "area") &&
!MATCHES(tagName, "base") && !MATCHES(tagName, "link")) {
continue;
}
rv = attrNode->GetValue(aURI);
NS_ENSURE_SUCCESS(rv, rv);
nsString uri (aURI);
// href pointing to a named anchor doesn't count
if (aURI.First() != char16_t('#')) {
return NS_OK;
}
aURI.Truncate();
return NS_ERROR_INVALID_ARG;
}
// src >> FRAME, IFRAME, IMG, INPUT, SCRIPT
else if (MATCHES(curAttr, "src")) {
if (!MATCHES(tagName, "img") &&
!MATCHES(tagName, "frame") && !MATCHES(tagName, "iframe") &&
!MATCHES(tagName, "input") && !MATCHES(tagName, "script")) {
continue;
}
return attrNode->GetValue(aURI);
}
//<META http-equiv="refresh" content="3,http://www.acme.com/intro.html">
else if (MATCHES(curAttr, "content")) {
if (!MATCHES(tagName, "meta")) {
continue;
}
}
// longdesc >> FRAME, IFRAME, IMG
else if (MATCHES(curAttr, "longdesc")) {
if (!MATCHES(tagName, "img") &&
!MATCHES(tagName, "frame") && !MATCHES(tagName, "iframe")) {
continue;
}
}
// usemap >> IMG, INPUT, OBJECT
else if (MATCHES(curAttr, "usemap")) {
if (!MATCHES(tagName, "img") &&
!MATCHES(tagName, "input") && !MATCHES(tagName, "object")) {
continue;
}
}
// action >> FORM
else if (MATCHES(curAttr, "action")) {
if (!MATCHES(tagName, "form")) {
continue;
}
}
// background >> BODY
else if (MATCHES(curAttr, "background")) {
if (!MATCHES(tagName, "body")) {
continue;
}
}
// codebase >> OBJECT, APPLET
else if (MATCHES(curAttr, "codebase")) {
if (!MATCHES(tagName, "meta")) {
continue;
}
}
// classid >> OBJECT
else if (MATCHES(curAttr, "classid")) {
if (!MATCHES(tagName, "object")) {
continue;
}
}
// data >> OBJECT
else if (MATCHES(curAttr, "data")) {
if (!MATCHES(tagName, "object")) {
continue;
}
}
// cite >> BLOCKQUOTE, DEL, INS, Q
else if (MATCHES(curAttr, "cite")) {
if (!MATCHES(tagName, "blockquote") && !MATCHES(tagName, "q") &&
!MATCHES(tagName, "del") && !MATCHES(tagName, "ins")) {
continue;
}
}
// profile >> HEAD
else if (MATCHES(curAttr, "profile")) {
if (!MATCHES(tagName, "head")) {
continue;
}
}
// archive attribute on APPLET; warning, it contains a list of URIs.
else if (MATCHES(curAttr, "archive")) {
if (!MATCHES(tagName, "applet")) {
continue;
}
}
}
// Return a code to indicate that there are no more,
// to distinguish that case from real errors.
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
HTMLURIRefObject::RewriteAllURIs(const nsAString& aOldPat,
const nsAString& aNewPat,
bool aMakeRel)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
HTMLURIRefObject::GetNode(nsIDOMNode** aNode)
{
NS_ENSURE_TRUE(mNode, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
*aNode = mNode.get();
NS_ADDREF(*aNode);
return NS_OK;
}
NS_IMETHODIMP
HTMLURIRefObject::SetNode(nsIDOMNode* aNode)
{
mNode = aNode;
nsAutoString dummyURI;
if (NS_SUCCEEDED(GetNextURI(dummyURI))) {
mCurAttrIndex = 0; // Reset so we'll get the first node next time
return NS_OK;
}
// If there weren't any URIs in the attributes,
// then don't accept this node.
mNode = nullptr;
return NS_ERROR_INVALID_ARG;
}
} // namespace mozilla
nsresult NS_NewHTMLURIRefObject(nsIURIRefObject** aResult, nsIDOMNode* aNode)
{
RefPtr<mozilla::HTMLURIRefObject> refObject = new mozilla::HTMLURIRefObject();
nsresult rv = refObject->SetNode(aNode);
if (NS_FAILED(rv)) {
*aResult = 0;
return rv;
}
refObject.forget(aResult);
return NS_OK;
}

View file

@ -0,0 +1,48 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef HTMLURIRefObject_h
#define HTMLURIRefObject_h
#include "nsCOMPtr.h"
#include "nsISupportsImpl.h"
#include "nsIURIRefObject.h"
#include "nscore.h"
#define NS_URI_REF_OBJECT_CID \
{ /* {bdd79df6-1dd1-11b2-b29c-c3d63a58f1d2} */ \
0xbdd79df6, 0x1dd1, 0x11b2, \
{ 0xb2, 0x9c, 0xc3, 0xd6, 0x3a, 0x58, 0xf1, 0xd2 } \
}
class nsIDOMMozNamedAttrMap;
class nsIDOMNode;
namespace mozilla {
class HTMLURIRefObject final : public nsIURIRefObject
{
public:
HTMLURIRefObject();
// Interfaces for addref and release and queryinterface
NS_DECL_ISUPPORTS
NS_DECL_NSIURIREFOBJECT
protected:
virtual ~HTMLURIRefObject();
nsCOMPtr<nsIDOMNode> mNode;
nsCOMPtr<nsIDOMMozNamedAttrMap> mAttributes;
uint32_t mCurAttrIndex;
uint32_t mAttributeCnt;
};
} // namespace mozilla
nsresult NS_NewHTMLURIRefObject(nsIURIRefObject** aResult, nsIDOMNode* aNode);
#endif // #ifndef HTMLURIRefObject_h

View file

@ -0,0 +1,97 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "InsertNodeTransaction.h"
#include "mozilla/EditorBase.h" // for EditorBase
#include "mozilla/dom/Selection.h" // for Selection
#include "nsAString.h"
#include "nsDebug.h" // for NS_ENSURE_TRUE, etc.
#include "nsError.h" // for NS_ERROR_NULL_POINTER, etc.
#include "nsIContent.h" // for nsIContent
#include "nsMemory.h" // for nsMemory
#include "nsReadableUtils.h" // for ToNewCString
#include "nsString.h" // for nsString
namespace mozilla {
using namespace dom;
InsertNodeTransaction::InsertNodeTransaction(nsIContent& aNode,
nsINode& aParent,
int32_t aOffset,
EditorBase& aEditorBase)
: mNode(&aNode)
, mParent(&aParent)
, mOffset(aOffset)
, mEditorBase(aEditorBase)
{
}
InsertNodeTransaction::~InsertNodeTransaction()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertNodeTransaction, EditTransactionBase,
mNode,
mParent)
NS_IMPL_ADDREF_INHERITED(InsertNodeTransaction, EditTransactionBase)
NS_IMPL_RELEASE_INHERITED(InsertNodeTransaction, EditTransactionBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertNodeTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
InsertNodeTransaction::DoTransaction()
{
MOZ_ASSERT(mNode && mParent);
uint32_t count = mParent->GetChildCount();
if (mOffset > static_cast<int32_t>(count) || mOffset == -1) {
// -1 is sentinel value meaning "append at end"
mOffset = count;
}
// Note, it's ok for ref to be null. That means append.
nsCOMPtr<nsIContent> ref = mParent->GetChildAt(mOffset);
mEditorBase.MarkNodeDirty(GetAsDOMNode(mNode));
ErrorResult rv;
mParent->InsertBefore(*mNode, ref, rv);
NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
// Only set selection to insertion point if editor gives permission
if (mEditorBase.GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase.GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
// Place the selection just after the inserted element
selection->Collapse(mParent, mOffset + 1);
} else {
// Do nothing - DOM Range gravity will adjust selection
}
return NS_OK;
}
NS_IMETHODIMP
InsertNodeTransaction::UndoTransaction()
{
MOZ_ASSERT(mNode && mParent);
ErrorResult rv;
mParent->RemoveChild(*mNode, rv);
return rv.StealNSResult();
}
NS_IMETHODIMP
InsertNodeTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("InsertNodeTransaction");
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,58 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef InsertNodeTransaction_h
#define InsertNodeTransaction_h
#include "mozilla/EditTransactionBase.h" // for EditTransactionBase, etc.
#include "nsCOMPtr.h" // for nsCOMPtr
#include "nsCycleCollectionParticipant.h"
#include "nsIContent.h" // for nsIContent
#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS_INHERITED
namespace mozilla {
class EditorBase;
/**
* A transaction that inserts a single element
*/
class InsertNodeTransaction final : public EditTransactionBase
{
public:
/**
* Initialize the transaction.
* @param aNode The node to insert.
* @param aParent The node to insert into.
* @param aOffset The offset in aParent to insert aNode.
*/
InsertNodeTransaction(nsIContent& aNode, nsINode& aParent, int32_t aOffset,
EditorBase& aEditorBase);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InsertNodeTransaction,
EditTransactionBase)
NS_DECL_EDITTRANSACTIONBASE
protected:
virtual ~InsertNodeTransaction();
// The element to insert.
nsCOMPtr<nsIContent> mNode;
// The node into which the new node will be inserted.
nsCOMPtr<nsINode> mParent;
// The index in mParent for the new node.
int32_t mOffset;
// The editor for this transaction.
EditorBase& mEditorBase;
};
} // namespace mozilla
#endif // #ifndef InsertNodeTransaction_h

View file

@ -0,0 +1,125 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "InsertTextTransaction.h"
#include "mozilla/EditorBase.h" // mEditorBase
#include "mozilla/SelectionState.h" // RangeUpdater
#include "mozilla/dom/Selection.h" // Selection local var
#include "mozilla/dom/Text.h" // mTextNode
#include "nsAString.h" // nsAString parameter
#include "nsDebug.h" // for NS_ASSERTION, etc.
#include "nsError.h" // for NS_OK, etc.
#include "nsQueryObject.h" // for do_QueryObject
namespace mozilla {
using namespace dom;
InsertTextTransaction::InsertTextTransaction(Text& aTextNode,
uint32_t aOffset,
const nsAString& aStringToInsert,
EditorBase& aEditorBase,
RangeUpdater* aRangeUpdater)
: mTextNode(&aTextNode)
, mOffset(aOffset)
, mStringToInsert(aStringToInsert)
, mEditorBase(aEditorBase)
, mRangeUpdater(aRangeUpdater)
{
}
InsertTextTransaction::~InsertTextTransaction()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertTextTransaction, EditTransactionBase,
mTextNode)
NS_IMPL_ADDREF_INHERITED(InsertTextTransaction, EditTransactionBase)
NS_IMPL_RELEASE_INHERITED(InsertTextTransaction, EditTransactionBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertTextTransaction)
if (aIID.Equals(NS_GET_IID(InsertTextTransaction))) {
foundInterface = static_cast<nsITransaction*>(this);
} else
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
InsertTextTransaction::DoTransaction()
{
nsresult rv = mTextNode->InsertData(mOffset, mStringToInsert);
NS_ENSURE_SUCCESS(rv, rv);
// Only set selection to insertion point if editor gives permission
if (mEditorBase.GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase.GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
DebugOnly<nsresult> rv =
selection->Collapse(mTextNode, mOffset + mStringToInsert.Length());
NS_ASSERTION(NS_SUCCEEDED(rv),
"Selection could not be collapsed after insert");
} else {
// Do nothing - DOM Range gravity will adjust selection
}
mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
return NS_OK;
}
NS_IMETHODIMP
InsertTextTransaction::UndoTransaction()
{
return mTextNode->DeleteData(mOffset, mStringToInsert.Length());
}
NS_IMETHODIMP
InsertTextTransaction::Merge(nsITransaction* aTransaction,
bool* aDidMerge)
{
if (!aTransaction || !aDidMerge) {
return NS_OK;
}
// Set out param default value
*aDidMerge = false;
// If aTransaction is a InsertTextTransaction, and if the selection hasn't
// changed, then absorb it.
RefPtr<InsertTextTransaction> otherTransaction = do_QueryObject(aTransaction);
if (otherTransaction && IsSequentialInsert(*otherTransaction)) {
nsAutoString otherData;
otherTransaction->GetData(otherData);
mStringToInsert += otherData;
*aDidMerge = true;
}
return NS_OK;
}
NS_IMETHODIMP
InsertTextTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("InsertTextTransaction: ");
aString += mStringToInsert;
return NS_OK;
}
/* ============ private methods ================== */
void
InsertTextTransaction::GetData(nsString& aResult)
{
aResult = mStringToInsert;
}
bool
InsertTextTransaction::IsSequentialInsert(
InsertTextTransaction& aOtherTransaction)
{
return aOtherTransaction.mTextNode == mTextNode &&
aOtherTransaction.mOffset == mOffset + mStringToInsert.Length();
}
} // namespace mozilla

View file

@ -0,0 +1,88 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef InsertTextTransaction_h
#define InsertTextTransaction_h
#include "mozilla/EditTransactionBase.h" // base class
#include "nsCycleCollectionParticipant.h" // various macros
#include "nsID.h" // NS_DECLARE_STATIC_IID_ACCESSOR
#include "nsISupportsImpl.h" // NS_DECL_ISUPPORTS_INHERITED
#include "nsString.h" // nsString members
#include "nscore.h" // NS_IMETHOD, nsAString
class nsITransaction;
#define NS_INSERTTEXTTXN_IID \
{ 0x8c9ad77f, 0x22a7, 0x4d01, \
{ 0xb1, 0x59, 0x8a, 0x0f, 0xdb, 0x1d, 0x08, 0xe9 } }
namespace mozilla {
class EditorBase;
class RangeUpdater;
namespace dom {
class Text;
} // namespace dom
/**
* A transaction that inserts text into a content node.
*/
class InsertTextTransaction final : public EditTransactionBase
{
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INSERTTEXTTXN_IID)
/**
* @param aElement The text content node.
* @param aOffset The location in aElement to do the insertion.
* @param aString The new text to insert.
* @param aPresShell Used to get and set the selection.
* @param aRangeUpdater The range updater
*/
InsertTextTransaction(dom::Text& aTextNode, uint32_t aOffset,
const nsAString& aString, EditorBase& aEditorBase,
RangeUpdater* aRangeUpdater);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InsertTextTransaction,
EditTransactionBase)
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aDidMerge) override;
/**
* Return the string data associated with this transaction.
*/
void GetData(nsString& aResult);
private:
virtual ~InsertTextTransaction();
// Return true if aOtherTransaction immediately follows this transaction.
bool IsSequentialInsert(InsertTextTransaction& aOtherTrasaction);
// The Text node to operate upon.
RefPtr<dom::Text> mTextNode;
// The offset into mTextNode where the insertion is to take place.
uint32_t mOffset;
// The text to insert into mTextNode at mOffset.
nsString mStringToInsert;
// The editor, which we'll need to get the selection.
EditorBase& mEditorBase;
RangeUpdater* mRangeUpdater;
};
NS_DEFINE_STATIC_IID_ACCESSOR(InsertTextTransaction, NS_INSERTTEXTTXN_IID)
} // namespace mozilla
#endif // #ifndef InsertTextTransaction_h

View file

@ -0,0 +1,366 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "InternetCiter.h"
#include "nsAString.h"
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include "nsDebug.h"
#include "nsDependentSubstring.h"
#include "nsError.h"
#include "nsILineBreaker.h"
#include "nsLWBrkCIID.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsStringIterator.h"
namespace mozilla {
const char16_t gt ('>');
const char16_t space (' ');
const char16_t nl ('\n');
const char16_t cr('\r');
/**
* Mail citations using the Internet style: > This is a citation.
*/
nsresult
InternetCiter::GetCiteString(const nsAString& aInString,
nsAString& aOutString)
{
aOutString.Truncate();
char16_t uch = nl;
// Strip trailing new lines which will otherwise turn up
// as ugly quoted empty lines.
nsReadingIterator <char16_t> beginIter,endIter;
aInString.BeginReading(beginIter);
aInString.EndReading(endIter);
while(beginIter!= endIter &&
(*endIter == cr || *endIter == nl)) {
--endIter;
}
// Loop over the string:
while (beginIter != endIter) {
if (uch == nl) {
aOutString.Append(gt);
// No space between >: this is ">>> " style quoting, for
// compatibility with RFC 2646 and format=flowed.
if (*beginIter != gt) {
aOutString.Append(space);
}
}
uch = *beginIter;
++beginIter;
aOutString += uch;
}
if (uch != nl) {
aOutString += nl;
}
return NS_OK;
}
nsresult
InternetCiter::StripCitesAndLinebreaks(const nsAString& aInString,
nsAString& aOutString,
bool aLinebreaksToo,
int32_t* aCiteLevel)
{
if (aCiteLevel) {
*aCiteLevel = 0;
}
aOutString.Truncate();
nsReadingIterator <char16_t> beginIter,endIter;
aInString.BeginReading(beginIter);
aInString.EndReading(endIter);
while (beginIter!= endIter) { // loop over lines
// Clear out cites first, at the beginning of the line:
int32_t thisLineCiteLevel = 0;
while (beginIter!= endIter &&
(*beginIter == gt || nsCRT::IsAsciiSpace(*beginIter))) {
if (*beginIter == gt) {
++thisLineCiteLevel;
}
++beginIter;
}
// Now copy characters until line end:
while (beginIter != endIter && (*beginIter != '\r' && *beginIter != '\n')) {
aOutString.Append(*beginIter);
++beginIter;
}
if (aLinebreaksToo) {
aOutString.Append(char16_t(' '));
} else {
aOutString.Append(char16_t('\n')); // DOM linebreaks, not NS_LINEBREAK
}
// Skip over any more consecutive linebreak-like characters:
while (beginIter != endIter && (*beginIter == '\r' || *beginIter == '\n')) {
++beginIter;
}
// Done with this line -- update cite level
if (aCiteLevel && (thisLineCiteLevel > *aCiteLevel)) {
*aCiteLevel = thisLineCiteLevel;
}
}
return NS_OK;
}
nsresult
InternetCiter::StripCites(const nsAString& aInString,
nsAString& aOutString)
{
return StripCitesAndLinebreaks(aInString, aOutString, false, 0);
}
static void AddCite(nsAString& aOutString, int32_t citeLevel)
{
for (int32_t i = 0; i < citeLevel; ++i) {
aOutString.Append(gt);
}
if (citeLevel > 0) {
aOutString.Append(space);
}
}
static inline void
BreakLine(nsAString& aOutString, uint32_t& outStringCol,
uint32_t citeLevel)
{
aOutString.Append(nl);
if (citeLevel > 0) {
AddCite(aOutString, citeLevel);
outStringCol = citeLevel + 1;
} else {
outStringCol = 0;
}
}
static inline bool IsSpace(char16_t c)
{
const char16_t nbsp (0xa0);
return (nsCRT::IsAsciiSpace(c) || (c == nl) || (c == cr) || (c == nbsp));
}
nsresult
InternetCiter::Rewrap(const nsAString& aInString,
uint32_t aWrapCol,
uint32_t aFirstLineOffset,
bool aRespectNewlines,
nsAString& aOutString)
{
// There shouldn't be returns in this string, only dom newlines.
// Check to make sure:
#ifdef DEBUG
int32_t cr = aInString.FindChar(char16_t('\r'));
NS_ASSERTION((cr < 0), "Rewrap: CR in string gotten from DOM!\n");
#endif /* DEBUG */
aOutString.Truncate();
nsresult rv;
nsCOMPtr<nsILineBreaker> lineBreaker = do_GetService(NS_LBRK_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Loop over lines in the input string, rewrapping each one.
uint32_t length;
uint32_t posInString = 0;
uint32_t outStringCol = 0;
uint32_t citeLevel = 0;
const nsPromiseFlatString &tString = PromiseFlatString(aInString);
length = tString.Length();
while (posInString < length) {
// Get the new cite level here since we're at the beginning of a line
uint32_t newCiteLevel = 0;
while (posInString < length && tString[posInString] == gt) {
++newCiteLevel;
++posInString;
while (posInString < length && tString[posInString] == space) {
++posInString;
}
}
if (posInString >= length) {
break;
}
// Special case: if this is a blank line, maintain a blank line
// (retain the original paragraph breaks)
if (tString[posInString] == nl && !aOutString.IsEmpty()) {
if (aOutString.Last() != nl) {
aOutString.Append(nl);
}
AddCite(aOutString, newCiteLevel);
aOutString.Append(nl);
++posInString;
outStringCol = 0;
continue;
}
// If the cite level has changed, then start a new line with the
// new cite level (but if we're at the beginning of the string,
// don't bother).
if (newCiteLevel != citeLevel && posInString > newCiteLevel+1 &&
outStringCol) {
BreakLine(aOutString, outStringCol, 0);
}
citeLevel = newCiteLevel;
// Prepend the quote level to the out string if appropriate
if (!outStringCol) {
AddCite(aOutString, citeLevel);
outStringCol = citeLevel + (citeLevel ? 1 : 0);
}
// If it's not a cite, and we're not at the beginning of a line in
// the output string, add a space to separate new text from the
// previous text.
else if (outStringCol > citeLevel) {
aOutString.Append(space);
++outStringCol;
}
// find the next newline -- don't want to go farther than that
int32_t nextNewline = tString.FindChar(nl, posInString);
if (nextNewline < 0) {
nextNewline = length;
}
// For now, don't wrap unquoted lines at all.
// This is because the plaintext edit window has already wrapped them
// by the time we get them for rewrap, yet when we call the line
// breaker, it will refuse to break backwards, and we'll end up
// with a line that's too long and gets displayed as a lone word
// on a line by itself. Need special logic to detect this case
// and break it ourselves without resorting to the line breaker.
if (!citeLevel) {
aOutString.Append(Substring(tString, posInString,
nextNewline-posInString));
outStringCol += nextNewline - posInString;
if (nextNewline != (int32_t)length) {
aOutString.Append(nl);
outStringCol = 0;
}
posInString = nextNewline+1;
continue;
}
// Otherwise we have to use the line breaker and loop
// over this line of the input string to get all of it:
while ((int32_t)posInString < nextNewline) {
// Skip over initial spaces:
while ((int32_t)posInString < nextNewline &&
nsCRT::IsAsciiSpace(tString[posInString])) {
++posInString;
}
// If this is a short line, just append it and continue:
if (outStringCol + nextNewline - posInString <= aWrapCol-citeLevel-1) {
// If this short line is the final one in the in string,
// then we need to include the final newline, if any:
if (nextNewline+1 == (int32_t)length && tString[nextNewline-1] == nl) {
++nextNewline;
}
// Trim trailing spaces:
int32_t lastRealChar = nextNewline;
while ((uint32_t)lastRealChar > posInString &&
nsCRT::IsAsciiSpace(tString[lastRealChar-1])) {
--lastRealChar;
}
aOutString += Substring(tString,
posInString, lastRealChar - posInString);
outStringCol += lastRealChar - posInString;
posInString = nextNewline + 1;
continue;
}
int32_t eol = posInString + aWrapCol - citeLevel - outStringCol;
// eol is the prospective end of line.
// We'll first look backwards from there for a place to break.
// If it's already less than our current position,
// then our line is already too long, so break now.
if (eol <= (int32_t)posInString) {
BreakLine(aOutString, outStringCol, citeLevel);
continue; // continue inner loop, with outStringCol now at bol
}
int32_t breakPt = 0;
// XXX Why this uses NS_ERROR_"BASE"?
rv = NS_ERROR_BASE;
if (lineBreaker) {
breakPt = lineBreaker->Prev(tString.get() + posInString,
length - posInString, eol + 1 - posInString);
if (breakPt == NS_LINEBREAKER_NEED_MORE_TEXT) {
// if we couldn't find a breakpoint looking backwards,
// and we're not starting a new line, then end this line
// and loop around again:
if (outStringCol > citeLevel + 1) {
BreakLine(aOutString, outStringCol, citeLevel);
continue; // continue inner loop, with outStringCol now at bol
}
// Else try looking forwards:
breakPt = lineBreaker->Next(tString.get() + posInString,
length - posInString, eol - posInString);
rv = breakPt == NS_LINEBREAKER_NEED_MORE_TEXT ? NS_ERROR_BASE :
NS_OK;
} else {
rv = NS_OK;
}
}
// If rv is okay, then breakPt is the place to break.
// If we get out here and rv is set, something went wrong with line
// breaker. Just break the line, hard.
if (NS_FAILED(rv)) {
breakPt = eol;
}
// Special case: maybe we should have wrapped last time.
// If the first breakpoint here makes the current line too long,
// then if we already have text on the current line,
// break and loop around again.
// If we're at the beginning of the current line, though,
// don't force a break since the long word might be a url
// and breaking it would make it unclickable on the other end.
const int SLOP = 6;
if (outStringCol + breakPt > aWrapCol + SLOP &&
outStringCol > citeLevel+1) {
BreakLine(aOutString, outStringCol, citeLevel);
continue;
}
nsAutoString sub (Substring(tString, posInString, breakPt));
// skip newlines or whitespace at the end of the string
int32_t subend = sub.Length();
while (subend > 0 && IsSpace(sub[subend-1])) {
--subend;
}
sub.Left(sub, subend);
aOutString += sub;
outStringCol += sub.Length();
// Advance past the whitespace which caused the wrap:
posInString += breakPt;
while (posInString < length && IsSpace(tString[posInString])) {
++posInString;
}
// Add a newline and the quote level to the out string
if (posInString < length) { // not for the last line, though
BreakLine(aOutString, outStringCol, citeLevel);
}
} // end inner loop within one line of aInString
} // end outer loop over lines of aInString
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,40 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef InternetCiter_h
#define InternetCiter_h
#include "nscore.h"
#include "nsStringFwd.h"
namespace mozilla {
/**
* Mail citations using standard Internet style.
*/
class InternetCiter final
{
public:
static nsresult GetCiteString(const nsAString& aInString,
nsAString& aOutString);
static nsresult StripCites(const nsAString& aInString,
nsAString& aOutString);
static nsresult Rewrap(const nsAString& aInString,
uint32_t aWrapCol, uint32_t aFirstLineOffset,
bool aRespectNewlines,
nsAString& aOutString);
protected:
static nsresult StripCitesAndLinebreaks(const nsAString& aInString,
nsAString& aOutString,
bool aLinebreaksToo,
int32_t* aCiteLevel);
};
} // namespace mozilla
#endif // #ifndef InternetCiter_h

View file

@ -0,0 +1,109 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "JoinNodeTransaction.h"
#include "mozilla/EditorBase.h" // for EditorBase
#include "nsAString.h"
#include "nsDebug.h" // for NS_ASSERTION, etc.
#include "nsError.h" // for NS_ERROR_NULL_POINTER, etc.
#include "nsIContent.h" // for nsIContent
#include "nsIDOMCharacterData.h" // for nsIDOMCharacterData
#include "nsIEditor.h" // for EditorBase::IsModifiableNode
#include "nsISupportsImpl.h" // for QueryInterface, etc.
namespace mozilla {
using namespace dom;
JoinNodeTransaction::JoinNodeTransaction(EditorBase& aEditorBase,
nsINode& aLeftNode,
nsINode& aRightNode)
: mEditorBase(aEditorBase)
, mLeftNode(&aLeftNode)
, mRightNode(&aRightNode)
, mOffset(0)
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(JoinNodeTransaction, EditTransactionBase,
mLeftNode,
mRightNode,
mParent)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JoinNodeTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
nsresult
JoinNodeTransaction::CheckValidity()
{
if (!mEditorBase.IsModifiableNode(mLeftNode->GetParentNode())) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
// After DoTransaction() and RedoTransaction(), the left node is removed from
// the content tree and right node remains.
NS_IMETHODIMP
JoinNodeTransaction::DoTransaction()
{
// Get the parent node
nsCOMPtr<nsINode> leftParent = mLeftNode->GetParentNode();
NS_ENSURE_TRUE(leftParent, NS_ERROR_NULL_POINTER);
// Verify that mLeftNode and mRightNode have the same parent
if (leftParent != mRightNode->GetParentNode()) {
NS_ASSERTION(false, "Nodes do not have same parent");
return NS_ERROR_INVALID_ARG;
}
// Set this instance's mParent. Other methods will see a non-null mParent
// and know all is well
mParent = leftParent;
mOffset = mLeftNode->Length();
return mEditorBase.JoinNodesImpl(mRightNode, mLeftNode, mParent);
}
//XXX: What if instead of split, we just deleted the unneeded children of
// mRight and re-inserted mLeft?
NS_IMETHODIMP
JoinNodeTransaction::UndoTransaction()
{
MOZ_ASSERT(mParent);
// First, massage the existing node so it is in its post-split state
ErrorResult rv;
if (mRightNode->GetAsText()) {
rv = mRightNode->GetAsText()->DeleteData(0, mOffset);
} else {
nsCOMPtr<nsIContent> child = mRightNode->GetFirstChild();
for (uint32_t i = 0; i < mOffset; i++) {
if (rv.Failed()) {
return rv.StealNSResult();
}
if (!child) {
return NS_ERROR_NULL_POINTER;
}
nsCOMPtr<nsIContent> nextSibling = child->GetNextSibling();
mLeftNode->AppendChild(*child, rv);
child = nextSibling;
}
}
// Second, re-insert the left node into the tree
nsCOMPtr<nsINode> refNode = mRightNode;
mParent->InsertBefore(*mLeftNode, refNode, rv);
return rv.StealNSResult();
}
NS_IMETHODIMP
JoinNodeTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("JoinNodeTransaction");
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,68 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef JoinNodeTransaction_h
#define JoinNodeTransaction_h
#include "mozilla/EditTransactionBase.h" // for EditTransactionBase, etc.
#include "nsCOMPtr.h" // for nsCOMPtr
#include "nsCycleCollectionParticipant.h"
#include "nsID.h" // for REFNSIID
#include "nscore.h" // for NS_IMETHOD
class nsINode;
namespace mozilla {
class EditorBase;
/**
* A transaction that joins two nodes E1 (left node) and E2 (right node) into a
* single node E. The children of E are the children of E1 followed by the
* children of E2. After DoTransaction() and RedoTransaction(), E1 is removed
* from the content tree and E2 remains.
*/
class JoinNodeTransaction final : public EditTransactionBase
{
public:
/**
* @param aEditorBase The provider of core editing operations.
* @param aLeftNode The first of two nodes to join.
* @param aRightNode The second of two nodes to join.
*/
JoinNodeTransaction(EditorBase& aEditorBase,
nsINode& aLeftNode, nsINode& aRightNode);
/**
* Call this after constructing to ensure the inputs are correct.
*/
nsresult CheckValidity();
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JoinNodeTransaction,
EditTransactionBase)
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
NS_DECL_EDITTRANSACTIONBASE
protected:
EditorBase& mEditorBase;
// The nodes to operate upon. After the merge, mRightNode remains and
// mLeftNode is removed from the content tree.
nsCOMPtr<nsINode> mLeftNode;
nsCOMPtr<nsINode> mRightNode;
// The offset into mNode where the children of mElement are split (for
// undo). mOffset is the index of the first child in the right node. -1
// means the left node had no children.
uint32_t mOffset;
// The parent node containing mLeftNode and mRightNode.
nsCOMPtr<nsINode> mParent;
};
} // namespace mozilla
#endif // #ifndef JoinNodeTransaction_h

View file

@ -0,0 +1,270 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "PlaceholderTransaction.h"
#include "CompositionTransaction.h"
#include "mozilla/EditorBase.h"
#include "mozilla/dom/Selection.h"
#include "nsGkAtoms.h"
#include "nsQueryObject.h"
namespace mozilla {
using namespace dom;
PlaceholderTransaction::PlaceholderTransaction()
: mAbsorb(true)
, mForwarding(nullptr)
, mCompositionTransaction(nullptr)
, mCommitted(false)
, mEditorBase(nullptr)
{
}
PlaceholderTransaction::~PlaceholderTransaction()
{
}
NS_IMPL_CYCLE_COLLECTION_CLASS(PlaceholderTransaction)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PlaceholderTransaction,
EditAggregateTransaction)
if (tmp->mStartSel) {
ImplCycleCollectionUnlink(*tmp->mStartSel);
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSel);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PlaceholderTransaction,
EditAggregateTransaction)
if (tmp->mStartSel) {
ImplCycleCollectionTraverse(cb, *tmp->mStartSel, "mStartSel", 0);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSel);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PlaceholderTransaction)
NS_INTERFACE_MAP_ENTRY(nsIAbsorbingTransaction)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
NS_IMPL_ADDREF_INHERITED(PlaceholderTransaction, EditAggregateTransaction)
NS_IMPL_RELEASE_INHERITED(PlaceholderTransaction, EditAggregateTransaction)
NS_IMETHODIMP
PlaceholderTransaction::Init(nsIAtom* aName,
SelectionState* aSelState,
EditorBase* aEditorBase)
{
NS_ENSURE_TRUE(aEditorBase && aSelState, NS_ERROR_NULL_POINTER);
mName = aName;
mStartSel = aSelState;
mEditorBase = aEditorBase;
return NS_OK;
}
NS_IMETHODIMP
PlaceholderTransaction::DoTransaction()
{
return NS_OK;
}
NS_IMETHODIMP
PlaceholderTransaction::UndoTransaction()
{
// Undo transactions.
nsresult rv = EditAggregateTransaction::UndoTransaction();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(mStartSel, NS_ERROR_NULL_POINTER);
// now restore selection
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
return mStartSel->RestoreSelection(selection);
}
NS_IMETHODIMP
PlaceholderTransaction::RedoTransaction()
{
// Redo transactions.
nsresult rv = EditAggregateTransaction::RedoTransaction();
NS_ENSURE_SUCCESS(rv, rv);
// now restore selection
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
return mEndSel.RestoreSelection(selection);
}
NS_IMETHODIMP
PlaceholderTransaction::Merge(nsITransaction* aTransaction,
bool* aDidMerge)
{
NS_ENSURE_TRUE(aDidMerge && aTransaction, NS_ERROR_NULL_POINTER);
// set out param default value
*aDidMerge=false;
if (mForwarding) {
NS_NOTREACHED("tried to merge into a placeholder that was in forwarding mode!");
return NS_ERROR_FAILURE;
}
// check to see if aTransaction is one of the editor's
// private transactions. If not, we want to avoid merging
// the foreign transaction into our placeholder since we
// don't know what it does.
nsCOMPtr<nsPIEditorTransaction> pTxn = do_QueryInterface(aTransaction);
NS_ENSURE_TRUE(pTxn, NS_OK); // it's foreign so just bail!
// XXX: hack, not safe! need nsIEditTransaction!
EditTransactionBase* editTransactionBase = (EditTransactionBase*)aTransaction;
// determine if this incoming txn is a placeholder txn
nsCOMPtr<nsIAbsorbingTransaction> absorbingTransaction =
do_QueryObject(editTransactionBase);
// We are absorbing all transactions if mAbsorb is lit.
if (mAbsorb) {
RefPtr<CompositionTransaction> otherTransaction =
do_QueryObject(aTransaction);
if (otherTransaction) {
// special handling for CompositionTransaction's: they need to merge with
// any previous CompositionTransaction in this placeholder, if possible.
if (!mCompositionTransaction) {
// this is the first IME txn in the placeholder
mCompositionTransaction = otherTransaction;
AppendChild(editTransactionBase);
} else {
bool didMerge;
mCompositionTransaction->Merge(otherTransaction, &didMerge);
if (!didMerge) {
// it wouldn't merge. Earlier IME txn is already committed and will
// not absorb further IME txns. So just stack this one after it
// and remember it as a candidate for further merges.
mCompositionTransaction = otherTransaction;
AppendChild(editTransactionBase);
}
}
} else if (!absorbingTransaction) {
// See bug 171243: just drop incoming placeholders on the floor.
// Their children will be swallowed by this preexisting one.
AppendChild(editTransactionBase);
}
*aDidMerge = true;
// RememberEndingSelection();
// efficiency hack: no need to remember selection here, as we haven't yet
// finished the initial batch and we know we will be told when the batch ends.
// we can remeber the selection then.
} else {
// merge typing or IME or deletion transactions if the selection matches
if ((mName.get() == nsGkAtoms::TypingTxnName ||
mName.get() == nsGkAtoms::IMETxnName ||
mName.get() == nsGkAtoms::DeleteTxnName) && !mCommitted) {
if (absorbingTransaction) {
nsCOMPtr<nsIAtom> atom;
absorbingTransaction->GetTxnName(getter_AddRefs(atom));
if (atom && atom == mName) {
// check if start selection of next placeholder matches
// end selection of this placeholder
bool isSame;
absorbingTransaction->StartSelectionEquals(&mEndSel, &isSame);
if (isSame) {
mAbsorb = true; // we need to start absorbing again
absorbingTransaction->ForwardEndBatchTo(this);
// AppendChild(editTransactionBase);
// see bug 171243: we don't need to merge placeholders
// into placeholders. We just reactivate merging in the pre-existing
// placeholder and drop the new one on the floor. The EndPlaceHolderBatch()
// call on the new placeholder will be forwarded to this older one.
RememberEndingSelection();
*aDidMerge = true;
}
}
}
}
}
return NS_OK;
}
NS_IMETHODIMP
PlaceholderTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("PlaceholderTransaction: ");
if (mName) {
nsAutoString name;
mName->ToString(name);
aString += name;
}
return NS_OK;
}
NS_IMETHODIMP
PlaceholderTransaction::GetTxnName(nsIAtom** aName)
{
return GetName(aName);
}
NS_IMETHODIMP
PlaceholderTransaction::StartSelectionEquals(SelectionState* aSelState,
bool* aResult)
{
// determine if starting selection matches the given selection state.
// note that we only care about collapsed selections.
NS_ENSURE_TRUE(aResult && aSelState, NS_ERROR_NULL_POINTER);
if (!mStartSel->IsCollapsed() || !aSelState->IsCollapsed()) {
*aResult = false;
return NS_OK;
}
*aResult = mStartSel->IsEqual(aSelState);
return NS_OK;
}
NS_IMETHODIMP
PlaceholderTransaction::EndPlaceHolderBatch()
{
mAbsorb = false;
if (mForwarding) {
nsCOMPtr<nsIAbsorbingTransaction> plcTxn = do_QueryReferent(mForwarding);
if (plcTxn) {
plcTxn->EndPlaceHolderBatch();
}
}
// remember our selection state.
return RememberEndingSelection();
}
NS_IMETHODIMP
PlaceholderTransaction::ForwardEndBatchTo(
nsIAbsorbingTransaction* aForwardingAddress)
{
mForwarding = do_GetWeakReference(aForwardingAddress);
return NS_OK;
}
NS_IMETHODIMP
PlaceholderTransaction::Commit()
{
mCommitted = true;
return NS_OK;
}
nsresult
PlaceholderTransaction::RememberEndingSelection()
{
RefPtr<Selection> selection = mEditorBase->GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
mEndSel.SaveSelection(selection);
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,92 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef PlaceholderTransaction_h
#define PlaceholderTransaction_h
#include "EditAggregateTransaction.h"
#include "mozilla/EditorUtils.h"
#include "nsIAbsorbingTransaction.h"
#include "nsIDOMNode.h"
#include "nsCOMPtr.h"
#include "nsWeakPtr.h"
#include "nsWeakReference.h"
#include "nsAutoPtr.h"
namespace mozilla {
class CompositionTransaction;
/**
* An aggregate transaction that knows how to absorb all subsequent
* transactions with the same name. This transaction does not "Do" anything.
* But it absorbs other transactions via merge, and can undo/redo the
* transactions it has absorbed.
*/
class PlaceholderTransaction final : public EditAggregateTransaction,
public nsIAbsorbingTransaction,
public nsSupportsWeakReference
{
public:
NS_DECL_ISUPPORTS_INHERITED
PlaceholderTransaction();
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PlaceholderTransaction,
EditAggregateTransaction)
// ------------ EditAggregateTransaction -----------------------
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD RedoTransaction() override;
NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aDidMerge) override;
// ------------ nsIAbsorbingTransaction -----------------------
NS_IMETHOD Init(nsIAtom* aName, SelectionState* aSelState,
EditorBase* aEditorBase) override;
NS_IMETHOD GetTxnName(nsIAtom** aName) override;
NS_IMETHOD StartSelectionEquals(SelectionState* aSelState,
bool* aResult) override;
NS_IMETHOD EndPlaceHolderBatch() override;
NS_IMETHOD ForwardEndBatchTo(
nsIAbsorbingTransaction* aForwardingAddress) override;
NS_IMETHOD Commit() override;
nsresult RememberEndingSelection();
protected:
virtual ~PlaceholderTransaction();
// Do we auto absorb any and all transaction?
bool mAbsorb;
nsWeakPtr mForwarding;
// First IME txn in this placeholder - used for IME merging.
mozilla::CompositionTransaction* mCompositionTransaction;
// Do we stop auto absorbing any matching placeholder transactions?
bool mCommitted;
// These next two members store the state of the selection in a safe way.
// Selection at the start of the transaction is stored, as is the selection
// at the end. This is so that UndoTransaction() and RedoTransaction() can
// restore the selection properly.
// Use a pointer because this is constructed before we exist.
nsAutoPtr<SelectionState> mStartSel;
SelectionState mEndSel;
// The editor for this transaction.
EditorBase* mEditorBase;
};
} // namespace mozilla
#endif // #ifndef PlaceholderTransaction_h

View file

@ -0,0 +1,695 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/SelectionState.h"
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
#include "mozilla/EditorUtils.h" // for EditorUtils
#include "mozilla/dom/Selection.h" // for Selection
#include "nsAString.h" // for nsAString_internal::Length
#include "nsCycleCollectionParticipant.h"
#include "nsDebug.h" // for NS_ENSURE_TRUE, etc.
#include "nsError.h" // for NS_OK, etc.
#include "nsIContent.h" // for nsIContent
#include "nsIDOMCharacterData.h" // for nsIDOMCharacterData
#include "nsIDOMNode.h" // for nsIDOMNode
#include "nsISupportsImpl.h" // for nsRange::Release
#include "nsRange.h" // for nsRange
namespace mozilla {
using namespace dom;
/******************************************************************************
* mozilla::SelectionState
*
* Class for recording selection info. Stores selection as collection of
* { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store
* ranges since dom gravity will possibly change the ranges.
******************************************************************************/
SelectionState::SelectionState()
{
}
SelectionState::~SelectionState()
{
MakeEmpty();
}
void
SelectionState::SaveSelection(Selection* aSel)
{
MOZ_ASSERT(aSel);
int32_t arrayCount = mArray.Length();
int32_t rangeCount = aSel->RangeCount();
// if we need more items in the array, new them
if (arrayCount < rangeCount) {
for (int32_t i = arrayCount; i < rangeCount; i++) {
mArray.AppendElement();
mArray[i] = new RangeItem();
}
} else if (arrayCount > rangeCount) {
// else if we have too many, delete them
for (int32_t i = arrayCount - 1; i >= rangeCount; i--) {
mArray.RemoveElementAt(i);
}
}
// now store the selection ranges
for (int32_t i = 0; i < rangeCount; i++) {
mArray[i]->StoreRange(aSel->GetRangeAt(i));
}
}
nsresult
SelectionState::RestoreSelection(Selection* aSel)
{
NS_ENSURE_TRUE(aSel, NS_ERROR_NULL_POINTER);
// clear out selection
aSel->RemoveAllRanges();
// set the selection ranges anew
size_t arrayCount = mArray.Length();
for (size_t i = 0; i < arrayCount; i++) {
RefPtr<nsRange> range = mArray[i]->GetRange();
NS_ENSURE_TRUE(range, NS_ERROR_UNEXPECTED);
nsresult rv = aSel->AddRange(range);
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
bool
SelectionState::IsCollapsed()
{
if (mArray.Length() != 1) {
return false;
}
RefPtr<nsRange> range = mArray[0]->GetRange();
NS_ENSURE_TRUE(range, false);
bool bIsCollapsed = false;
range->GetCollapsed(&bIsCollapsed);
return bIsCollapsed;
}
bool
SelectionState::IsEqual(SelectionState* aSelState)
{
NS_ENSURE_TRUE(aSelState, false);
size_t myCount = mArray.Length(), itsCount = aSelState->mArray.Length();
if (myCount != itsCount) {
return false;
}
if (!myCount) {
return false;
}
for (size_t i = 0; i < myCount; i++) {
RefPtr<nsRange> myRange = mArray[i]->GetRange();
RefPtr<nsRange> itsRange = aSelState->mArray[i]->GetRange();
NS_ENSURE_TRUE(myRange && itsRange, false);
int16_t compResult;
nsresult rv;
rv = myRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, itsRange, &compResult);
if (NS_FAILED(rv) || compResult) {
return false;
}
rv = myRange->CompareBoundaryPoints(nsIDOMRange::END_TO_END, itsRange, &compResult);
if (NS_FAILED(rv) || compResult) {
return false;
}
}
// if we got here, they are equal
return true;
}
void
SelectionState::MakeEmpty()
{
// free any items in the array
mArray.Clear();
}
bool
SelectionState::IsEmpty()
{
return mArray.IsEmpty();
}
/******************************************************************************
* mozilla::RangeUpdater
*
* Class for updating nsRanges in response to editor actions.
******************************************************************************/
RangeUpdater::RangeUpdater()
: mLock(false)
{
}
RangeUpdater::~RangeUpdater()
{
// nothing to do, we don't own the items in our array.
}
void
RangeUpdater::RegisterRangeItem(RangeItem* aRangeItem)
{
if (!aRangeItem) {
return;
}
if (mArray.Contains(aRangeItem)) {
NS_ERROR("tried to register an already registered range");
return; // don't register it again. It would get doubly adjusted.
}
mArray.AppendElement(aRangeItem);
}
void
RangeUpdater::DropRangeItem(RangeItem* aRangeItem)
{
if (!aRangeItem) {
return;
}
mArray.RemoveElement(aRangeItem);
}
nsresult
RangeUpdater::RegisterSelectionState(SelectionState& aSelState)
{
size_t theCount = aSelState.mArray.Length();
if (theCount < 1) {
return NS_ERROR_FAILURE;
}
for (size_t i = 0; i < theCount; i++) {
RegisterRangeItem(aSelState.mArray[i]);
}
return NS_OK;
}
nsresult
RangeUpdater::DropSelectionState(SelectionState& aSelState)
{
size_t theCount = aSelState.mArray.Length();
if (theCount < 1) {
return NS_ERROR_FAILURE;
}
for (size_t i = 0; i < theCount; i++) {
DropRangeItem(aSelState.mArray[i]);
}
return NS_OK;
}
// gravity methods:
nsresult
RangeUpdater::SelAdjCreateNode(nsINode* aParent,
int32_t aPosition)
{
if (mLock) {
// lock set by Will/DidReplaceParent, etc...
return NS_OK;
}
NS_ENSURE_TRUE(aParent, NS_ERROR_NULL_POINTER);
size_t count = mArray.Length();
if (!count) {
return NS_OK;
}
for (size_t i = 0; i < count; i++) {
RangeItem* item = mArray[i];
NS_ENSURE_TRUE(item, NS_ERROR_NULL_POINTER);
if (item->startNode == aParent && item->startOffset > aPosition) {
item->startOffset++;
}
if (item->endNode == aParent && item->endOffset > aPosition) {
item->endOffset++;
}
}
return NS_OK;
}
nsresult
RangeUpdater::SelAdjCreateNode(nsIDOMNode* aParent,
int32_t aPosition)
{
nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
return SelAdjCreateNode(parent, aPosition);
}
nsresult
RangeUpdater::SelAdjInsertNode(nsINode* aParent,
int32_t aPosition)
{
return SelAdjCreateNode(aParent, aPosition);
}
nsresult
RangeUpdater::SelAdjInsertNode(nsIDOMNode* aParent,
int32_t aPosition)
{
return SelAdjCreateNode(aParent, aPosition);
}
void
RangeUpdater::SelAdjDeleteNode(nsINode* aNode)
{
if (mLock) {
// lock set by Will/DidReplaceParent, etc...
return;
}
MOZ_ASSERT(aNode);
size_t count = mArray.Length();
if (!count) {
return;
}
nsCOMPtr<nsINode> parent = aNode->GetParentNode();
int32_t offset = parent ? parent->IndexOf(aNode) : -1;
// check for range endpoints that are after aNode and in the same parent
for (size_t i = 0; i < count; i++) {
RangeItem* item = mArray[i];
MOZ_ASSERT(item);
if (item->startNode == parent && item->startOffset > offset) {
item->startOffset--;
}
if (item->endNode == parent && item->endOffset > offset) {
item->endOffset--;
}
// check for range endpoints that are in aNode
if (item->startNode == aNode) {
item->startNode = parent;
item->startOffset = offset;
}
if (item->endNode == aNode) {
item->endNode = parent;
item->endOffset = offset;
}
// check for range endpoints that are in descendants of aNode
nsCOMPtr<nsINode> oldStart;
if (EditorUtils::IsDescendantOf(item->startNode, aNode)) {
oldStart = item->startNode; // save for efficiency hack below.
item->startNode = parent;
item->startOffset = offset;
}
// avoid having to call IsDescendantOf() for common case of range startnode == range endnode.
if (item->endNode == oldStart ||
EditorUtils::IsDescendantOf(item->endNode, aNode)) {
item->endNode = parent;
item->endOffset = offset;
}
}
}
void
RangeUpdater::SelAdjDeleteNode(nsIDOMNode* aNode)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
NS_ENSURE_TRUE_VOID(node);
return SelAdjDeleteNode(node);
}
nsresult
RangeUpdater::SelAdjSplitNode(nsIContent& aOldRightNode,
int32_t aOffset,
nsIContent* aNewLeftNode)
{
if (mLock) {
// lock set by Will/DidReplaceParent, etc...
return NS_OK;
}
NS_ENSURE_TRUE(aNewLeftNode, NS_ERROR_NULL_POINTER);
size_t count = mArray.Length();
if (!count) {
return NS_OK;
}
nsCOMPtr<nsINode> parent = aOldRightNode.GetParentNode();
int32_t offset = parent ? parent->IndexOf(&aOldRightNode) : -1;
// first part is same as inserting aNewLeftnode
nsresult rv = SelAdjInsertNode(parent, offset - 1);
NS_ENSURE_SUCCESS(rv, rv);
// next step is to check for range enpoints inside aOldRightNode
for (size_t i = 0; i < count; i++) {
RangeItem* item = mArray[i];
NS_ENSURE_TRUE(item, NS_ERROR_NULL_POINTER);
if (item->startNode == &aOldRightNode) {
if (item->startOffset > aOffset) {
item->startOffset -= aOffset;
} else {
item->startNode = aNewLeftNode;
}
}
if (item->endNode == &aOldRightNode) {
if (item->endOffset > aOffset) {
item->endOffset -= aOffset;
} else {
item->endNode = aNewLeftNode;
}
}
}
return NS_OK;
}
nsresult
RangeUpdater::SelAdjJoinNodes(nsINode& aLeftNode,
nsINode& aRightNode,
nsINode& aParent,
int32_t aOffset,
int32_t aOldLeftNodeLength)
{
if (mLock) {
// lock set by Will/DidReplaceParent, etc...
return NS_OK;
}
size_t count = mArray.Length();
if (!count) {
return NS_OK;
}
for (size_t i = 0; i < count; i++) {
RangeItem* item = mArray[i];
NS_ENSURE_TRUE(item, NS_ERROR_NULL_POINTER);
if (item->startNode == &aParent) {
// adjust start point in aParent
if (item->startOffset > aOffset) {
item->startOffset--;
} else if (item->startOffset == aOffset) {
// join keeps right hand node
item->startNode = &aRightNode;
item->startOffset = aOldLeftNodeLength;
}
} else if (item->startNode == &aRightNode) {
// adjust start point in aRightNode
item->startOffset += aOldLeftNodeLength;
} else if (item->startNode == &aLeftNode) {
// adjust start point in aLeftNode
item->startNode = &aRightNode;
}
if (item->endNode == &aParent) {
// adjust end point in aParent
if (item->endOffset > aOffset) {
item->endOffset--;
} else if (item->endOffset == aOffset) {
// join keeps right hand node
item->endNode = &aRightNode;
item->endOffset = aOldLeftNodeLength;
}
} else if (item->endNode == &aRightNode) {
// adjust end point in aRightNode
item->endOffset += aOldLeftNodeLength;
} else if (item->endNode == &aLeftNode) {
// adjust end point in aLeftNode
item->endNode = &aRightNode;
}
}
return NS_OK;
}
void
RangeUpdater::SelAdjInsertText(Text& aTextNode,
int32_t aOffset,
const nsAString& aString)
{
if (mLock) {
// lock set by Will/DidReplaceParent, etc...
return;
}
size_t count = mArray.Length();
if (!count) {
return;
}
size_t len = aString.Length();
for (size_t i = 0; i < count; i++) {
RangeItem* item = mArray[i];
MOZ_ASSERT(item);
if (item->startNode == &aTextNode && item->startOffset > aOffset) {
item->startOffset += len;
}
if (item->endNode == &aTextNode && item->endOffset > aOffset) {
item->endOffset += len;
}
}
return;
}
nsresult
RangeUpdater::SelAdjDeleteText(nsIContent* aTextNode,
int32_t aOffset,
int32_t aLength)
{
if (mLock) {
// lock set by Will/DidReplaceParent, etc...
return NS_OK;
}
size_t count = mArray.Length();
if (!count) {
return NS_OK;
}
NS_ENSURE_TRUE(aTextNode, NS_ERROR_NULL_POINTER);
for (size_t i = 0; i < count; i++) {
RangeItem* item = mArray[i];
NS_ENSURE_TRUE(item, NS_ERROR_NULL_POINTER);
if (item->startNode == aTextNode && item->startOffset > aOffset) {
item->startOffset -= aLength;
if (item->startOffset < 0) {
item->startOffset = 0;
}
}
if (item->endNode == aTextNode && item->endOffset > aOffset) {
item->endOffset -= aLength;
if (item->endOffset < 0) {
item->endOffset = 0;
}
}
}
return NS_OK;
}
nsresult
RangeUpdater::SelAdjDeleteText(nsIDOMCharacterData* aTextNode,
int32_t aOffset,
int32_t aLength)
{
nsCOMPtr<nsIContent> textNode = do_QueryInterface(aTextNode);
return SelAdjDeleteText(textNode, aOffset, aLength);
}
nsresult
RangeUpdater::WillReplaceContainer()
{
if (mLock) {
return NS_ERROR_UNEXPECTED;
}
mLock = true;
return NS_OK;
}
nsresult
RangeUpdater::DidReplaceContainer(Element* aOriginalNode,
Element* aNewNode)
{
NS_ENSURE_TRUE(mLock, NS_ERROR_UNEXPECTED);
mLock = false;
NS_ENSURE_TRUE(aOriginalNode && aNewNode, NS_ERROR_NULL_POINTER);
size_t count = mArray.Length();
if (!count) {
return NS_OK;
}
for (size_t i = 0; i < count; i++) {
RangeItem* item = mArray[i];
NS_ENSURE_TRUE(item, NS_ERROR_NULL_POINTER);
if (item->startNode == aOriginalNode) {
item->startNode = aNewNode;
}
if (item->endNode == aOriginalNode) {
item->endNode = aNewNode;
}
}
return NS_OK;
}
nsresult
RangeUpdater::WillRemoveContainer()
{
if (mLock) {
return NS_ERROR_UNEXPECTED;
}
mLock = true;
return NS_OK;
}
nsresult
RangeUpdater::DidRemoveContainer(nsINode* aNode,
nsINode* aParent,
int32_t aOffset,
uint32_t aNodeOrigLen)
{
NS_ENSURE_TRUE(mLock, NS_ERROR_UNEXPECTED);
mLock = false;
NS_ENSURE_TRUE(aNode && aParent, NS_ERROR_NULL_POINTER);
size_t count = mArray.Length();
if (!count) {
return NS_OK;
}
for (size_t i = 0; i < count; i++) {
RangeItem* item = mArray[i];
NS_ENSURE_TRUE(item, NS_ERROR_NULL_POINTER);
if (item->startNode == aNode) {
item->startNode = aParent;
item->startOffset += aOffset;
} else if (item->startNode == aParent && item->startOffset > aOffset) {
item->startOffset += (int32_t)aNodeOrigLen - 1;
}
if (item->endNode == aNode) {
item->endNode = aParent;
item->endOffset += aOffset;
} else if (item->endNode == aParent && item->endOffset > aOffset) {
item->endOffset += (int32_t)aNodeOrigLen - 1;
}
}
return NS_OK;
}
nsresult
RangeUpdater::DidRemoveContainer(nsIDOMNode* aNode,
nsIDOMNode* aParent,
int32_t aOffset,
uint32_t aNodeOrigLen)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
return DidRemoveContainer(node, parent, aOffset, aNodeOrigLen);
}
nsresult
RangeUpdater::WillInsertContainer()
{
if (mLock) {
return NS_ERROR_UNEXPECTED;
}
mLock = true;
return NS_OK;
}
nsresult
RangeUpdater::DidInsertContainer()
{
NS_ENSURE_TRUE(mLock, NS_ERROR_UNEXPECTED);
mLock = false;
return NS_OK;
}
void
RangeUpdater::WillMoveNode()
{
mLock = true;
}
void
RangeUpdater::DidMoveNode(nsINode* aOldParent, int32_t aOldOffset,
nsINode* aNewParent, int32_t aNewOffset)
{
MOZ_ASSERT(aOldParent);
MOZ_ASSERT(aNewParent);
NS_ENSURE_TRUE_VOID(mLock);
mLock = false;
for (size_t i = 0, count = mArray.Length(); i < count; ++i) {
RangeItem* item = mArray[i];
NS_ENSURE_TRUE_VOID(item);
// like a delete in aOldParent
if (item->startNode == aOldParent && item->startOffset > aOldOffset) {
item->startOffset--;
}
if (item->endNode == aOldParent && item->endOffset > aOldOffset) {
item->endOffset--;
}
// and like an insert in aNewParent
if (item->startNode == aNewParent && item->startOffset > aNewOffset) {
item->startOffset++;
}
if (item->endNode == aNewParent && item->endOffset > aNewOffset) {
item->endOffset++;
}
}
}
/******************************************************************************
* mozilla::RangeItem
*
* Helper struct for SelectionState. This stores range endpoints.
******************************************************************************/
RangeItem::RangeItem()
{
}
RangeItem::~RangeItem()
{
}
NS_IMPL_CYCLE_COLLECTION(RangeItem, startNode, endNode)
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(RangeItem, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(RangeItem, Release)
void
RangeItem::StoreRange(nsRange* aRange)
{
MOZ_ASSERT(aRange);
startNode = aRange->GetStartParent();
startOffset = aRange->StartOffset();
endNode = aRange->GetEndParent();
endOffset = aRange->EndOffset();
}
already_AddRefed<nsRange>
RangeItem::GetRange()
{
RefPtr<nsRange> range = new nsRange(startNode);
if (NS_FAILED(range->Set(startNode, startOffset, endNode, endOffset))) {
return nullptr;
}
return range.forget();
}
} // namespace mozilla

View file

@ -0,0 +1,357 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_SelectionState_h
#define mozilla_SelectionState_h
#include "nsCOMPtr.h"
#include "nsIDOMNode.h"
#include "nsINode.h"
#include "nsTArray.h"
#include "nscore.h"
class nsCycleCollectionTraversalCallback;
class nsIDOMCharacterData;
class nsRange;
namespace mozilla {
class RangeUpdater;
namespace dom {
class Selection;
class Text;
} // namespace dom
/**
* A helper struct for saving/setting ranges.
*/
struct RangeItem final
{
RangeItem();
private:
// Private destructor, to discourage deletion outside of Release():
~RangeItem();
public:
void StoreRange(nsRange* aRange);
already_AddRefed<nsRange> GetRange();
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(RangeItem)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(RangeItem)
nsCOMPtr<nsINode> startNode;
int32_t startOffset;
nsCOMPtr<nsINode> endNode;
int32_t endOffset;
};
/**
* mozilla::SelectionState
*
* Class for recording selection info. Stores selection as collection of
* { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store
* ranges since dom gravity will possibly change the ranges.
*/
class SelectionState final
{
public:
SelectionState();
~SelectionState();
void SaveSelection(dom::Selection *aSel);
nsresult RestoreSelection(dom::Selection* aSel);
bool IsCollapsed();
bool IsEqual(SelectionState *aSelState);
void MakeEmpty();
bool IsEmpty();
private:
nsTArray<RefPtr<RangeItem>> mArray;
friend class RangeUpdater;
friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
SelectionState&,
const char*,
uint32_t);
friend void ImplCycleCollectionUnlink(SelectionState&);
};
inline void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
SelectionState& aField,
const char* aName,
uint32_t aFlags = 0)
{
ImplCycleCollectionTraverse(aCallback, aField.mArray, aName, aFlags);
}
inline void
ImplCycleCollectionUnlink(SelectionState& aField)
{
ImplCycleCollectionUnlink(aField.mArray);
}
class RangeUpdater final
{
public:
RangeUpdater();
~RangeUpdater();
void RegisterRangeItem(RangeItem* aRangeItem);
void DropRangeItem(RangeItem* aRangeItem);
nsresult RegisterSelectionState(SelectionState& aSelState);
nsresult DropSelectionState(SelectionState& aSelState);
// editor selection gravity routines. Note that we can't always depend on
// DOM Range gravity to do what we want to the "real" selection. For instance,
// if you move a node, that corresponds to deleting it and reinserting it.
// DOM Range gravity will promote the selection out of the node on deletion,
// which is not what you want if you know you are reinserting it.
nsresult SelAdjCreateNode(nsINode* aParent, int32_t aPosition);
nsresult SelAdjCreateNode(nsIDOMNode* aParent, int32_t aPosition);
nsresult SelAdjInsertNode(nsINode* aParent, int32_t aPosition);
nsresult SelAdjInsertNode(nsIDOMNode* aParent, int32_t aPosition);
void SelAdjDeleteNode(nsINode* aNode);
void SelAdjDeleteNode(nsIDOMNode* aNode);
nsresult SelAdjSplitNode(nsIContent& aOldRightNode, int32_t aOffset,
nsIContent* aNewLeftNode);
nsresult SelAdjJoinNodes(nsINode& aLeftNode,
nsINode& aRightNode,
nsINode& aParent,
int32_t aOffset,
int32_t aOldLeftNodeLength);
void SelAdjInsertText(dom::Text& aTextNode, int32_t aOffset,
const nsAString &aString);
nsresult SelAdjDeleteText(nsIContent* aTextNode, int32_t aOffset,
int32_t aLength);
nsresult SelAdjDeleteText(nsIDOMCharacterData* aTextNode,
int32_t aOffset, int32_t aLength);
// the following gravity routines need will/did sandwiches, because the other
// gravity routines will be called inside of these sandwiches, but should be
// ignored.
nsresult WillReplaceContainer();
nsresult DidReplaceContainer(dom::Element* aOriginalNode,
dom::Element* aNewNode);
nsresult WillRemoveContainer();
nsresult DidRemoveContainer(nsINode* aNode, nsINode* aParent,
int32_t aOffset, uint32_t aNodeOrigLen);
nsresult DidRemoveContainer(nsIDOMNode* aNode, nsIDOMNode* aParent,
int32_t aOffset, uint32_t aNodeOrigLen);
nsresult WillInsertContainer();
nsresult DidInsertContainer();
void WillMoveNode();
void DidMoveNode(nsINode* aOldParent, int32_t aOldOffset,
nsINode* aNewParent, int32_t aNewOffset);
private:
friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
RangeUpdater&,
const char*,
uint32_t);
friend void ImplCycleCollectionUnlink(RangeUpdater& aField);
nsTArray<RefPtr<RangeItem>> mArray;
bool mLock;
};
inline void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
RangeUpdater& aField,
const char* aName,
uint32_t aFlags = 0)
{
ImplCycleCollectionTraverse(aCallback, aField.mArray, aName, aFlags);
}
inline void
ImplCycleCollectionUnlink(RangeUpdater& aField)
{
ImplCycleCollectionUnlink(aField.mArray);
}
/**
* Helper class for using SelectionState. Stack based class for doing
* preservation of dom points across editor actions.
*/
class MOZ_STACK_CLASS AutoTrackDOMPoint final
{
private:
RangeUpdater& mRangeUpdater;
// Allow tracking either nsIDOMNode or nsINode until nsIDOMNode is gone
nsCOMPtr<nsINode>* mNode;
nsCOMPtr<nsIDOMNode>* mDOMNode;
int32_t* mOffset;
RefPtr<RangeItem> mRangeItem;
public:
AutoTrackDOMPoint(RangeUpdater& aRangeUpdater,
nsCOMPtr<nsINode>* aNode, int32_t* aOffset)
: mRangeUpdater(aRangeUpdater)
, mNode(aNode)
, mDOMNode(nullptr)
, mOffset(aOffset)
{
mRangeItem = new RangeItem();
mRangeItem->startNode = *mNode;
mRangeItem->endNode = *mNode;
mRangeItem->startOffset = *mOffset;
mRangeItem->endOffset = *mOffset;
mRangeUpdater.RegisterRangeItem(mRangeItem);
}
AutoTrackDOMPoint(RangeUpdater& aRangeUpdater,
nsCOMPtr<nsIDOMNode>* aNode, int32_t* aOffset)
: mRangeUpdater(aRangeUpdater)
, mNode(nullptr)
, mDOMNode(aNode)
, mOffset(aOffset)
{
mRangeItem = new RangeItem();
mRangeItem->startNode = do_QueryInterface(*mDOMNode);
mRangeItem->endNode = do_QueryInterface(*mDOMNode);
mRangeItem->startOffset = *mOffset;
mRangeItem->endOffset = *mOffset;
mRangeUpdater.RegisterRangeItem(mRangeItem);
}
~AutoTrackDOMPoint()
{
mRangeUpdater.DropRangeItem(mRangeItem);
if (mNode) {
*mNode = mRangeItem->startNode;
} else {
*mDOMNode = GetAsDOMNode(mRangeItem->startNode);
}
*mOffset = mRangeItem->startOffset;
}
};
/**
* Another helper class for SelectionState. Stack based class for doing
* Will/DidReplaceContainer()
*/
class MOZ_STACK_CLASS AutoReplaceContainerSelNotify final
{
private:
RangeUpdater& mRangeUpdater;
dom::Element* mOriginalElement;
dom::Element* mNewElement;
public:
AutoReplaceContainerSelNotify(RangeUpdater& aRangeUpdater,
dom::Element* aOriginalElement,
dom::Element* aNewElement)
: mRangeUpdater(aRangeUpdater)
, mOriginalElement(aOriginalElement)
, mNewElement(aNewElement)
{
mRangeUpdater.WillReplaceContainer();
}
~AutoReplaceContainerSelNotify()
{
mRangeUpdater.DidReplaceContainer(mOriginalElement, mNewElement);
}
};
/**
* Another helper class for SelectionState. Stack based class for doing
* Will/DidRemoveContainer()
*/
class MOZ_STACK_CLASS AutoRemoveContainerSelNotify final
{
private:
RangeUpdater& mRangeUpdater;
nsIDOMNode* mNode;
nsIDOMNode* mParent;
int32_t mOffset;
uint32_t mNodeOrigLen;
public:
AutoRemoveContainerSelNotify(RangeUpdater& aRangeUpdater,
nsINode* aNode,
nsINode* aParent,
int32_t aOffset,
uint32_t aNodeOrigLen)
: mRangeUpdater(aRangeUpdater)
, mNode(aNode->AsDOMNode())
, mParent(aParent->AsDOMNode())
, mOffset(aOffset)
, mNodeOrigLen(aNodeOrigLen)
{
mRangeUpdater.WillRemoveContainer();
}
~AutoRemoveContainerSelNotify()
{
mRangeUpdater.DidRemoveContainer(mNode, mParent, mOffset, mNodeOrigLen);
}
};
/**
* Another helper class for SelectionState. Stack based class for doing
* Will/DidInsertContainer()
*/
class MOZ_STACK_CLASS AutoInsertContainerSelNotify final
{
private:
RangeUpdater& mRangeUpdater;
public:
explicit AutoInsertContainerSelNotify(RangeUpdater& aRangeUpdater)
: mRangeUpdater(aRangeUpdater)
{
mRangeUpdater.WillInsertContainer();
}
~AutoInsertContainerSelNotify()
{
mRangeUpdater.DidInsertContainer();
}
};
/**
* Another helper class for SelectionState. Stack based class for doing
* Will/DidMoveNode()
*/
class MOZ_STACK_CLASS AutoMoveNodeSelNotify final
{
private:
RangeUpdater& mRangeUpdater;
nsINode* mOldParent;
nsINode* mNewParent;
int32_t mOldOffset;
int32_t mNewOffset;
public:
AutoMoveNodeSelNotify(RangeUpdater& aRangeUpdater,
nsINode* aOldParent,
int32_t aOldOffset,
nsINode* aNewParent,
int32_t aNewOffset)
: mRangeUpdater(aRangeUpdater)
, mOldParent(aOldParent)
, mNewParent(aNewParent)
, mOldOffset(aOldOffset)
, mNewOffset(aNewOffset)
{
MOZ_ASSERT(aOldParent);
MOZ_ASSERT(aNewParent);
mRangeUpdater.WillMoveNode();
}
~AutoMoveNodeSelNotify()
{
mRangeUpdater.DidMoveNode(mOldParent, mOldOffset, mNewParent, mNewOffset);
}
};
} // namespace mozilla
#endif // #ifndef mozilla_SelectionState_h

View file

@ -0,0 +1,229 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SetDocumentTitleTransaction.h"
#include "mozilla/dom/Element.h" // for Element
#include "nsAString.h"
#include "nsCOMPtr.h" // for nsCOMPtr, getter_AddRefs, etc.
#include "nsDebug.h" // for NS_ENSURE_SUCCESS, etc.
#include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc.
#include "nsIDOMCharacterData.h" // for nsIDOMCharacterData
#include "nsIDOMDocument.h" // for nsIDOMDocument
#include "nsIDOMElement.h" // for nsIDOMElement
#include "nsIDOMNode.h" // for nsIDOMNode
#include "nsIDOMNodeList.h" // for nsIDOMNodeList
#include "nsIDOMText.h" // for nsIDOMText
#include "nsIDocument.h" // for nsIDocument
#include "nsIEditor.h" // for nsIEditor
#include "nsIHTMLEditor.h" // for nsIHTMLEditor
#include "nsLiteralString.h" // for NS_LITERAL_STRING
namespace mozilla {
// Note that aEditor is not refcounted.
SetDocumentTitleTransaction::SetDocumentTitleTransaction()
: mEditor(nullptr)
, mIsTransient(false)
{
}
NS_IMETHODIMP
SetDocumentTitleTransaction::Init(nsIHTMLEditor* aEditor,
const nsAString* aValue)
{
NS_ASSERTION(aEditor && aValue, "null args");
if (!aEditor || !aValue) {
return NS_ERROR_NULL_POINTER;
}
mEditor = aEditor;
mValue = *aValue;
return NS_OK;
}
NS_IMETHODIMP
SetDocumentTitleTransaction::DoTransaction()
{
return SetDomTitle(mValue);
}
NS_IMETHODIMP
SetDocumentTitleTransaction::UndoTransaction()
{
// No extra work required; the DOM changes alone are enough
return NS_OK;
}
NS_IMETHODIMP
SetDocumentTitleTransaction::RedoTransaction()
{
// No extra work required; the DOM changes alone are enough
return NS_OK;
}
nsresult
SetDocumentTitleTransaction::SetDomTitle(const nsAString& aTitle)
{
nsCOMPtr<nsIEditor> editor = do_QueryInterface(mEditor);
if (NS_WARN_IF(!editor)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMDocument> domDoc;
nsresult rv = editor->GetDocument(getter_AddRefs(domDoc));
if (NS_WARN_IF(!domDoc)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMNodeList> titleList;
rv = domDoc->GetElementsByTagName(NS_LITERAL_STRING("title"),
getter_AddRefs(titleList));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// First assume we will NOT really do anything
// (transaction will not be pushed on stack)
mIsTransient = true;
nsCOMPtr<nsIDOMNode> titleNode;
if(titleList) {
rv = titleList->Item(0, getter_AddRefs(titleNode));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (titleNode) {
// Delete existing child textnode of title node
// (Note: all contents under a TITLE node are always in a single text node)
nsCOMPtr<nsIDOMNode> child;
rv = titleNode->GetFirstChild(getter_AddRefs(child));
if (NS_FAILED(rv)) {
return rv;
}
if(child) {
// Save current text as the undo value
nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(child);
if(textNode) {
textNode->GetData(mUndoValue);
// If title text is identical to what already exists,
// quit now (mIsTransient is now TRUE)
if (mUndoValue == aTitle) {
return NS_OK;
}
}
rv = editor->DeleteNode(child);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
}
// We didn't return above, thus we really will be changing the title
mIsTransient = false;
// Get the <HEAD> node, create a <TITLE> and insert it under the HEAD
nsCOMPtr<nsIDocument> document = do_QueryInterface(domDoc);
if (NS_WARN_IF(!document)) {
return NS_ERROR_UNEXPECTED;
}
RefPtr<dom::Element> headElement = document->GetHeadElement();
if (NS_WARN_IF(!headElement)) {
return NS_ERROR_UNEXPECTED;
}
bool newTitleNode = false;
uint32_t newTitleIndex = 0;
if (!titleNode) {
// Didn't find one above: Create a new one
nsCOMPtr<nsIDOMElement>titleElement;
rv = domDoc->CreateElement(NS_LITERAL_STRING("title"),
getter_AddRefs(titleElement));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (NS_WARN_IF(!titleElement)) {
return NS_ERROR_FAILURE;
}
titleNode = do_QueryInterface(titleElement);
newTitleNode = true;
// Get index so we append new title node after all existing HEAD children.
newTitleIndex = headElement->GetChildCount();
}
// Append a text node under the TITLE only if the title text isn't empty.
if (titleNode && !aTitle.IsEmpty()) {
nsCOMPtr<nsIDOMText> textNode;
rv = domDoc->CreateTextNode(aTitle, getter_AddRefs(textNode));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIDOMNode> newNode = do_QueryInterface(textNode);
if (NS_WARN_IF(!newNode)) {
return NS_ERROR_FAILURE;
}
if (newTitleNode) {
// Not undoable: We will insert newTitleNode below
nsCOMPtr<nsIDOMNode> resultNode;
rv = titleNode->AppendChild(newNode, getter_AddRefs(resultNode));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
} else {
// This is an undoable transaction
rv = editor->InsertNode(newNode, titleNode, 0);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
// Calling AppendChild() or InsertNode() could cause removing the head
// element. So, let's mark it dirty.
headElement = nullptr;
}
if (newTitleNode) {
if (!headElement) {
headElement = document->GetHeadElement();
if (NS_WARN_IF(!headElement)) {
// XXX Can we return NS_OK when there is no head element?
return NS_ERROR_UNEXPECTED;
}
}
// Undoable transaction to insert title+text together
rv = editor->InsertNode(titleNode, headElement->AsDOMNode(), newTitleIndex);
}
return rv;
}
NS_IMETHODIMP
SetDocumentTitleTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("SetDocumentTitleTransaction: ");
aString += mValue;
return NS_OK;
}
NS_IMETHODIMP
SetDocumentTitleTransaction::GetIsTransient(bool* aIsTransient)
{
if (NS_WARN_IF(!aIsTransient)) {
return NS_ERROR_NULL_POINTER;
}
*aIsTransient = mIsTransient;
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,60 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef SetDocumentTitleTransaction_h
#define SetDocumentTitleTransaction_h
#include "mozilla/EditTransactionBase.h" // for EditTransactionBase, etc.
#include "nsString.h" // for nsString
#include "nscore.h" // for NS_IMETHOD, nsAString, etc.
class nsIHTMLEditor;
namespace mozilla {
/**
* A transaction that changes the document's title,
* which is a text node under the <title> tag in a page's <head> section
* provides default concrete behavior for all nsITransaction methods.
*/
class SetDocumentTitleTransaction final : public EditTransactionBase
{
public:
/**
* Initialize the transaction.
* @param aEditor The object providing core editing operations.
* @param aValue The new value for document title.
*/
NS_IMETHOD Init(nsIHTMLEditor* aEditor,
const nsAString* aValue);
SetDocumentTitleTransaction();
private:
nsresult SetDomTitle(const nsAString& aTitle);
public:
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD RedoTransaction() override;
NS_IMETHOD GetIsTransient(bool *aIsTransient) override;
protected:
// The editor that created this transaction.
nsIHTMLEditor* mEditor;
// The new title string.
nsString mValue;
// The previous title string to use for undo.
nsString mUndoValue;
// Set true if we dont' really change the title during Do().
bool mIsTransient;
};
} // namespace mozilla
#endif // #ifndef SetDocumentTitleTransaction_h

View file

@ -0,0 +1,128 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SplitNodeTransaction.h"
#include "mozilla/EditorBase.h" // for EditorBase
#include "mozilla/dom/Selection.h"
#include "nsAString.h"
#include "nsDebug.h" // for NS_ASSERTION, etc.
#include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc.
#include "nsIContent.h" // for nsIContent
namespace mozilla {
using namespace dom;
SplitNodeTransaction::SplitNodeTransaction(EditorBase& aEditorBase,
nsIContent& aNode,
int32_t aOffset)
: mEditorBase(aEditorBase)
, mExistingRightNode(&aNode)
, mOffset(aOffset)
{
}
SplitNodeTransaction::~SplitNodeTransaction()
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(SplitNodeTransaction, EditTransactionBase,
mParent,
mNewLeftNode)
NS_IMPL_ADDREF_INHERITED(SplitNodeTransaction, EditTransactionBase)
NS_IMPL_RELEASE_INHERITED(SplitNodeTransaction, EditTransactionBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SplitNodeTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
SplitNodeTransaction::DoTransaction()
{
// Create a new node
ErrorResult rv;
// Don't use .downcast directly because AsContent has an assertion we want
nsCOMPtr<nsINode> clone = mExistingRightNode->CloneNode(false, rv);
NS_ASSERTION(!rv.Failed() && clone, "Could not create clone");
NS_ENSURE_TRUE(!rv.Failed() && clone, rv.StealNSResult());
mNewLeftNode = dont_AddRef(clone.forget().take()->AsContent());
mEditorBase.MarkNodeDirty(mExistingRightNode->AsDOMNode());
// Get the parent node
mParent = mExistingRightNode->GetParentNode();
NS_ENSURE_TRUE(mParent, NS_ERROR_NULL_POINTER);
// Insert the new node
rv = mEditorBase.SplitNodeImpl(*mExistingRightNode, mOffset, *mNewLeftNode);
if (mEditorBase.GetShouldTxnSetSelection()) {
RefPtr<Selection> selection = mEditorBase.GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
rv = selection->Collapse(mNewLeftNode, mOffset);
}
return rv.StealNSResult();
}
NS_IMETHODIMP
SplitNodeTransaction::UndoTransaction()
{
MOZ_ASSERT(mNewLeftNode && mParent);
// This assumes Do inserted the new node in front of the prior existing node
return mEditorBase.JoinNodesImpl(mExistingRightNode, mNewLeftNode, mParent);
}
/* Redo cannot simply resplit the right node, because subsequent transactions
* on the redo stack may depend on the left node existing in its previous
* state.
*/
NS_IMETHODIMP
SplitNodeTransaction::RedoTransaction()
{
MOZ_ASSERT(mNewLeftNode && mParent);
ErrorResult rv;
// First, massage the existing node so it is in its post-split state
if (mExistingRightNode->GetAsText()) {
rv = mExistingRightNode->GetAsText()->DeleteData(0, mOffset);
NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
} else {
nsCOMPtr<nsIContent> child = mExistingRightNode->GetFirstChild();
nsCOMPtr<nsIContent> nextSibling;
for (int32_t i=0; i < mOffset; i++) {
if (rv.Failed()) {
return rv.StealNSResult();
}
if (!child) {
return NS_ERROR_NULL_POINTER;
}
nextSibling = child->GetNextSibling();
mExistingRightNode->RemoveChild(*child, rv);
if (!rv.Failed()) {
mNewLeftNode->AppendChild(*child, rv);
}
child = nextSibling;
}
}
// Second, re-insert the left node into the tree
nsCOMPtr<nsIContent> refNode = mExistingRightNode;
mParent->InsertBefore(*mNewLeftNode, refNode, rv);
return rv.StealNSResult();
}
NS_IMETHODIMP
SplitNodeTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("SplitNodeTransaction");
return NS_OK;
}
nsIContent*
SplitNodeTransaction::GetNewNode()
{
return mNewLeftNode;
}
} // namespace mozilla

View file

@ -0,0 +1,71 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef SplitNodeTransaction_h
#define SplitNodeTransaction_h
#include "mozilla/EditTransactionBase.h" // for EditTxn, etc.
#include "nsCOMPtr.h" // for nsCOMPtr
#include "nsCycleCollectionParticipant.h"
#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS_INHERITED
#include "nscore.h" // for NS_IMETHOD
class nsIContent;
class nsINode;
namespace mozilla {
class EditorBase;
/**
* A transaction that splits a node into two identical nodes, with the children
* divided between the new nodes.
*/
class SplitNodeTransaction final : public EditTransactionBase
{
public:
/**
* @param aEditorBase The provider of core editing operations
* @param aNode The node to split
* @param aOffset The location within aNode to do the split. aOffset may
* refer to children of aNode, or content of aNode. The
* left node will have child|content 0..aOffset-1.
*/
SplitNodeTransaction(EditorBase& aEditorBase, nsIContent& aNode,
int32_t aOffset);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SplitNodeTransaction,
EditTransactionBase)
NS_DECL_EDITTRANSACTIONBASE
NS_IMETHOD RedoTransaction() override;
nsIContent* GetNewNode();
protected:
virtual ~SplitNodeTransaction();
EditorBase& mEditorBase;
// The node to operate upon.
nsCOMPtr<nsIContent> mExistingRightNode;
// The offset into mExistingRightNode where its children are split. mOffset
// is the index of the first child in the right node. -1 means the new node
// gets no children.
int32_t mOffset;
// The node we create when splitting mExistingRightNode.
nsCOMPtr<nsIContent> mNewLeftNode;
// The parent shared by mExistingRightNode and mNewLeftNode.
nsCOMPtr<nsINode> mParent;
};
} // namespace mozilla
#endif // #ifndef SplitNodeTransaction_h

View file

@ -0,0 +1,157 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "StyleSheetTransactions.h"
#include <stddef.h> // for nullptr
#include "nsAString.h"
#include "nsCOMPtr.h" // for nsCOMPtr, do_QueryInterface, etc.
#include "mozilla/StyleSheet.h" // for mozilla::StyleSheet
#include "mozilla/StyleSheetInlines.h"
#include "nsDebug.h" // for NS_ENSURE_TRUE
#include "nsError.h" // for NS_OK, etc.
#include "nsIDOMDocument.h" // for nsIDOMDocument
#include "nsIDocument.h" // for nsIDocument
#include "nsIDocumentObserver.h" // for UPDATE_STYLE
#include "nsIEditor.h" // for nsIEditor
namespace mozilla {
static void
AddStyleSheet(nsIEditor* aEditor, StyleSheet* aSheet)
{
nsCOMPtr<nsIDOMDocument> domDoc;
aEditor->GetDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
if (doc) {
doc->BeginUpdate(UPDATE_STYLE);
doc->AddStyleSheet(aSheet);
doc->EndUpdate(UPDATE_STYLE);
}
}
static void
RemoveStyleSheet(nsIEditor* aEditor, StyleSheet* aSheet)
{
nsCOMPtr<nsIDOMDocument> domDoc;
aEditor->GetDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
if (doc) {
doc->BeginUpdate(UPDATE_STYLE);
doc->RemoveStyleSheet(aSheet);
doc->EndUpdate(UPDATE_STYLE);
}
}
/******************************************************************************
* AddStyleSheetTransaction
******************************************************************************/
AddStyleSheetTransaction::AddStyleSheetTransaction()
: mEditor(nullptr)
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(AddStyleSheetTransaction,
EditTransactionBase,
mSheet)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AddStyleSheetTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
AddStyleSheetTransaction::Init(nsIEditor* aEditor,
StyleSheet* aSheet)
{
NS_ENSURE_TRUE(aEditor && aSheet, NS_ERROR_INVALID_ARG);
mEditor = aEditor;
mSheet = aSheet;
return NS_OK;
}
NS_IMETHODIMP
AddStyleSheetTransaction::DoTransaction()
{
NS_ENSURE_TRUE(mEditor && mSheet, NS_ERROR_NOT_INITIALIZED);
AddStyleSheet(mEditor, mSheet);
return NS_OK;
}
NS_IMETHODIMP
AddStyleSheetTransaction::UndoTransaction()
{
NS_ENSURE_TRUE(mEditor && mSheet, NS_ERROR_NOT_INITIALIZED);
RemoveStyleSheet(mEditor, mSheet);
return NS_OK;
}
NS_IMETHODIMP
AddStyleSheetTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("AddStyleSheetTransaction");
return NS_OK;
}
/******************************************************************************
* RemoveStyleSheetTransaction
******************************************************************************/
RemoveStyleSheetTransaction::RemoveStyleSheetTransaction()
: mEditor(nullptr)
{
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(RemoveStyleSheetTransaction,
EditTransactionBase,
mSheet)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RemoveStyleSheetTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
NS_IMETHODIMP
RemoveStyleSheetTransaction::Init(nsIEditor* aEditor,
StyleSheet* aSheet)
{
NS_ENSURE_TRUE(aEditor && aSheet, NS_ERROR_INVALID_ARG);
mEditor = aEditor;
mSheet = aSheet;
return NS_OK;
}
NS_IMETHODIMP
RemoveStyleSheetTransaction::DoTransaction()
{
NS_ENSURE_TRUE(mEditor && mSheet, NS_ERROR_NOT_INITIALIZED);
RemoveStyleSheet(mEditor, mSheet);
return NS_OK;
}
NS_IMETHODIMP
RemoveStyleSheetTransaction::UndoTransaction()
{
NS_ENSURE_TRUE(mEditor && mSheet, NS_ERROR_NOT_INITIALIZED);
AddStyleSheet(mEditor, mSheet);
return NS_OK;
}
NS_IMETHODIMP
RemoveStyleSheetTransaction::GetTxnDescription(nsAString& aString)
{
aString.AssignLiteral("RemoveStyleSheetTransaction");
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,73 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef StylesheetTransactions_h
#define StylesheetTransactions_h
#include "mozilla/EditTransactionBase.h" // for EditTransactionBase, etc.
#include "mozilla/StyleSheet.h" // for mozilla::StyleSheet
#include "nsCycleCollectionParticipant.h"
#include "nsID.h" // for REFNSIID
#include "nscore.h" // for NS_IMETHOD
class nsIEditor;
namespace mozilla {
class AddStyleSheetTransaction final : public EditTransactionBase
{
public:
/**
* Initialize the transaction.
* @param aEditor The object providing core editing operations
* @param aSheet The stylesheet to add
*/
NS_IMETHOD Init(nsIEditor* aEditor, StyleSheet* aSheet);
AddStyleSheetTransaction();
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AddStyleSheetTransaction,
EditTransactionBase)
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
NS_DECL_EDITTRANSACTIONBASE
protected:
// The editor that created this transaction.
nsIEditor* mEditor;
// The style sheet to add.
RefPtr<mozilla::StyleSheet> mSheet;
};
class RemoveStyleSheetTransaction final : public EditTransactionBase
{
public:
/**
* Initialize the transaction.
* @param aEditor The object providing core editing operations.
* @param aSheet The stylesheet to remove.
*/
NS_IMETHOD Init(nsIEditor* aEditor, StyleSheet* aSheet);
RemoveStyleSheetTransaction();
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RemoveStyleSheetTransaction,
EditTransactionBase)
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
NS_DECL_EDITTRANSACTIONBASE
protected:
// The editor that created this transaction.
nsIEditor* mEditor;
// The style sheet to remove.
RefPtr<StyleSheet> mSheet;
};
} // namespace mozilla
#endif // #ifndef StylesheetTransactions_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,357 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_TextEditRules_h
#define mozilla_TextEditRules_h
#include "mozilla/EditorBase.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIEditRules.h"
#include "nsIEditor.h"
#include "nsISupportsImpl.h"
#include "nsITimer.h"
#include "nsString.h"
#include "nscore.h"
class nsIDOMElement;
class nsIDOMNode;
namespace mozilla {
class AutoLockRulesSniffing;
class TextEditor;
namespace dom {
class Selection;
} // namespace dom
/**
* Object that encapsulates HTML text-specific editing rules.
*
* To be a good citizen, edit rules must live by these restrictions:
* 1. All data manipulation is through the editor.
* Content nodes in the document tree must <B>not</B> be manipulated
* directly. Content nodes in document fragments that are not part of the
* document itself may be manipulated at will. Operations on document
* fragments must <B>not</B> go through the editor.
* 2. Selection must not be explicitly set by the rule method.
* Any manipulation of Selection must be done by the editor.
*/
class TextEditRules : public nsIEditRules
, public nsITimerCallback
{
public:
typedef dom::Element Element;
typedef dom::Selection Selection;
typedef dom::Text Text;
template<typename T> using OwningNonNull = OwningNonNull<T>;
NS_DECL_NSITIMERCALLBACK
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TextEditRules, nsIEditRules)
TextEditRules();
// nsIEditRules methods
NS_IMETHOD Init(TextEditor* aTextEditor) override;
NS_IMETHOD SetInitialValue(const nsAString& aValue) override;
NS_IMETHOD DetachEditor() override;
NS_IMETHOD BeforeEdit(EditAction action,
nsIEditor::EDirection aDirection) override;
NS_IMETHOD AfterEdit(EditAction action,
nsIEditor::EDirection aDirection) override;
NS_IMETHOD WillDoAction(Selection* aSelection, RulesInfo* aInfo,
bool* aCancel, bool* aHandled) override;
NS_IMETHOD DidDoAction(Selection* aSelection, RulesInfo* aInfo,
nsresult aResult) override;
NS_IMETHOD DocumentIsEmpty(bool* aDocumentIsEmpty) override;
NS_IMETHOD DocumentModified() override;
protected:
virtual ~TextEditRules();
public:
void ResetIMETextPWBuf();
/**
* Handles the newline characters either according to aNewLineHandling
* or to the default system prefs if aNewLineHandling is negative.
*
* @param aString the string to be modified in place.
* @param aNewLineHandling determine the desired type of newline handling:
* * negative values:
* handle newlines according to platform defaults.
* * nsIPlaintextEditor::eNewlinesReplaceWithSpaces:
* replace newlines with spaces.
* * nsIPlaintextEditor::eNewlinesStrip:
* remove newlines from the string.
* * nsIPlaintextEditor::eNewlinesReplaceWithCommas:
* replace newlines with commas.
* * nsIPlaintextEditor::eNewlinesStripSurroundingWhitespace:
* collapse newlines and surrounding whitespace characters and
* remove them from the string.
* * nsIPlaintextEditor::eNewlinesPasteIntact:
* only remove the leading and trailing newlines.
* * nsIPlaintextEditor::eNewlinesPasteToFirst or any other value:
* remove the first newline and all characters following it.
*/
static void HandleNewLines(nsString& aString, int32_t aNewLineHandling);
/**
* Prepare a string buffer for being displayed as the contents of a password
* field. This function uses the platform-specific character for representing
* characters entered into password fields.
*
* @param aOutString the output string. When this function returns,
* aOutString will contain aLength password characters.
* @param aLength the number of password characters that aOutString should
* contain.
*/
static void FillBufWithPWChars(nsAString* aOutString, int32_t aLength);
protected:
void InitFields();
// TextEditRules implementation methods
nsresult WillInsertText(EditAction aAction,
Selection* aSelection,
bool* aCancel,
bool* aHandled,
const nsAString* inString,
nsAString* outString,
int32_t aMaxLength);
nsresult DidInsertText(Selection* aSelection, nsresult aResult);
nsresult GetTopEnclosingPre(nsIDOMNode* aNode, nsIDOMNode** aOutPreNode);
nsresult WillInsertBreak(Selection* aSelection, bool* aCancel,
bool* aHandled, int32_t aMaxLength);
nsresult DidInsertBreak(Selection* aSelection, nsresult aResult);
void WillInsert(Selection& aSelection, bool* aCancel);
nsresult DidInsert(Selection* aSelection, nsresult aResult);
nsresult WillDeleteSelection(Selection* aSelection,
nsIEditor::EDirection aCollapsedAction,
bool* aCancel,
bool* aHandled);
nsresult DidDeleteSelection(Selection* aSelection,
nsIEditor::EDirection aCollapsedAction,
nsresult aResult);
nsresult WillSetTextProperty(Selection* aSelection, bool* aCancel,
bool* aHandled);
nsresult DidSetTextProperty(Selection* aSelection, nsresult aResult);
nsresult WillRemoveTextProperty(Selection* aSelection, bool* aCancel,
bool* aHandled);
nsresult DidRemoveTextProperty(Selection* aSelection, nsresult aResult);
nsresult WillUndo(Selection* aSelection, bool* aCancel, bool* aHandled);
nsresult DidUndo(Selection* aSelection, nsresult aResult);
nsresult WillRedo(Selection* aSelection, bool* aCancel, bool* aHandled);
nsresult DidRedo(Selection* aSelection, nsresult aResult);
/**
* Called prior to nsIEditor::OutputToString.
* @param aSelection
* @param aInFormat The format requested for the output, a MIME type.
* @param aOutText The string to use for output, if aCancel is set to true.
* @param aOutCancel If set to true, the caller should cancel the operation
* and use aOutText as the result.
*/
nsresult WillOutputText(Selection* aSelection,
const nsAString* aInFormat,
nsAString* aOutText,
bool* aOutCancel,
bool* aHandled);
nsresult DidOutputText(Selection* aSelection, nsresult aResult);
/**
* Check for and replace a redundant trailing break.
*/
nsresult RemoveRedundantTrailingBR();
/**
* Creates a trailing break in the text doc if there is not one already.
*/
nsresult CreateTrailingBRIfNeeded();
/**
* Creates a bogus text node if the document has no editable content.
*/
nsresult CreateBogusNodeIfNeeded(Selection* aSelection);
/**
* Returns a truncated insertion string if insertion would place us over
* aMaxLength
*/
nsresult TruncateInsertionIfNeeded(Selection* aSelection,
const nsAString* aInString,
nsAString* aOutString,
int32_t aMaxLength,
bool* aTruncated);
/**
* Remove IME composition text from password buffer.
*/
void RemoveIMETextFromPWBuf(int32_t& aStart, nsAString* aIMEString);
nsresult CreateMozBR(nsIDOMNode* inParent, int32_t inOffset,
nsIDOMNode** outBRNode = nullptr);
void UndefineCaretBidiLevel(Selection* aSelection);
nsresult CheckBidiLevelForDeletion(Selection* aSelection,
nsIDOMNode* aSelNode,
int32_t aSelOffset,
nsIEditor::EDirection aAction,
bool* aCancel);
nsresult HideLastPWInput();
nsresult CollapseSelectionToTrailingBRIfNeeded(Selection* aSelection);
bool IsPasswordEditor() const;
bool IsSingleLineEditor() const;
bool IsPlaintextEditor() const;
bool IsReadonly() const;
bool IsDisabled() const;
bool IsMailEditor() const;
bool DontEchoPassword() const;
// Note that we do not refcount the editor.
TextEditor* mTextEditor;
// A buffer we use to store the real value of password editors.
nsString mPasswordText;
// A buffer we use to track the IME composition string.
nsString mPasswordIMEText;
uint32_t mPasswordIMEIndex;
// Magic node acts as placeholder in empty doc.
nsCOMPtr<nsIDOMNode> mBogusNode;
// Cached selected node.
nsCOMPtr<nsIDOMNode> mCachedSelectionNode;
// Cached selected offset.
int32_t mCachedSelectionOffset;
uint32_t mActionNesting;
bool mLockRulesSniffing;
bool mDidExplicitlySetInterline;
// In bidirectional text, delete characters not visually adjacent to the
// caret without moving the caret first.
bool mDeleteBidiImmediately;
// The top level editor action.
EditAction mTheAction;
nsCOMPtr<nsITimer> mTimer;
uint32_t mLastStart;
uint32_t mLastLength;
// friends
friend class AutoLockRulesSniffing;
};
class TextRulesInfo final : public RulesInfo
{
public:
explicit TextRulesInfo(EditAction aAction)
: RulesInfo(aAction)
, inString(nullptr)
, outString(nullptr)
, outputFormat(nullptr)
, maxLength(-1)
, collapsedAction(nsIEditor::eNext)
, stripWrappers(nsIEditor::eStrip)
, bOrdered(false)
, entireList(false)
, bulletType(nullptr)
, alignType(nullptr)
, blockType(nullptr)
, insertElement(nullptr)
{}
// kInsertText
const nsAString* inString;
nsAString* outString;
const nsAString* outputFormat;
int32_t maxLength;
// kDeleteSelection
nsIEditor::EDirection collapsedAction;
nsIEditor::EStripWrappers stripWrappers;
// kMakeList
bool bOrdered;
bool entireList;
const nsAString* bulletType;
// kAlign
const nsAString* alignType;
// kMakeBasicBlock
const nsAString* blockType;
// kInsertElement
const nsIDOMElement* insertElement;
};
/**
* Stack based helper class for StartOperation()/EndOperation() sandwich.
* This class sets a bool letting us know to ignore any rules sniffing
* that tries to occur reentrantly.
*/
class MOZ_STACK_CLASS AutoLockRulesSniffing final
{
public:
explicit AutoLockRulesSniffing(TextEditRules* aRules)
: mRules(aRules)
{
if (mRules) {
mRules->mLockRulesSniffing = true;
}
}
~AutoLockRulesSniffing()
{
if (mRules) {
mRules->mLockRulesSniffing = false;
}
}
protected:
TextEditRules* mRules;
};
/**
* Stack based helper class for turning on/off the edit listener.
*/
class MOZ_STACK_CLASS AutoLockListener final
{
public:
explicit AutoLockListener(bool* aEnabled)
: mEnabled(aEnabled)
, mOldState(false)
{
if (mEnabled) {
mOldState = *mEnabled;
*mEnabled = false;
}
}
~AutoLockListener()
{
if (mEnabled) {
*mEnabled = mOldState;
}
}
protected:
bool* mEnabled;
bool mOldState;
};
} // namespace mozilla
#endif // #ifndef mozilla_TextEditRules_h

View file

@ -0,0 +1,100 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/TextEditRules.h"
#include "mozilla/TextEditor.h"
#include "mozilla/dom/Selection.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsFrameSelection.h"
#include "nsIContent.h"
#include "nsIDOMNode.h"
#include "nsIEditor.h"
#include "nsIPresShell.h"
#include "nsISupportsImpl.h"
#include "nsPresContext.h"
#include "nscore.h"
namespace mozilla {
using namespace dom;
// Test for distance between caret and text that will be deleted
nsresult
TextEditRules::CheckBidiLevelForDeletion(Selection* aSelection,
nsIDOMNode* aSelNode,
int32_t aSelOffset,
nsIEditor::EDirection aAction,
bool* aCancel)
{
NS_ENSURE_ARG_POINTER(aCancel);
*aCancel = false;
nsCOMPtr<nsIPresShell> shell = mTextEditor->GetPresShell();
NS_ENSURE_TRUE(shell, NS_ERROR_NOT_INITIALIZED);
nsPresContext *context = shell->GetPresContext();
NS_ENSURE_TRUE(context, NS_ERROR_NULL_POINTER);
if (!context->BidiEnabled()) {
return NS_OK;
}
nsCOMPtr<nsIContent> content = do_QueryInterface(aSelNode);
NS_ENSURE_TRUE(content, NS_ERROR_NULL_POINTER);
nsBidiLevel levelBefore;
nsBidiLevel levelAfter;
RefPtr<nsFrameSelection> frameSelection =
aSelection->AsSelection()->GetFrameSelection();
NS_ENSURE_TRUE(frameSelection, NS_ERROR_NULL_POINTER);
nsPrevNextBidiLevels levels = frameSelection->
GetPrevNextBidiLevels(content, aSelOffset, true);
levelBefore = levels.mLevelBefore;
levelAfter = levels.mLevelAfter;
nsBidiLevel currentCaretLevel = frameSelection->GetCaretBidiLevel();
nsBidiLevel levelOfDeletion;
levelOfDeletion =
(nsIEditor::eNext==aAction || nsIEditor::eNextWord==aAction) ?
levelAfter : levelBefore;
if (currentCaretLevel == levelOfDeletion) {
return NS_OK; // perform the deletion
}
if (!mDeleteBidiImmediately && levelBefore != levelAfter) {
*aCancel = true;
}
// Set the bidi level of the caret to that of the
// character that will be (or would have been) deleted
frameSelection->SetCaretBidiLevel(levelOfDeletion);
return NS_OK;
}
void
TextEditRules::UndefineCaretBidiLevel(Selection* aSelection)
{
/**
* After inserting text the caret Bidi level must be set to the level of the
* inserted text.This is difficult, because we cannot know what the level is
* until after the Bidi algorithm is applied to the whole paragraph.
*
* So we set the caret Bidi level to UNDEFINED here, and the caret code will
* set it correctly later
*/
RefPtr<nsFrameSelection> frameSelection = aSelection->GetFrameSelection();
if (frameSelection) {
frameSelection->UndefineCaretBidiLevel();
}
}
} // namespace mozilla

View file

@ -0,0 +1,113 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TextEditUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/TextEditor.h"
#include "mozilla/dom/Element.h"
#include "nsAString.h"
#include "nsCOMPtr.h"
#include "nsCaseTreatment.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsGkAtoms.h"
#include "nsIDOMElement.h"
#include "nsIDOMNode.h"
#include "nsNameSpaceManager.h"
#include "nsLiteralString.h"
#include "nsString.h"
namespace mozilla {
/******************************************************************************
* TextEditUtils
******************************************************************************/
/**
* IsBody() returns true if aNode is an html body node.
*/
bool
TextEditUtils::IsBody(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::body);
}
/**
* IsBreak() returns true if aNode is an html break node.
*/
bool
TextEditUtils::IsBreak(nsIDOMNode* aNode)
{
return EditorBase::NodeIsType(aNode, nsGkAtoms::br);
}
bool
TextEditUtils::IsBreak(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsHTMLElement(nsGkAtoms::br);
}
/**
* IsMozBR() returns true if aNode is an html br node with |type = _moz|.
*/
bool
TextEditUtils::IsMozBR(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
return IsBreak(aNode) && HasMozAttr(aNode);
}
bool
TextEditUtils::IsMozBR(nsINode* aNode)
{
MOZ_ASSERT(aNode);
return aNode->IsHTMLElement(nsGkAtoms::br) &&
aNode->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
NS_LITERAL_STRING("_moz"),
eIgnoreCase);
}
/**
* HasMozAttr() returns true if aNode has type attribute and its value is
* |_moz|. (Used to indicate div's and br's we use in mail compose rules)
*/
bool
TextEditUtils::HasMozAttr(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
if (!element) {
return false;
}
nsAutoString typeAttrVal;
nsresult rv = element->GetAttribute(NS_LITERAL_STRING("type"), typeAttrVal);
return NS_SUCCEEDED(rv) && typeAttrVal.LowerCaseEqualsLiteral("_moz");
}
/******************************************************************************
* AutoEditInitRulesTrigger
******************************************************************************/
AutoEditInitRulesTrigger::AutoEditInitRulesTrigger(TextEditor* aTextEditor,
nsresult& aResult)
: mTextEditor(aTextEditor)
, mResult(aResult)
{
if (mTextEditor) {
mTextEditor->BeginEditorInit();
}
}
AutoEditInitRulesTrigger::~AutoEditInitRulesTrigger()
{
if (mTextEditor) {
mResult = mTextEditor->EndEditorInit();
}
}
} // namespace mozilla

View file

@ -0,0 +1,47 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef TextEditUtils_h
#define TextEditUtils_h
#include "nscore.h"
class nsIDOMNode;
class nsINode;
namespace mozilla {
class TextEditor;
class TextEditUtils final
{
public:
// from TextEditRules:
static bool IsBody(nsIDOMNode* aNode);
static bool IsBreak(nsIDOMNode* aNode);
static bool IsBreak(nsINode* aNode);
static bool IsMozBR(nsIDOMNode* aNode);
static bool IsMozBR(nsINode* aNode);
static bool HasMozAttr(nsIDOMNode* aNode);
};
/***************************************************************************
* stack based helper class for detecting end of editor initialization, in
* order to trigger "end of init" initialization of the edit rules.
*/
class AutoEditInitRulesTrigger final
{
private:
TextEditor* mTextEditor;
nsresult& mResult;
public:
AutoEditInitRulesTrigger(TextEditor* aTextEditor, nsresult& aResult);
~AutoEditInitRulesTrigger();
};
} // naemspace mozilla
#endif // #ifndef TextEditUtils_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,246 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_TextEditor_h
#define mozilla_TextEditor_h
#include "mozilla/EditorBase.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIEditor.h"
#include "nsIEditorMailSupport.h"
#include "nsIPlaintextEditor.h"
#include "nsISupportsImpl.h"
#include "nscore.h"
class nsIContent;
class nsIDOMDocument;
class nsIDOMElement;
class nsIDOMEvent;
class nsIDOMKeyEvent;
class nsIDOMNode;
class nsIDocumentEncoder;
class nsIEditRules;
class nsIOutputStream;
class nsISelectionController;
class nsITransferable;
namespace mozilla {
class AutoEditInitRulesTrigger;
class HTMLEditRules;
class TextEditRules;
namespace dom {
class Selection;
} // namespace dom
/**
* The text editor implementation.
* Use to edit text document represented as a DOM tree.
*/
class TextEditor : public EditorBase
, public nsIPlaintextEditor
, public nsIEditorMailSupport
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TextEditor, EditorBase)
enum ETypingAction
{
eTypedText, /* user typed text */
eTypedBR, /* user typed shift-enter to get a br */
eTypedBreak /* user typed enter */
};
TextEditor();
// nsIPlaintextEditor methods
NS_DECL_NSIPLAINTEXTEDITOR
// nsIEditorMailSupport overrides
NS_DECL_NSIEDITORMAILSUPPORT
// Overrides of EditorBase interface methods
NS_IMETHOD SetAttributeOrEquivalent(nsIDOMElement* aElement,
const nsAString& aAttribute,
const nsAString& aValue,
bool aSuppressTransaction) override;
NS_IMETHOD RemoveAttributeOrEquivalent(nsIDOMElement* aElement,
const nsAString& aAttribute,
bool aSuppressTransaction) override;
NS_IMETHOD Init(nsIDOMDocument* aDoc, nsIContent* aRoot,
nsISelectionController* aSelCon, uint32_t aFlags,
const nsAString& aValue) override;
NS_IMETHOD GetDocumentIsEmpty(bool* aDocumentIsEmpty) override;
NS_IMETHOD GetIsDocumentEditable(bool* aIsDocumentEditable) override;
NS_IMETHOD DeleteSelection(EDirection aAction,
EStripWrappers aStripWrappers) override;
NS_IMETHOD SetDocumentCharacterSet(const nsACString& characterSet) override;
NS_IMETHOD Undo(uint32_t aCount) override;
NS_IMETHOD Redo(uint32_t aCount) override;
NS_IMETHOD Cut() override;
NS_IMETHOD CanCut(bool* aCanCut) override;
NS_IMETHOD Copy() override;
NS_IMETHOD CanCopy(bool* aCanCopy) override;
NS_IMETHOD CanDelete(bool* aCanDelete) override;
NS_IMETHOD Paste(int32_t aSelectionType) override;
NS_IMETHOD CanPaste(int32_t aSelectionType, bool* aCanPaste) override;
NS_IMETHOD PasteTransferable(nsITransferable* aTransferable) override;
NS_IMETHOD CanPasteTransferable(nsITransferable* aTransferable,
bool* aCanPaste) override;
NS_IMETHOD OutputToString(const nsAString& aFormatType,
uint32_t aFlags,
nsAString& aOutputString) override;
NS_IMETHOD OutputToStream(nsIOutputStream* aOutputStream,
const nsAString& aFormatType,
const nsACString& aCharsetOverride,
uint32_t aFlags) override;
/**
* All editor operations which alter the doc should be prefaced
* with a call to StartOperation, naming the action and direction.
*/
NS_IMETHOD StartOperation(EditAction opID,
nsIEditor::EDirection aDirection) override;
/**
* All editor operations which alter the doc should be followed
* with a call to EndOperation.
*/
NS_IMETHOD EndOperation() override;
/**
* Make the given selection span the entire document.
*/
virtual nsresult SelectEntireDocument(Selection* aSelection) override;
virtual nsresult HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent) override;
virtual already_AddRefed<dom::EventTarget> GetDOMEventTarget() override;
virtual nsresult BeginIMEComposition(WidgetCompositionEvent* aEvent) override;
virtual nsresult UpdateIMEComposition(nsIDOMEvent* aTextEvent) override;
virtual already_AddRefed<nsIContent> GetInputEventTargetContent() override;
// Utility Routines, not part of public API
NS_IMETHOD TypedText(const nsAString& aString, ETypingAction aAction);
nsresult InsertTextAt(const nsAString& aStringToInsert,
nsIDOMNode* aDestinationNode,
int32_t aDestOffset,
bool aDoDeleteSelection);
virtual nsresult InsertFromDataTransfer(dom::DataTransfer* aDataTransfer,
int32_t aIndex,
nsIDOMDocument* aSourceDoc,
nsIDOMNode* aDestinationNode,
int32_t aDestOffset,
bool aDoDeleteSelection) override;
virtual nsresult InsertFromDrop(nsIDOMEvent* aDropEvent) override;
/**
* Extends the selection for given deletion operation
* If done, also update aAction to what's actually left to do after the
* extension.
*/
nsresult ExtendSelectionForDelete(Selection* aSelection,
nsIEditor::EDirection* aAction);
/**
* Return true if the data is safe to insert as the source and destination
* principals match, or we are in a editor context where this doesn't matter.
* Otherwise, the data must be sanitized first.
*/
bool IsSafeToInsertData(nsIDOMDocument* aSourceDoc);
static void GetDefaultEditorPrefs(int32_t& aNewLineHandling,
int32_t& aCaretStyle);
protected:
virtual ~TextEditor();
NS_IMETHOD InitRules();
void BeginEditorInit();
nsresult EndEditorInit();
NS_IMETHOD GetAndInitDocEncoder(const nsAString& aFormatType,
uint32_t aFlags,
const nsACString& aCharset,
nsIDocumentEncoder** encoder);
NS_IMETHOD CreateBR(nsIDOMNode* aNode, int32_t aOffset,
nsCOMPtr<nsIDOMNode>* outBRNode,
EDirection aSelect = eNone);
already_AddRefed<Element> CreateBRImpl(nsCOMPtr<nsINode>* aInOutParent,
int32_t* aInOutOffset,
EDirection aSelect);
nsresult CreateBRImpl(nsCOMPtr<nsIDOMNode>* aInOutParent,
int32_t* aInOutOffset,
nsCOMPtr<nsIDOMNode>* outBRNode,
EDirection aSelect);
nsresult InsertBR(nsCOMPtr<nsIDOMNode>* outBRNode);
/**
* Factored methods for handling insertion of data from transferables
* (drag&drop or clipboard).
*/
NS_IMETHOD PrepareTransferable(nsITransferable** transferable);
NS_IMETHOD InsertTextFromTransferable(nsITransferable* transferable,
nsIDOMNode* aDestinationNode,
int32_t aDestOffset,
bool aDoDeleteSelection);
/**
* Shared outputstring; returns whether selection is collapsed and resulting
* string.
*/
nsresult SharedOutputString(uint32_t aFlags, bool* aIsCollapsed,
nsAString& aResult);
/**
* Small utility routine to test the eEditorReadonly bit.
*/
bool IsModifiable();
enum PasswordFieldAllowed
{
ePasswordFieldAllowed,
ePasswordFieldNotAllowed
};
bool CanCutOrCopy(PasswordFieldAllowed aPasswordFieldAllowed);
bool FireClipboardEvent(EventMessage aEventMessage,
int32_t aSelectionType,
bool* aActionTaken = nullptr);
bool UpdateMetaCharset(nsIDOMDocument* aDocument,
const nsACString& aCharacterSet);
protected:
nsCOMPtr<nsIEditRules> mRules;
int32_t mWrapColumn;
int32_t mMaxTextLength;
int32_t mInitTriggerCounter;
int32_t mNewlineHandling;
int32_t mCaretStyle;
friend class AutoEditInitRulesTrigger;
friend class HTMLEditRules;
friend class TextEditRules;
};
} // namespace mozilla
#endif // #ifndef mozilla_TextEditor_h

View file

@ -0,0 +1,472 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/TextEditor.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/EditorUtils.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/SelectionState.h"
#include "mozilla/dom/Selection.h"
#include "nsAString.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIClipboard.h"
#include "nsIContent.h"
#include "nsIDOMDataTransfer.h"
#include "nsIDOMDocument.h"
#include "nsIDOMDragEvent.h"
#include "nsIDOMEvent.h"
#include "nsIDOMNode.h"
#include "nsIDOMUIEvent.h"
#include "nsIDocument.h"
#include "nsIDragService.h"
#include "nsIDragSession.h"
#include "nsIEditor.h"
#include "nsIEditorIMESupport.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIPrincipal.h"
#include "nsIFormControl.h"
#include "nsIPlaintextEditor.h"
#include "nsISupportsPrimitives.h"
#include "nsITransferable.h"
#include "nsIVariant.h"
#include "nsLiteralString.h"
#include "nsRange.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsXPCOM.h"
#include "nscore.h"
class nsILoadContext;
class nsISupports;
namespace mozilla {
using namespace dom;
NS_IMETHODIMP
TextEditor::PrepareTransferable(nsITransferable** transferable)
{
// Create generic Transferable for getting the data
nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable);
NS_ENSURE_SUCCESS(rv, rv);
// Get the nsITransferable interface for getting the data from the clipboard
if (transferable) {
nsCOMPtr<nsIDocument> destdoc = GetDocument();
nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
(*transferable)->Init(loadContext);
(*transferable)->AddDataFlavor(kUnicodeMime);
(*transferable)->AddDataFlavor(kMozTextInternal);
};
return NS_OK;
}
nsresult
TextEditor::InsertTextAt(const nsAString& aStringToInsert,
nsIDOMNode* aDestinationNode,
int32_t aDestOffset,
bool aDoDeleteSelection)
{
if (aDestinationNode) {
RefPtr<Selection> selection = GetSelection();
NS_ENSURE_STATE(selection);
nsCOMPtr<nsIDOMNode> targetNode = aDestinationNode;
int32_t targetOffset = aDestOffset;
if (aDoDeleteSelection) {
// Use an auto tracker so that our drop point is correctly
// positioned after the delete.
AutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
nsresult rv = DeleteSelection(eNone, eStrip);
NS_ENSURE_SUCCESS(rv, rv);
}
nsresult rv = selection->Collapse(targetNode, targetOffset);
NS_ENSURE_SUCCESS(rv, rv);
}
return InsertText(aStringToInsert);
}
NS_IMETHODIMP
TextEditor::InsertTextFromTransferable(nsITransferable* aTransferable,
nsIDOMNode* aDestinationNode,
int32_t aDestOffset,
bool aDoDeleteSelection)
{
nsresult rv = NS_OK;
nsAutoCString bestFlavor;
nsCOMPtr<nsISupports> genericDataObj;
uint32_t len = 0;
if (NS_SUCCEEDED(
aTransferable->GetAnyTransferData(bestFlavor,
getter_AddRefs(genericDataObj),
&len)) &&
(bestFlavor.EqualsLiteral(kUnicodeMime) ||
bestFlavor.EqualsLiteral(kMozTextInternal))) {
AutoTransactionsConserveSelection dontSpazMySelection(this);
nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
if (textDataObj && len > 0) {
nsAutoString stuffToPaste;
textDataObj->GetData(stuffToPaste);
NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
// Sanitize possible carriage returns in the string to be inserted
nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
AutoEditBatch beginBatching(this);
rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection);
}
}
// Try to scroll the selection into view if the paste/drop succeeded
if (NS_SUCCEEDED(rv)) {
ScrollSelectionIntoView(false);
}
return rv;
}
nsresult
TextEditor::InsertFromDataTransfer(DataTransfer* aDataTransfer,
int32_t aIndex,
nsIDOMDocument* aSourceDoc,
nsIDOMNode* aDestinationNode,
int32_t aDestOffset,
bool aDoDeleteSelection)
{
nsCOMPtr<nsIVariant> data;
DataTransfer::Cast(aDataTransfer)->GetDataAtNoSecurityCheck(NS_LITERAL_STRING("text/plain"), aIndex,
getter_AddRefs(data));
if (data) {
nsAutoString insertText;
data->GetAsAString(insertText);
nsContentUtils::PlatformToDOMLineBreaks(insertText);
AutoEditBatch beginBatching(this);
return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection);
}
return NS_OK;
}
nsresult
TextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
{
ForceCompositionEnd();
nsCOMPtr<nsIDOMDragEvent> dragEvent(do_QueryInterface(aDropEvent));
NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE);
nsCOMPtr<nsIDOMDataTransfer> domDataTransfer;
dragEvent->GetDataTransfer(getter_AddRefs(domDataTransfer));
nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(domDataTransfer);
NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE);
nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
NS_ASSERTION(dragSession, "No drag session");
nsCOMPtr<nsIDOMNode> sourceNode;
dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode));
nsCOMPtr<nsIDOMDocument> srcdomdoc;
if (sourceNode) {
sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc));
NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE);
}
if (nsContentUtils::CheckForSubFrameDrop(dragSession,
aDropEvent->WidgetEventPtr()->AsDragEvent())) {
// Don't allow drags from subframe documents with different origins than
// the drop destination.
if (srcdomdoc && !IsSafeToInsertData(srcdomdoc)) {
return NS_OK;
}
}
// Current doc is destination
nsCOMPtr<nsIDOMDocument> destdomdoc = GetDOMDocument();
NS_ENSURE_TRUE(destdomdoc, NS_ERROR_NOT_INITIALIZED);
uint32_t numItems = 0;
nsresult rv = dataTransfer->GetMozItemCount(&numItems);
NS_ENSURE_SUCCESS(rv, rv);
if (numItems < 1) {
return NS_ERROR_FAILURE; // Nothing to drop?
}
// Combine any deletion and drop insertion into one transaction
AutoEditBatch beginBatching(this);
bool deleteSelection = false;
// We have to figure out whether to delete and relocate caret only once
// Parent and offset are under the mouse cursor
nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aDropEvent);
NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE);
nsCOMPtr<nsIDOMNode> newSelectionParent;
rv = uiEvent->GetRangeParent(getter_AddRefs(newSelectionParent));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
int32_t newSelectionOffset;
rv = uiEvent->GetRangeOffset(&newSelectionOffset);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<Selection> selection = GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
bool isCollapsed = selection->Collapsed();
// Only the HTMLEditor::FindUserSelectAllNode returns a node.
nsCOMPtr<nsIDOMNode> userSelectNode = FindUserSelectAllNode(newSelectionParent);
if (userSelectNode) {
// The drop is happening over a "-moz-user-select: all"
// subtree so make sure the content we insert goes before
// the root of the subtree.
//
// XXX: Note that inserting before the subtree matches the
// current behavior when dropping on top of an image.
// The decision for dropping before or after the
// subtree should really be done based on coordinates.
newSelectionParent = GetNodeLocation(userSelectNode, &newSelectionOffset);
NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
}
// Check if mouse is in the selection
// if so, jump through some hoops to determine if mouse is over selection (bail)
// and whether user wants to copy selection or delete it
if (!isCollapsed) {
// We never have to delete if selection is already collapsed
bool cursorIsInSelection = false;
int32_t rangeCount;
rv = selection->GetRangeCount(&rangeCount);
NS_ENSURE_SUCCESS(rv, rv);
for (int32_t j = 0; j < rangeCount; j++) {
RefPtr<nsRange> range = selection->GetRangeAt(j);
if (!range) {
// don't bail yet, iterate through them all
continue;
}
rv = range->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection);
if (cursorIsInSelection) {
break;
}
}
if (cursorIsInSelection) {
// Dragging within same doc can't drop on itself -- leave!
if (srcdomdoc == destdomdoc) {
return NS_OK;
}
// Dragging from another window onto a selection
// XXX Decision made to NOT do this,
// note that 4.x does replace if dropped on
//deleteSelection = true;
} else {
// We are NOT over the selection
if (srcdomdoc == destdomdoc) {
// Within the same doc: delete if user doesn't want to copy
uint32_t dropEffect;
dataTransfer->GetDropEffectInt(&dropEffect);
deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY);
} else {
// Different source doc: Don't delete
deleteSelection = false;
}
}
}
if (IsPlaintextEditor()) {
nsCOMPtr<nsIContent> content = do_QueryInterface(newSelectionParent);
while (content) {
nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content));
if (formControl && !formControl->AllowDrop()) {
// Don't allow dropping into a form control that doesn't allow being
// dropped into.
return NS_OK;
}
content = content->GetParent();
}
}
for (uint32_t i = 0; i < numItems; ++i) {
InsertFromDataTransfer(dataTransfer, i, srcdomdoc, newSelectionParent,
newSelectionOffset, deleteSelection);
}
if (NS_SUCCEEDED(rv)) {
ScrollSelectionIntoView(false);
}
return rv;
}
NS_IMETHODIMP
TextEditor::Paste(int32_t aSelectionType)
{
if (!FireClipboardEvent(ePaste, aSelectionType)) {
return NS_OK;
}
// Get Clipboard Service
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
if (NS_FAILED(rv)) {
return rv;
}
// Get the nsITransferable interface for getting the data from the clipboard
nsCOMPtr<nsITransferable> trans;
rv = PrepareTransferable(getter_AddRefs(trans));
if (NS_SUCCEEDED(rv) && trans) {
// Get the Data from the clipboard
if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) &&
IsModifiable()) {
// handle transferable hooks
nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
if (!EditorHookUtils::DoInsertionHook(domdoc, nullptr, trans)) {
return NS_OK;
}
rv = InsertTextFromTransferable(trans, nullptr, 0, true);
}
}
return rv;
}
NS_IMETHODIMP
TextEditor::PasteTransferable(nsITransferable* aTransferable)
{
// Use an invalid value for the clipboard type as data comes from aTransferable
// and we don't currently implement a way to put that in the data transfer yet.
if (!FireClipboardEvent(ePaste, -1)) {
return NS_OK;
}
if (!IsModifiable()) {
return NS_OK;
}
// handle transferable hooks
nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
if (!EditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable)) {
return NS_OK;
}
return InsertTextFromTransferable(aTransferable, nullptr, 0, true);
}
NS_IMETHODIMP
TextEditor::CanPaste(int32_t aSelectionType,
bool* aCanPaste)
{
NS_ENSURE_ARG_POINTER(aCanPaste);
*aCanPaste = false;
// can't paste if readonly
if (!IsModifiable()) {
return NS_OK;
}
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
NS_ENSURE_SUCCESS(rv, rv);
// the flavors that we can deal with
const char* textEditorFlavors[] = { kUnicodeMime };
bool haveFlavors;
rv = clipboard->HasDataMatchingFlavors(textEditorFlavors,
ArrayLength(textEditorFlavors),
aSelectionType, &haveFlavors);
NS_ENSURE_SUCCESS(rv, rv);
*aCanPaste = haveFlavors;
return NS_OK;
}
NS_IMETHODIMP
TextEditor::CanPasteTransferable(nsITransferable* aTransferable,
bool* aCanPaste)
{
NS_ENSURE_ARG_POINTER(aCanPaste);
// can't paste if readonly
if (!IsModifiable()) {
*aCanPaste = false;
return NS_OK;
}
// If |aTransferable| is null, assume that a paste will succeed.
if (!aTransferable) {
*aCanPaste = true;
return NS_OK;
}
nsCOMPtr<nsISupports> data;
uint32_t dataLen;
nsresult rv = aTransferable->GetTransferData(kUnicodeMime,
getter_AddRefs(data),
&dataLen);
if (NS_SUCCEEDED(rv) && data) {
*aCanPaste = true;
} else {
*aCanPaste = false;
}
return NS_OK;
}
bool
TextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc)
{
// Try to determine whether we should use a sanitizing fragment sink
bool isSafe = false;
nsCOMPtr<nsIDocument> destdoc = GetDocument();
NS_ASSERTION(destdoc, "Where is our destination doc?");
nsCOMPtr<nsIDocShellTreeItem> dsti = destdoc->GetDocShell();
nsCOMPtr<nsIDocShellTreeItem> root;
if (dsti) {
dsti->GetRootTreeItem(getter_AddRefs(root));
}
nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(root);
uint32_t appType;
if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType))) {
isSafe = appType == nsIDocShell::APP_TYPE_EDITOR;
}
if (!isSafe && aSourceDoc) {
nsCOMPtr<nsIDocument> srcdoc = do_QueryInterface(aSourceDoc);
NS_ASSERTION(srcdoc, "Where is our source doc?");
nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal();
nsIPrincipal* destPrincipal = destdoc->NodePrincipal();
NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?");
srcPrincipal->Subsumes(destPrincipal, &isSafe);
}
return isSafe;
}
} // namespace mozilla

View file

@ -0,0 +1,259 @@
/* -*- Mode: C++ tab-width: 2 indent-tabs-mode: nil c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef DEBUG
#include "TextEditorTest.h"
#include <stdio.h>
#include "nsDebug.h"
#include "nsError.h"
#include "nsGkAtoms.h"
#include "nsIDOMCharacterData.h"
#include "nsIDOMDocument.h"
#include "nsIDOMNode.h"
#include "nsIDOMNodeList.h"
#include "nsIEditor.h"
#include "nsIHTMLEditor.h"
#include "nsIPlaintextEditor.h"
#include "nsISelection.h"
#include "nsLiteralString.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsStringFwd.h"
#define TEST_RESULT(r) { if (NS_FAILED(r)) {printf("FAILURE result=%X\n", static_cast<uint32_t>(r)); return r; } }
#define TEST_POINTER(p) { if (!p) {printf("FAILURE null pointer\n"); return NS_ERROR_NULL_POINTER; } }
TextEditorTest::TextEditorTest()
{
printf("constructed a TextEditorTest\n");
}
TextEditorTest::~TextEditorTest()
{
printf("destroyed a TextEditorTest\n");
}
void TextEditorTest::Run(nsIEditor *aEditor, int32_t *outNumTests, int32_t *outNumTestsFailed)
{
if (!aEditor) return;
mTextEditor = do_QueryInterface(aEditor);
mEditor = do_QueryInterface(aEditor);
RunUnitTest(outNumTests, outNumTestsFailed);
}
nsresult TextEditorTest::RunUnitTest(int32_t *outNumTests, int32_t *outNumTestsFailed)
{
NS_ENSURE_TRUE(outNumTests && outNumTestsFailed, NS_ERROR_NULL_POINTER);
*outNumTests = 0;
*outNumTestsFailed = 0;
nsresult rv = InitDoc();
TEST_RESULT(rv);
// shouldn't we just bail on error here?
// insert some simple text
rv = mTextEditor->InsertText(NS_LITERAL_STRING("1234567890abcdefghij1234567890"));
TEST_RESULT(rv);
(*outNumTests)++;
if (NS_FAILED(rv)) {
++(*outNumTestsFailed);
}
// insert some more text
rv = mTextEditor->InsertText(NS_LITERAL_STRING("Moreover, I am cognizant of the interrelatedness of all communities and states. I cannot sit idly by in Atlanta and not be concerned about what happens in Birmingham. Injustice anywhere is a threat to justice everywhere"));
TEST_RESULT(rv);
(*outNumTests)++;
if (NS_FAILED(rv)) {
++(*outNumTestsFailed);
}
rv = TestInsertBreak();
TEST_RESULT(rv);
(*outNumTests)++;
if (NS_FAILED(rv)) {
++(*outNumTestsFailed);
}
rv = TestTextProperties();
TEST_RESULT(rv);
(*outNumTests)++;
if (NS_FAILED(rv)) {
++(*outNumTestsFailed);
}
// get us back to the original document
rv = mEditor->Undo(12);
TEST_RESULT(rv);
return rv;
}
nsresult TextEditorTest::InitDoc()
{
nsresult rv = mEditor->SelectAll();
TEST_RESULT(rv);
rv = mEditor->DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip);
TEST_RESULT(rv);
return rv;
}
nsresult TextEditorTest::TestInsertBreak()
{
nsCOMPtr<nsISelection>selection;
nsresult rv = mEditor->GetSelection(getter_AddRefs(selection));
TEST_RESULT(rv);
TEST_POINTER(selection.get());
nsCOMPtr<nsIDOMNode>anchor;
rv = selection->GetAnchorNode(getter_AddRefs(anchor));
TEST_RESULT(rv);
TEST_POINTER(anchor.get());
selection->Collapse(anchor, 0);
// insert one break
printf("inserting a break\n");
rv = mTextEditor->InsertLineBreak();
TEST_RESULT(rv);
mEditor->DebugDumpContent();
// insert a second break adjacent to the first
printf("inserting a second break\n");
rv = mTextEditor->InsertLineBreak();
TEST_RESULT(rv);
mEditor->DebugDumpContent();
return rv;
}
nsresult TextEditorTest::TestTextProperties()
{
nsCOMPtr<nsIDOMDocument>doc;
nsresult rv = mEditor->GetDocument(getter_AddRefs(doc));
TEST_RESULT(rv);
TEST_POINTER(doc.get());
nsCOMPtr<nsIDOMNodeList>nodeList;
// XXX This is broken, text nodes are not elements.
nsAutoString textTag(NS_LITERAL_STRING("#text"));
rv = doc->GetElementsByTagName(textTag, getter_AddRefs(nodeList));
TEST_RESULT(rv);
TEST_POINTER(nodeList.get());
uint32_t count;
nodeList->GetLength(&count);
NS_ASSERTION(0!=count, "there are no text nodes in the document!");
nsCOMPtr<nsIDOMNode>textNode;
rv = nodeList->Item(count - 1, getter_AddRefs(textNode));
TEST_RESULT(rv);
TEST_POINTER(textNode.get());
// set the whole text node to bold
printf("set the whole first text node to bold\n");
nsCOMPtr<nsISelection>selection;
rv = mEditor->GetSelection(getter_AddRefs(selection));
TEST_RESULT(rv);
TEST_POINTER(selection.get());
nsCOMPtr<nsIDOMCharacterData>textData;
textData = do_QueryInterface(textNode);
uint32_t length;
textData->GetLength(&length);
selection->Collapse(textNode, 0);
selection->Extend(textNode, length);
nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(mTextEditor));
NS_ENSURE_TRUE(htmlEditor, NS_ERROR_FAILURE);
bool any = false;
bool all = false;
bool first=false;
const nsAFlatString& empty = EmptyString();
rv = htmlEditor->GetInlineProperty(nsGkAtoms::b, empty, empty, &first,
&any, &all);
TEST_RESULT(rv);
NS_ASSERTION(false==first, "first should be false");
NS_ASSERTION(false==any, "any should be false");
NS_ASSERTION(false==all, "all should be false");
rv = htmlEditor->SetInlineProperty(nsGkAtoms::b, empty, empty);
TEST_RESULT(rv);
rv = htmlEditor->GetInlineProperty(nsGkAtoms::b, empty, empty, &first,
&any, &all);
TEST_RESULT(rv);
NS_ASSERTION(true==first, "first should be true");
NS_ASSERTION(true==any, "any should be true");
NS_ASSERTION(true==all, "all should be true");
mEditor->DebugDumpContent();
// remove the bold we just set
printf("set the whole first text node to not bold\n");
rv = htmlEditor->RemoveInlineProperty(nsGkAtoms::b, empty);
TEST_RESULT(rv);
rv = htmlEditor->GetInlineProperty(nsGkAtoms::b, empty, empty, &first,
&any, &all);
TEST_RESULT(rv);
NS_ASSERTION(false==first, "first should be false");
NS_ASSERTION(false==any, "any should be false");
NS_ASSERTION(false==all, "all should be false");
mEditor->DebugDumpContent();
// set all but the first and last character to bold
printf("set the first text node (1, length-1) to bold and italic, and (2, length-1) to underline.\n");
selection->Collapse(textNode, 1);
selection->Extend(textNode, length-1);
rv = htmlEditor->SetInlineProperty(nsGkAtoms::b, empty, empty);
TEST_RESULT(rv);
rv = htmlEditor->GetInlineProperty(nsGkAtoms::b, empty, empty, &first,
&any, &all);
TEST_RESULT(rv);
NS_ASSERTION(true==first, "first should be true");
NS_ASSERTION(true==any, "any should be true");
NS_ASSERTION(true==all, "all should be true");
mEditor->DebugDumpContent();
// make all that same text italic
rv = htmlEditor->SetInlineProperty(nsGkAtoms::i, empty, empty);
TEST_RESULT(rv);
rv = htmlEditor->GetInlineProperty(nsGkAtoms::i, empty, empty, &first,
&any, &all);
TEST_RESULT(rv);
NS_ASSERTION(true==first, "first should be true");
NS_ASSERTION(true==any, "any should be true");
NS_ASSERTION(true==all, "all should be true");
rv = htmlEditor->GetInlineProperty(nsGkAtoms::b, empty, empty, &first,
&any, &all);
TEST_RESULT(rv);
NS_ASSERTION(true==first, "first should be true");
NS_ASSERTION(true==any, "any should be true");
NS_ASSERTION(true==all, "all should be true");
mEditor->DebugDumpContent();
// make all the text underlined, except for the first 2 and last 2 characters
rv = doc->GetElementsByTagName(textTag, getter_AddRefs(nodeList));
TEST_RESULT(rv);
TEST_POINTER(nodeList.get());
nodeList->GetLength(&count);
NS_ASSERTION(0!=count, "there are no text nodes in the document!");
rv = nodeList->Item(count-2, getter_AddRefs(textNode));
TEST_RESULT(rv);
TEST_POINTER(textNode.get());
textData = do_QueryInterface(textNode);
textData->GetLength(&length);
NS_ASSERTION(length==915, "wrong text node");
selection->Collapse(textNode, 1);
selection->Extend(textNode, length-2);
rv = htmlEditor->SetInlineProperty(nsGkAtoms::u, empty, empty);
TEST_RESULT(rv);
rv = htmlEditor->GetInlineProperty(nsGkAtoms::u, empty, empty, &first,
&any, &all);
TEST_RESULT(rv);
NS_ASSERTION(true==first, "first should be true");
NS_ASSERTION(true==any, "any should be true");
NS_ASSERTION(true==all, "all should be true");
mEditor->DebugDumpContent();
return rv;
}
#endif

View file

@ -0,0 +1,42 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __TextEditorTest_h__
#define __TextEditorTest_h__
#include "nscore.h"
class nsIEditor;
class nsIPlaintextEditor;
#ifdef DEBUG
#include "nsCOMPtr.h"
class TextEditorTest
{
public:
void Run(nsIEditor *aEditor, int32_t *outNumTests, int32_t *outNumTestsFailed);
TextEditorTest();
~TextEditorTest();
protected:
/** create an empty document */
nsresult InitDoc();
nsresult RunUnitTest(int32_t *outNumTests, int32_t *outNumTestsFailed);
nsresult TestInsertBreak();
nsresult TestTextProperties();
nsCOMPtr<nsIPlaintextEditor> mTextEditor;
nsCOMPtr<nsIEditor> mEditor;
};
#endif /* DEBUG */
#endif

View file

@ -0,0 +1,397 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TypeInState.h"
#include <stddef.h>
#include "nsError.h"
#include "mozilla/EditorBase.h"
#include "mozilla/mozalloc.h"
#include "mozilla/dom/Selection.h"
#include "nsAString.h"
#include "nsDebug.h"
#include "nsGkAtoms.h"
#include "nsIDOMNode.h"
#include "nsISupportsBase.h"
#include "nsISupportsImpl.h"
#include "nsReadableUtils.h"
#include "nsStringFwd.h"
class nsIAtom;
class nsIDOMDocument;
namespace mozilla {
using namespace dom;
/********************************************************************
* mozilla::TypeInState
*******************************************************************/
NS_IMPL_CYCLE_COLLECTION(TypeInState, mLastSelectionContainer)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TypeInState)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TypeInState)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TypeInState)
NS_INTERFACE_MAP_ENTRY(nsISelectionListener)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
TypeInState::TypeInState()
: mRelativeFontSize(0)
, mLastSelectionOffset(0)
{
Reset();
}
TypeInState::~TypeInState()
{
// Call Reset() to release any data that may be in
// mClearedArray and mSetArray.
Reset();
}
nsresult
TypeInState::UpdateSelState(Selection* aSelection)
{
NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
if (!aSelection->Collapsed()) {
return NS_OK;
}
return EditorBase::GetStartNodeAndOffset(
aSelection, getter_AddRefs(mLastSelectionContainer),
&mLastSelectionOffset);
}
NS_IMETHODIMP
TypeInState::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
nsISelection* aSelection,
int16_t aReason)
{
// XXX: Selection currently generates bogus selection changed notifications
// XXX: (bug 140303). It can notify us when the selection hasn't actually
// XXX: changed, and it notifies us more than once for the same change.
// XXX:
// XXX: The following code attempts to work around the bogus notifications,
// XXX: and should probably be removed once bug 140303 is fixed.
// XXX:
// XXX: This code temporarily fixes the problem where clicking the mouse in
// XXX: the same location clears the type-in-state.
RefPtr<Selection> selection =
aSelection ? aSelection->AsSelection() : nullptr;
if (aSelection) {
int32_t rangeCount = selection->RangeCount();
if (selection->Collapsed() && rangeCount) {
nsCOMPtr<nsIDOMNode> selNode;
int32_t selOffset = 0;
nsresult rv =
EditorBase::GetStartNodeAndOffset(selection, getter_AddRefs(selNode),
&selOffset);
NS_ENSURE_SUCCESS(rv, rv);
if (selNode &&
selNode == mLastSelectionContainer &&
selOffset == mLastSelectionOffset) {
// We got a bogus selection changed notification!
return NS_OK;
}
mLastSelectionContainer = selNode;
mLastSelectionOffset = selOffset;
} else {
mLastSelectionContainer = nullptr;
mLastSelectionOffset = 0;
}
}
Reset();
return NS_OK;
}
void
TypeInState::Reset()
{
for (size_t i = 0, n = mClearedArray.Length(); i < n; i++) {
delete mClearedArray[i];
}
mClearedArray.Clear();
for (size_t i = 0, n = mSetArray.Length(); i < n; i++) {
delete mSetArray[i];
}
mSetArray.Clear();
}
void
TypeInState::SetProp(nsIAtom* aProp,
const nsAString& aAttr,
const nsAString& aValue)
{
// special case for big/small, these nest
if (nsGkAtoms::big == aProp) {
mRelativeFontSize++;
return;
}
if (nsGkAtoms::small == aProp) {
mRelativeFontSize--;
return;
}
int32_t index;
if (IsPropSet(aProp, aAttr, nullptr, index)) {
// if it's already set, update the value
mSetArray[index]->value = aValue;
return;
}
// Make a new propitem and add it to the list of set properties.
mSetArray.AppendElement(new PropItem(aProp, aAttr, aValue));
// remove it from the list of cleared properties, if we have a match
RemovePropFromClearedList(aProp, aAttr);
}
void
TypeInState::ClearAllProps()
{
// null prop means "all" props
ClearProp(nullptr, EmptyString());
}
void
TypeInState::ClearProp(nsIAtom* aProp,
const nsAString& aAttr)
{
// if it's already cleared we are done
if (IsPropCleared(aProp, aAttr)) {
return;
}
// make a new propitem
PropItem* item = new PropItem(aProp, aAttr, EmptyString());
// remove it from the list of set properties, if we have a match
RemovePropFromSetList(aProp, aAttr);
// add it to the list of cleared properties
mClearedArray.AppendElement(item);
}
/**
* TakeClearProperty() hands back next property item on the clear list.
* Caller assumes ownership of PropItem and must delete it.
*/
PropItem*
TypeInState::TakeClearProperty()
{
size_t count = mClearedArray.Length();
if (!count) {
return nullptr;
}
--count; // indices are zero based
PropItem* propItem = mClearedArray[count];
mClearedArray.RemoveElementAt(count);
return propItem;
}
/**
* TakeSetProperty() hands back next poroperty item on the set list.
* Caller assumes ownership of PropItem and must delete it.
*/
PropItem*
TypeInState::TakeSetProperty()
{
size_t count = mSetArray.Length();
if (!count) {
return nullptr;
}
count--; // indices are zero based
PropItem* propItem = mSetArray[count];
mSetArray.RemoveElementAt(count);
return propItem;
}
/**
* TakeRelativeFontSize() hands back relative font value, which is then
* cleared out.
*/
int32_t
TypeInState::TakeRelativeFontSize()
{
int32_t relSize = mRelativeFontSize;
mRelativeFontSize = 0;
return relSize;
}
void
TypeInState::GetTypingState(bool& isSet,
bool& theSetting,
nsIAtom* aProp)
{
GetTypingState(isSet, theSetting, aProp, EmptyString(), nullptr);
}
void
TypeInState::GetTypingState(bool& isSet,
bool& theSetting,
nsIAtom* aProp,
const nsString& aAttr,
nsString* aValue)
{
if (IsPropSet(aProp, aAttr, aValue)) {
isSet = true;
theSetting = true;
} else if (IsPropCleared(aProp, aAttr)) {
isSet = true;
theSetting = false;
} else {
isSet = false;
}
}
void
TypeInState::RemovePropFromSetList(nsIAtom* aProp,
const nsAString& aAttr)
{
int32_t index;
if (!aProp) {
// clear _all_ props
for (size_t i = 0, n = mSetArray.Length(); i < n; i++) {
delete mSetArray[i];
}
mSetArray.Clear();
mRelativeFontSize=0;
} else if (FindPropInList(aProp, aAttr, nullptr, mSetArray, index)) {
delete mSetArray[index];
mSetArray.RemoveElementAt(index);
}
}
void
TypeInState::RemovePropFromClearedList(nsIAtom* aProp,
const nsAString& aAttr)
{
int32_t index;
if (FindPropInList(aProp, aAttr, nullptr, mClearedArray, index)) {
delete mClearedArray[index];
mClearedArray.RemoveElementAt(index);
}
}
bool
TypeInState::IsPropSet(nsIAtom* aProp,
const nsAString& aAttr,
nsAString* outValue)
{
int32_t i;
return IsPropSet(aProp, aAttr, outValue, i);
}
bool
TypeInState::IsPropSet(nsIAtom* aProp,
const nsAString& aAttr,
nsAString* outValue,
int32_t& outIndex)
{
// linear search. list should be short.
size_t count = mSetArray.Length();
for (size_t i = 0; i < count; i++) {
PropItem *item = mSetArray[i];
if (item->tag == aProp && item->attr == aAttr) {
if (outValue) {
*outValue = item->value;
}
outIndex = i;
return true;
}
}
return false;
}
bool
TypeInState::IsPropCleared(nsIAtom* aProp,
const nsAString& aAttr)
{
int32_t i;
return IsPropCleared(aProp, aAttr, i);
}
bool
TypeInState::IsPropCleared(nsIAtom* aProp,
const nsAString& aAttr,
int32_t& outIndex)
{
if (FindPropInList(aProp, aAttr, nullptr, mClearedArray, outIndex)) {
return true;
}
if (FindPropInList(0, EmptyString(), nullptr, mClearedArray, outIndex)) {
// special case for all props cleared
outIndex = -1;
return true;
}
return false;
}
bool
TypeInState::FindPropInList(nsIAtom* aProp,
const nsAString& aAttr,
nsAString* outValue,
nsTArray<PropItem*>& aList,
int32_t& outIndex)
{
// linear search. list should be short.
size_t count = aList.Length();
for (size_t i = 0; i < count; i++) {
PropItem *item = aList[i];
if (item->tag == aProp && item->attr == aAttr) {
if (outValue) {
*outValue = item->value;
}
outIndex = i;
return true;
}
}
return false;
}
/********************************************************************
* mozilla::PropItem: helper struct for mozilla::TypeInState
*******************************************************************/
PropItem::PropItem()
: tag(nullptr)
{
MOZ_COUNT_CTOR(PropItem);
}
PropItem::PropItem(nsIAtom* aTag,
const nsAString& aAttr,
const nsAString &aValue)
: tag(aTag)
, attr(aAttr)
, value(aValue)
{
MOZ_COUNT_CTOR(PropItem);
}
PropItem::~PropItem()
{
MOZ_COUNT_DTOR(PropItem);
}
} // namespace mozilla

View file

@ -0,0 +1,111 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef TypeInState_h
#define TypeInState_h
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISelectionListener.h"
#include "nsISupportsImpl.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nscore.h"
// Workaround for windows headers
#ifdef SetProp
#undef SetProp
#endif
class nsIAtom;
class nsIDOMNode;
namespace mozilla {
class HTMLEditRules;
namespace dom {
class Selection;
} // namespace dom
struct PropItem
{
nsIAtom* tag;
nsString attr;
nsString value;
PropItem();
PropItem(nsIAtom* aTag, const nsAString& aAttr, const nsAString& aValue);
~PropItem();
};
class TypeInState final : public nsISelectionListener
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(TypeInState)
TypeInState();
void Reset();
nsresult UpdateSelState(dom::Selection* aSelection);
// nsISelectionListener
NS_DECL_NSISELECTIONLISTENER
void SetProp(nsIAtom* aProp, const nsAString& aAttr, const nsAString& aValue);
void ClearAllProps();
void ClearProp(nsIAtom* aProp, const nsAString& aAttr);
/**
* TakeClearProperty() hands back next property item on the clear list.
* Caller assumes ownership of PropItem and must delete it.
*/
PropItem* TakeClearProperty();
/**
* TakeSetProperty() hands back next property item on the set list.
* Caller assumes ownership of PropItem and must delete it.
*/
PropItem* TakeSetProperty();
/**
* TakeRelativeFontSize() hands back relative font value, which is then
* cleared out.
*/
int32_t TakeRelativeFontSize();
void GetTypingState(bool& isSet, bool& theSetting, nsIAtom* aProp);
void GetTypingState(bool& isSet, bool& theSetting, nsIAtom* aProp,
const nsString& aAttr, nsString* outValue);
static bool FindPropInList(nsIAtom* aProp, const nsAString& aAttr,
nsAString* outValue, nsTArray<PropItem*>& aList,
int32_t& outIndex);
protected:
virtual ~TypeInState();
void RemovePropFromSetList(nsIAtom* aProp, const nsAString& aAttr);
void RemovePropFromClearedList(nsIAtom* aProp, const nsAString& aAttr);
bool IsPropSet(nsIAtom* aProp, const nsAString& aAttr, nsAString* outValue);
bool IsPropSet(nsIAtom* aProp, const nsAString& aAttr, nsAString* outValue,
int32_t& outIndex);
bool IsPropCleared(nsIAtom* aProp, const nsAString& aAttr);
bool IsPropCleared(nsIAtom* aProp, const nsAString& aAttr, int32_t& outIndex);
nsTArray<PropItem*> mSetArray;
nsTArray<PropItem*> mClearedArray;
int32_t mRelativeFontSize;
nsCOMPtr<nsIDOMNode> mLastSelectionContainer;
int32_t mLastSelectionOffset;
friend class HTMLEditRules;
};
} // namespace mozilla
#endif // #ifndef TypeInState_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,411 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef WSRunObject_h
#define WSRunObject_h
#include "nsCOMPtr.h"
#include "nsIEditor.h" // for EDirection
#include "nsINode.h"
#include "nscore.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/Text.h"
class nsIDOMNode;
namespace mozilla {
class HTMLEditor;
class HTMLEditRules;
struct EditorDOMPoint;
// class WSRunObject represents the entire whitespace situation
// around a given point. It collects up a list of nodes that contain
// whitespace and categorizes in up to 3 different WSFragments (detailed
// below). Each WSFragment is a collection of whitespace that is
// either all insignificant, or that is significant. A WSFragment could
// consist of insignificant whitespace because it is after a block
// boundary or after a break. Or it could be insignificant because it
// is before a block. Or it could be significant because it is
// surrounded by text, or starts and ends with nbsps, etc.
// Throughout I refer to LeadingWS, NormalWS, TrailingWS. LeadingWS & TrailingWS
// are runs of ascii ws that are insignificant (do not render) because they
// are adjacent to block boundaries, or after a break. NormalWS is ws that
// does cause soem rendering. Note that not all the ws in a NormalWS run need
// render. For example, two ascii spaces surrounded by text on both sides
// will only render as one space (in non-preformatted stlye html), yet both
// spaces count as NormalWS. Together, they render as the one visible space.
/**
* A type-safe bitfield indicating various types of whitespace or other things.
* Used as a member variable in WSRunObject and WSFragment.
*
* XXX: If this idea is useful in other places, we should generalize it using a
* template.
*/
class WSType
{
public:
enum Enum
{
none = 0,
leadingWS = 1, // leading insignificant ws, ie, after block or br
trailingWS = 1 << 1, // trailing insignificant ws, ie, before block
normalWS = 1 << 2, // normal significant ws, ie, after text, image, ...
text = 1 << 3, // indicates regular (non-ws) text
special = 1 << 4, // indicates an inline non-container, like image
br = 1 << 5, // indicates a br node
otherBlock = 1 << 6, // indicates a block other than one ws run is in
thisBlock = 1 << 7, // indicates the block ws run is in
block = otherBlock | thisBlock // block found
};
/**
* Implicit constructor, because the enums are logically just WSTypes
* themselves, and are only a separate type because there's no other obvious
* way to name specific WSType values.
*/
MOZ_IMPLICIT WSType(const Enum& aEnum = none)
: mEnum(aEnum)
{}
// operator==, &, and | need to access mEnum
friend bool operator==(const WSType& aLeft, const WSType& aRight);
friend const WSType operator&(const WSType& aLeft, const WSType& aRight);
friend const WSType operator|(const WSType& aLeft, const WSType& aRight);
WSType& operator=(const WSType& aOther)
{
// This handles self-assignment fine
mEnum = aOther.mEnum;
return *this;
}
WSType& operator&=(const WSType& aOther)
{
mEnum &= aOther.mEnum;
return *this;
}
WSType& operator|=(const WSType& aOther)
{
mEnum |= aOther.mEnum;
return *this;
}
private:
uint16_t mEnum;
void bool_conversion_helper() {}
public:
// Allow boolean conversion with no numeric conversion
typedef void (WSType::*bool_type)();
operator bool_type() const
{
return mEnum ? &WSType::bool_conversion_helper : nullptr;
}
};
/**
* These are declared as global functions so "WSType::Enum == WSType" et al.
* will work using the implicit constructor.
*/
inline bool operator==(const WSType& aLeft, const WSType& aRight)
{
return aLeft.mEnum == aRight.mEnum;
}
inline bool operator!=(const WSType& aLeft, const WSType& aRight)
{
return !(aLeft == aRight);
}
inline const WSType operator&(const WSType& aLeft, const WSType& aRight)
{
WSType ret;
ret.mEnum = aLeft.mEnum & aRight.mEnum;
return ret;
}
inline const WSType operator|(const WSType& aLeft, const WSType& aRight)
{
WSType ret;
ret.mEnum = aLeft.mEnum | aRight.mEnum;
return ret;
}
/**
* Make sure that & and | of WSType::Enum creates a WSType instead of an int,
* because operators between WSType and int shouldn't work
*/
inline const WSType operator&(const WSType::Enum& aLeft,
const WSType::Enum& aRight)
{
return WSType(aLeft) & WSType(aRight);
}
inline const WSType operator|(const WSType::Enum& aLeft,
const WSType::Enum& aRight)
{
return WSType(aLeft) | WSType(aRight);
}
class MOZ_STACK_CLASS WSRunObject final
{
public:
enum BlockBoundary
{
kBeforeBlock,
kBlockStart,
kBlockEnd,
kAfterBlock
};
enum {eBefore = 1};
enum {eAfter = 1 << 1};
enum {eBoth = eBefore | eAfter};
WSRunObject(HTMLEditor* aHTMLEditor, nsINode* aNode, int32_t aOffset);
WSRunObject(HTMLEditor* aHTMLEditor, nsIDOMNode* aNode, int32_t aOffset);
~WSRunObject();
// ScrubBlockBoundary removes any non-visible whitespace at the specified
// location relative to a block node.
static nsresult ScrubBlockBoundary(HTMLEditor* aHTMLEditor,
BlockBoundary aBoundary,
nsINode* aBlock,
int32_t aOffset = -1);
// PrepareToJoinBlocks fixes up ws at the end of aLeftBlock and the
// beginning of aRightBlock in preperation for them to be joined. Example
// of fixup: trailingws in aLeftBlock needs to be removed.
static nsresult PrepareToJoinBlocks(HTMLEditor* aHTMLEditor,
dom::Element* aLeftBlock,
dom::Element* aRightBlock);
// PrepareToDeleteRange fixes up ws before {aStartNode,aStartOffset}
// and after {aEndNode,aEndOffset} in preperation for content
// in that range to be deleted. Note that the nodes and offsets
// are adjusted in response to any dom changes we make while
// adjusting ws.
// example of fixup: trailingws before {aStartNode,aStartOffset}
// needs to be removed.
static nsresult PrepareToDeleteRange(HTMLEditor* aHTMLEditor,
nsCOMPtr<nsINode>* aStartNode,
int32_t* aStartOffset,
nsCOMPtr<nsINode>* aEndNode,
int32_t* aEndOffset);
// PrepareToDeleteNode fixes up ws before and after aContent in preparation
// for aContent to be deleted. Example of fixup: trailingws before
// aContent needs to be removed.
static nsresult PrepareToDeleteNode(HTMLEditor* aHTMLEditor,
nsIContent* aContent);
// PrepareToSplitAcrossBlocks fixes up ws before and after
// {aSplitNode,aSplitOffset} in preparation for a block parent to be split.
// Note that the aSplitNode and aSplitOffset are adjusted in response to
// any DOM changes we make while adjusting ws. Example of fixup: normalws
// before {aSplitNode,aSplitOffset} needs to end with nbsp.
static nsresult PrepareToSplitAcrossBlocks(HTMLEditor* aHTMLEditor,
nsCOMPtr<nsINode>* aSplitNode,
int32_t* aSplitOffset);
// InsertBreak inserts a br node at {aInOutParent,aInOutOffset}
// and makes any needed adjustments to ws around that point.
// example of fixup: normalws after {aInOutParent,aInOutOffset}
// needs to begin with nbsp.
already_AddRefed<dom::Element> InsertBreak(nsCOMPtr<nsINode>* aInOutParent,
int32_t* aInOutOffset,
nsIEditor::EDirection aSelect);
// InsertText inserts a string at {aInOutParent,aInOutOffset} and makes any
// needed adjustments to ws around that point. Example of fixup:
// trailingws before {aInOutParent,aInOutOffset} needs to be removed.
nsresult InsertText(const nsAString& aStringToInsert,
nsCOMPtr<nsINode>* aInOutNode,
int32_t* aInOutOffset,
nsIDocument* aDoc);
// DeleteWSBackward deletes a single visible piece of ws before the ws
// point (the point to create the wsRunObject, passed to its constructor).
// It makes any needed conversion to adjacent ws to retain its
// significance.
nsresult DeleteWSBackward();
// DeleteWSForward deletes a single visible piece of ws after the ws point
// (the point to create the wsRunObject, passed to its constructor). It
// makes any needed conversion to adjacent ws to retain its significance.
nsresult DeleteWSForward();
// PriorVisibleNode returns the first piece of visible thing before
// {aNode,aOffset}. If there is no visible ws qualifying it returns what
// is before the ws run. Note that {outVisNode,outVisOffset} is set to
// just AFTER the visible object.
void PriorVisibleNode(nsINode* aNode,
int32_t aOffset,
nsCOMPtr<nsINode>* outVisNode,
int32_t* outVisOffset,
WSType* outType);
// NextVisibleNode returns the first piece of visible thing after
// {aNode,aOffset}. If there is no visible ws qualifying it returns what
// is after the ws run. Note that {outVisNode,outVisOffset} is set to just
// BEFORE the visible object.
void NextVisibleNode(nsINode* aNode,
int32_t aOffset,
nsCOMPtr<nsINode>* outVisNode,
int32_t* outVisOffset,
WSType* outType);
// AdjustWhitespace examines the ws object for nbsp's that can
// be safely converted to regular ascii space and converts them.
nsresult AdjustWhitespace();
protected:
// WSFragment represents a single run of ws (all leadingws, or all normalws,
// or all trailingws, or all leading+trailingws). Note that this single run
// may still span multiple nodes.
struct WSFragment final
{
nsCOMPtr<nsINode> mStartNode; // node where ws run starts
nsCOMPtr<nsINode> mEndNode; // node where ws run ends
int32_t mStartOffset; // offset where ws run starts
int32_t mEndOffset; // offset where ws run ends
// type of ws, and what is to left and right of it
WSType mType, mLeftType, mRightType;
// other ws runs to left or right. may be null.
WSFragment *mLeft, *mRight;
WSFragment()
: mStartOffset(0)
, mEndOffset(0)
, mLeft(nullptr)
, mRight(nullptr)
{}
};
// A WSPoint struct represents a unique location within the ws run. It is
// always within a textnode that is one of the nodes stored in the list
// in the wsRunObject. For convenience, the character at that point is also
// stored in the struct.
struct MOZ_STACK_CLASS WSPoint final
{
RefPtr<dom::Text> mTextNode;
uint32_t mOffset;
char16_t mChar;
WSPoint()
: mTextNode(nullptr)
, mOffset(0)
, mChar(0)
{}
WSPoint(dom::Text* aTextNode, int32_t aOffset, char16_t aChar)
: mTextNode(aTextNode)
, mOffset(aOffset)
, mChar(aChar)
{}
};
enum AreaRestriction
{
eAnywhere, eOutsideUserSelectAll
};
/**
* Return the node which we will handle white-space under. This is the
* closest block within the DOM subtree we're editing, or if none is
* found, the (inline) root of the editable subtree.
*/
nsINode* GetWSBoundingParent();
nsresult GetWSNodes();
void GetRuns();
void ClearRuns();
void MakeSingleWSRun(WSType aType);
nsIContent* GetPreviousWSNodeInner(nsINode* aStartNode,
nsINode* aBlockParent);
nsIContent* GetPreviousWSNode(EditorDOMPoint aPoint, nsINode* aBlockParent);
nsIContent* GetNextWSNodeInner(nsINode* aStartNode, nsINode* aBlockParent);
nsIContent* GetNextWSNode(EditorDOMPoint aPoint, nsINode* aBlockParent);
nsresult PrepareToDeleteRangePriv(WSRunObject* aEndObject);
nsresult PrepareToSplitAcrossBlocksPriv();
nsresult DeleteChars(nsINode* aStartNode, int32_t aStartOffset,
nsINode* aEndNode, int32_t aEndOffset,
AreaRestriction aAR = eAnywhere);
WSPoint GetCharAfter(nsINode* aNode, int32_t aOffset);
WSPoint GetCharBefore(nsINode* aNode, int32_t aOffset);
WSPoint GetCharAfter(const WSPoint& aPoint);
WSPoint GetCharBefore(const WSPoint& aPoint);
nsresult ConvertToNBSP(WSPoint aPoint,
AreaRestriction aAR = eAnywhere);
void GetAsciiWSBounds(int16_t aDir, nsINode* aNode, int32_t aOffset,
dom::Text** outStartNode, int32_t* outStartOffset,
dom::Text** outEndNode, int32_t* outEndOffset);
void FindRun(nsINode* aNode, int32_t aOffset, WSFragment** outRun,
bool after);
char16_t GetCharAt(dom::Text* aTextNode, int32_t aOffset);
WSPoint GetWSPointAfter(nsINode* aNode, int32_t aOffset);
WSPoint GetWSPointBefore(nsINode* aNode, int32_t aOffset);
nsresult CheckTrailingNBSPOfRun(WSFragment *aRun);
nsresult CheckTrailingNBSP(WSFragment* aRun, nsINode* aNode,
int32_t aOffset);
nsresult CheckLeadingNBSP(WSFragment* aRun, nsINode* aNode,
int32_t aOffset);
nsresult Scrub();
bool IsBlockNode(nsINode* aNode);
// The node passed to our constructor.
nsCOMPtr<nsINode> mNode;
// The offset passed to our contructor.
int32_t mOffset;
// Together, the above represent the point at which we are building up ws info.
// true if we are in preformatted whitespace context.
bool mPRE;
// Node/offset where ws starts.
nsCOMPtr<nsINode> mStartNode;
int32_t mStartOffset;
// Reason why ws starts (eText, eOtherBlock, etc.).
WSType mStartReason;
// The node that implicated by start reason.
nsCOMPtr<nsINode> mStartReasonNode;
// Node/offset where ws ends.
nsCOMPtr<nsINode> mEndNode;
int32_t mEndOffset;
// Reason why ws ends (eText, eOtherBlock, etc.).
WSType mEndReason;
// The node that implicated by end reason.
nsCOMPtr<nsINode> mEndReasonNode;
// Location of first nbsp in ws run, if any.
RefPtr<dom::Text> mFirstNBSPNode;
int32_t mFirstNBSPOffset;
// Location of last nbsp in ws run, if any.
RefPtr<dom::Text> mLastNBSPNode;
int32_t mLastNBSPOffset;
// The list of nodes containing ws in this run.
nsTArray<RefPtr<dom::Text>> mNodeArray;
// The first WSFragment in the run.
WSFragment* mStartRun;
// The last WSFragment in the run, may be same as first.
WSFragment* mEndRun;
// Non-owning.
HTMLEditor* mHTMLEditor;
// Opening this class up for pillaging.
friend class HTMLEditRules;
// Opening this class up for more pillaging.
friend class HTMLEditor;
};
} // namespace mozilla
#endif // #ifndef WSRunObject_h

View file

@ -0,0 +1,9 @@
<html><body></body><script>
document.designMode = "on";
var hrElem = document.createElement("HR");
var select = window.getSelection();
document.body.appendChild(hrElem);
select.collapse(hrElem,0);
document.execCommand("InsertHTML", false, "<div>foo</div><div>bar</div>");
</script>
</html>

View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>Bug 1128787</title>
</head>
<body>
<input type="button"/>
<script>
window.onload = function () {
document.designMode = "on";
}
var input = document.getElementsByTagName("input")[0];
input.focus();
input.type = "text";
</script>
</body>
</html>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<!-- saved from url=(0065)https://bug1134545.bugzilla.mozilla.org/attachment.cgi?id=8566418 -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<script>
function boom()
{
textNode = document.createTextNode(" ");
x.appendChild(textNode);
x.setAttribute('contenteditable', "true");
textNode.remove();
window.getSelection().selectAllChildren(textNode);
document.execCommand("increasefontsize", false, null);
}
</script>
</head>
<body onload="boom();">
<div id="x" contenteditable="true"></div>
</body></html>

View file

@ -0,0 +1,10 @@
<div>
<div>
aaaaaaa
</script>
<script type="text/javascript">
document.designMode = "on"
window.getSelection().modify("extend", "backward", "line")
document.execCommand("increasefontsize","",null);
</script>

View file

@ -0,0 +1,18 @@
<script>
onload = function() {
var testContainer = document.createElement("span");
testContainer.contentEditable = true;
document.body.appendChild(testContainer);
function queryFormatBlock(content)
{
testContainer.innerHTML = content;
while (testContainer.firstChild)
testContainer = testContainer.firstChild;
window.getSelection().collapse(testContainer, 0);
document.queryCommandValue('formatBlock');
}
queryFormatBlock('<ol>hello</ol>');
};
</script>

View file

@ -0,0 +1,21 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
function boom()
{
document.designMode = 'on';
document.execCommand("indent", false, null);
document.execCommand("insertText", false, "a");
document.execCommand("forwardDelete", false, null);
document.execCommand("justifyfull", false, null);
}
window.addEventListener("load", boom, false);
</script>
</head>
<body> <span class="v"></span></body><body><input type="file" /></body>
</html>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.onload = function () {
var childDocument = document.getElementsByTagName("iframe")[0].contentDocument;
childDocument.designMode = "on";
function onAttrModified(aEvent) {
childDocument.removeEventListener("DOMAttrModified", onAttrModified, false);
// Remove the editor from document during executing "insertOrderedList".
document.body.innerHTML = "";
}
childDocument.addEventListener("DOMAttrModified", onAttrModified, false);
childDocument.execCommand("insertOrderedList", false, "1");
}
</script>
</head>
<body><iframe></iframe></body>
</html>

View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
addEventListener('DOMContentLoaded', function(){
document.documentElement.className = 'lizard';
setTimeout(function(){
document.execCommand('selectAll', false, null);
document.designMode = 'on';
document.execCommand('removeformat', false, null);
}, 0);
});
</script>
<style>
.lizard{
-webkit-user-select:all;
}
*{
position:fixed;
display:table-column;
}
</style>
</head>
<body>
<span>
<span contenteditable>
<span class=lizard></span>
<span class=lizard></span>
<span />
</span>
</span>
</span>
</span>
</body>
</html>

View file

@ -0,0 +1,52 @@
<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
<head>
<script>
<![CDATA[
function foop(targetWindow)
{
var targetDocument = targetWindow.document;
var r1 = targetDocument.createRange();
r1.setStart(targetDocument.getElementById("out1"), 0);
r1.setEnd (targetDocument.getElementById("out2"), 0);
targetWindow.getSelection().addRange(r1);
var r2 = targetDocument.createRange();
r2.setStart(targetDocument.getElementById("in1"), 0);
r2.setEnd (targetDocument.getElementById("in2"), 0);
targetWindow.getSelection().addRange(r2);
targetDocument.execCommand('removeformat', false, null);
targetDocument.execCommand('outdent', false, null);
}
function init()
{
setTimeout(function()
{
var fd = window.frames[0].document;
fd.body.appendChild(fd.importNode(document.getElementById('rootish'), true));
fd.designMode = 'on';
foop(window.frames[0]);
document.documentElement.removeAttribute("class");
}, 100);
}
]]>
</script>
</head>
<body onload="init()">
<iframe src="data:text/html," style="width: 95%; height: 500px;"/>
<div id="rootish">
<div id="out1"/>
<div id="in1"/>
<div id="in2"/>
<div id="out2"/>
</div>
</body>
</html>

View file

@ -0,0 +1,37 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
function init()
{
var targetWindow = window.frames[0];
var targetDocument = targetWindow.document;
var rootish = document.getElementById('rootish');
targetDocument.body.appendChild(targetDocument.adoptNode(rootish));
targetDocument.designMode = 'on';
targetWindow.getSelection().removeAllRanges();
var r = targetDocument.createRange();
r.setStart(targetDocument.getElementById("start"), 0);
r.setEnd (targetDocument.getElementById("endparent").firstChild, 0);
targetWindow.getSelection().addRange(r);
targetDocument.execCommand('outdent', false, null);
}
</script>
</head>
<body onload="setTimeout(init, 300);">
<iframe src="data:text/html," style="width: 95%; height: 500px;"></iframe>
<div id="rootish">
<div id="start"></div>
<p>Huh</p>
<svg xmlns="http://www.w3.org/2000/svg" id="endparent"> </svg>
</div>
</body>
</html>

View file

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html class="reftest-wait">
<head>
<script>
function init1()
{
targetIframe = document.createElementNS('http://www.w3.org/1999/xhtml', 'iframe');
targetIframe.src = "data:text/html,";
targetIframe.setAttribute("style", "width: 300px; height: 200px; border: 1px dotted green;");
targetIframe.addEventListener("load", init2, false);
document.body.appendChild(targetIframe);
}
function init2()
{
targetWindow = targetIframe.contentWindow;
targetDocument = targetWindow.document;
var div = document.getElementById("div");
textNode = div.firstChild;
targetDocument.body.appendChild(targetDocument.adoptNode(div, true));
targetDocument.designMode = 'on';
setTimeout(init3, 0);
}
function init3()
{
var rng = targetDocument.createRange();
rng.setStart(textNode, 1);
rng.setEnd(textNode, 1);
targetWindow.getSelection().addRange(rng);
try {
targetDocument.execCommand("inserthtml", false, "<p>");
} catch(e) {}
document.documentElement.removeAttribute("class");
}
</script>
</head>
<body onload="init1();">
<div id="div"> </div>
<script>
</script>
</body>
</html>

View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html class="reftest-wait">
<head>
<script>
function init1()
{
// Create an html:iframe in HTML mode (so designMode can be used 320092)
targetIframe = document.createElementNS('http://www.w3.org/1999/xhtml', 'iframe');
targetIframe.src = "data:text/html,";
targetIframe.setAttribute("style", "width: 700px; height: 500px; border: 1px dotted green;");
targetIframe.addEventListener("load", init2, false);
document.body.appendChild(targetIframe);
}
function init2()
{
targetWindow = targetIframe.contentWindow;
targetDocument = targetWindow.document;
p = document.getElementById("p");
pText = p.firstChild;
targetDocument.body.appendChild(targetDocument.adoptNode(p, true));
targetDocument.designMode = 'on';
setTimeout(boom, 0);
}
function boom()
{
var rng = targetDocument.createRange();
rng.setStart(pText, 3);
rng.setEnd(pText, 3);
targetWindow.getSelection().addRange(rng);
targetDocument.execCommand("insertorderedlist", false, null);
document.documentElement.removeAttribute("class")
}
</script>
</head>
<body onload="init1();">
<p id="p">word word</p>
</body>
</html>

View file

@ -0,0 +1,23 @@
<html>
<head>
<script>
function boom()
{
document.getElementById("div").contentEditable = "true";
document.getElementById("div").focus();
document.getElementById("div").contentEditable = "false";
document.getElementById("table").contentEditable = "true";
}
</script>
</head>
<body onload="boom();">
<table id="table"><td></td></table><div id="div"></div>
</body>
</html>

View file

@ -0,0 +1,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<tbody contenteditable="true"/>
<frameset/>
<xul:box onbeforecopy="event.explicitOriginalTarget.parentNode.parentNode.removeChild(event.explicitOriginalTarget.parentNode)">
<xul:box/>
</xul:box>
</html>

View file

@ -0,0 +1,7 @@
<html>
<head>
</head>
<body onload="try { document.execCommand('inserthtml', false, '0'); } catch(e) { }"><span id="textarea" contenteditable="true">is</span></body>
</html>

View file

@ -0,0 +1,15 @@
<html>
<head>
<script type="text/javascript">
function boom()
{
document.execCommand("selectAll", false, null);
document.execCommand("inserthtml", false, "<p>");
}
</script>
</head>
<body onload="boom();"><textarea contenteditable="true"></textarea></body>
</html>

View file

@ -0,0 +1,23 @@
<html>
<head>
<script type="text/javascript">
function boom()
{
document.addEventListener("DOMNodeInserted", x, false);
function x()
{
document.removeEventListener("DOMNodeInserted", x, false);
document.execCommand("insertParagraph", false, "");
}
document.execCommand("insertorderedlist", false, "");
}
</script>
</head>
<body contenteditable="true" onload="boom()"></body>
</html>

Some files were not shown because too many files have changed in this diff Show more