mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-01 04:47:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
1422
editor/libeditor/CSSEditUtils.cpp
Normal file
1422
editor/libeditor/CSSEditUtils.cpp
Normal file
File diff suppressed because it is too large
Load diff
473
editor/libeditor/CSSEditUtils.h
Normal file
473
editor/libeditor/CSSEditUtils.h
Normal 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
|
||||
98
editor/libeditor/ChangeAttributeTransaction.cpp
Normal file
98
editor/libeditor/ChangeAttributeTransaction.cpp
Normal 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
|
||||
72
editor/libeditor/ChangeAttributeTransaction.h
Normal file
72
editor/libeditor/ChangeAttributeTransaction.h
Normal 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
|
||||
287
editor/libeditor/ChangeStyleTransaction.cpp
Normal file
287
editor/libeditor/ChangeStyleTransaction.cpp
Normal 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
|
||||
123
editor/libeditor/ChangeStyleTransaction.h
Normal file
123
editor/libeditor/ChangeStyleTransaction.h
Normal 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
|
||||
332
editor/libeditor/CompositionTransaction.cpp
Normal file
332
editor/libeditor/CompositionTransaction.cpp
Normal 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
|
||||
104
editor/libeditor/CompositionTransaction.h
Normal file
104
editor/libeditor/CompositionTransaction.h
Normal 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
|
||||
147
editor/libeditor/CreateElementTransaction.cpp
Normal file
147
editor/libeditor/CreateElementTransaction.cpp
Normal 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
|
||||
80
editor/libeditor/CreateElementTransaction.h
Normal file
80
editor/libeditor/CreateElementTransaction.h
Normal 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
|
||||
123
editor/libeditor/DeleteNodeTransaction.cpp
Normal file
123
editor/libeditor/DeleteNodeTransaction.cpp
Normal 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
|
||||
66
editor/libeditor/DeleteNodeTransaction.h
Normal file
66
editor/libeditor/DeleteNodeTransaction.h
Normal 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
|
||||
239
editor/libeditor/DeleteRangeTransaction.cpp
Normal file
239
editor/libeditor/DeleteRangeTransaction.cpp
Normal 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
|
||||
78
editor/libeditor/DeleteRangeTransaction.h
Normal file
78
editor/libeditor/DeleteRangeTransaction.h
Normal 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
|
||||
102
editor/libeditor/DeleteTextTransaction.cpp
Normal file
102
editor/libeditor/DeleteTextTransaction.cpp
Normal 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
|
||||
76
editor/libeditor/DeleteTextTransaction.h
Normal file
76
editor/libeditor/DeleteTextTransaction.h
Normal 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
|
||||
17
editor/libeditor/EditActionListener.h
Normal file
17
editor/libeditor/EditActionListener.h
Normal 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__ */
|
||||
145
editor/libeditor/EditAggregateTransaction.cpp
Normal file
145
editor/libeditor/EditAggregateTransaction.cpp
Normal 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
|
||||
58
editor/libeditor/EditAggregateTransaction.h
Normal file
58
editor/libeditor/EditAggregateTransaction.h
Normal 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
|
||||
55
editor/libeditor/EditTransactionBase.cpp
Normal file
55
editor/libeditor/EditTransactionBase.cpp
Normal 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
|
||||
44
editor/libeditor/EditTransactionBase.h
Normal file
44
editor/libeditor/EditTransactionBase.h
Normal 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
|
||||
5244
editor/libeditor/EditorBase.cpp
Normal file
5244
editor/libeditor/EditorBase.cpp
Normal file
File diff suppressed because it is too large
Load diff
1046
editor/libeditor/EditorBase.h
Normal file
1046
editor/libeditor/EditorBase.h
Normal file
File diff suppressed because it is too large
Load diff
1169
editor/libeditor/EditorCommands.cpp
Normal file
1169
editor/libeditor/EditorCommands.cpp
Normal file
File diff suppressed because it is too large
Load diff
106
editor/libeditor/EditorCommands.h
Normal file
106
editor/libeditor/EditorCommands.h
Normal 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_
|
||||
146
editor/libeditor/EditorController.cpp
Normal file
146
editor/libeditor/EditorController.cpp
Normal 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
|
||||
37
editor/libeditor/EditorController.h
Normal file
37
editor/libeditor/EditorController.h
Normal 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
|
||||
1215
editor/libeditor/EditorEventListener.cpp
Normal file
1215
editor/libeditor/EditorEventListener.cpp
Normal file
File diff suppressed because it is too large
Load diff
102
editor/libeditor/EditorEventListener.h
Normal file
102
editor/libeditor/EditorEventListener.h
Normal 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
|
||||
227
editor/libeditor/EditorUtils.cpp
Normal file
227
editor/libeditor/EditorUtils.cpp
Normal 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
|
||||
315
editor/libeditor/EditorUtils.h
Normal file
315
editor/libeditor/EditorUtils.h
Normal 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
|
||||
34
editor/libeditor/EditorUtils.js
Normal file
34
editor/libeditor/EditorUtils.js
Normal 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]);
|
||||
2
editor/libeditor/EditorUtils.manifest
Normal file
2
editor/libeditor/EditorUtils.manifest
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
component {12e63991-86ac-4dff-bb1a-703495d67d17} EditorUtils.js
|
||||
contract @mozilla.org/editor-utils;1 {12e63991-86ac-4dff-bb1a-703495d67d17}
|
||||
682
editor/libeditor/HTMLAbsPositionEditor.cpp
Normal file
682
editor/libeditor/HTMLAbsPositionEditor.cpp
Normal 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
|
||||
548
editor/libeditor/HTMLAnonymousNodeEditor.cpp
Normal file
548
editor/libeditor/HTMLAnonymousNodeEditor.cpp
Normal 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
|
||||
8785
editor/libeditor/HTMLEditRules.cpp
Normal file
8785
editor/libeditor/HTMLEditRules.cpp
Normal file
File diff suppressed because it is too large
Load diff
377
editor/libeditor/HTMLEditRules.h
Normal file
377
editor/libeditor/HTMLEditRules.h
Normal 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
|
||||
|
||||
842
editor/libeditor/HTMLEditUtils.cpp
Normal file
842
editor/libeditor/HTMLEditUtils.cpp
Normal 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
|
||||
69
editor/libeditor/HTMLEditUtils.h
Normal file
69
editor/libeditor/HTMLEditUtils.h
Normal 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
|
||||
|
||||
5289
editor/libeditor/HTMLEditor.cpp
Normal file
5289
editor/libeditor/HTMLEditor.cpp
Normal file
File diff suppressed because it is too large
Load diff
1109
editor/libeditor/HTMLEditor.h
Normal file
1109
editor/libeditor/HTMLEditor.h
Normal file
File diff suppressed because it is too large
Load diff
2409
editor/libeditor/HTMLEditorDataTransfer.cpp
Normal file
2409
editor/libeditor/HTMLEditorDataTransfer.cpp
Normal file
File diff suppressed because it is too large
Load diff
215
editor/libeditor/HTMLEditorEventListener.cpp
Normal file
215
editor/libeditor/HTMLEditorEventListener.cpp
Normal 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
|
||||
43
editor/libeditor/HTMLEditorEventListener.h
Normal file
43
editor/libeditor/HTMLEditorEventListener.h
Normal 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
|
||||
1059
editor/libeditor/HTMLEditorObjectResizer.cpp
Normal file
1059
editor/libeditor/HTMLEditorObjectResizer.cpp
Normal file
File diff suppressed because it is too large
Load diff
82
editor/libeditor/HTMLEditorObjectResizerUtils.h
Normal file
82
editor/libeditor/HTMLEditorObjectResizerUtils.h
Normal 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
|
||||
283
editor/libeditor/HTMLInlineTableEditor.cpp
Normal file
283
editor/libeditor/HTMLInlineTableEditor.cpp
Normal 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
|
||||
1752
editor/libeditor/HTMLStyleEditor.cpp
Normal file
1752
editor/libeditor/HTMLStyleEditor.cpp
Normal file
File diff suppressed because it is too large
Load diff
3458
editor/libeditor/HTMLTableEditor.cpp
Normal file
3458
editor/libeditor/HTMLTableEditor.cpp
Normal file
File diff suppressed because it is too large
Load diff
262
editor/libeditor/HTMLURIRefObject.cpp
Normal file
262
editor/libeditor/HTMLURIRefObject.cpp
Normal 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;
|
||||
}
|
||||
48
editor/libeditor/HTMLURIRefObject.h
Normal file
48
editor/libeditor/HTMLURIRefObject.h
Normal 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
|
||||
97
editor/libeditor/InsertNodeTransaction.cpp
Normal file
97
editor/libeditor/InsertNodeTransaction.cpp
Normal 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
|
||||
58
editor/libeditor/InsertNodeTransaction.h
Normal file
58
editor/libeditor/InsertNodeTransaction.h
Normal 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
|
||||
125
editor/libeditor/InsertTextTransaction.cpp
Normal file
125
editor/libeditor/InsertTextTransaction.cpp
Normal 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
|
||||
88
editor/libeditor/InsertTextTransaction.h
Normal file
88
editor/libeditor/InsertTextTransaction.h
Normal 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
|
||||
366
editor/libeditor/InternetCiter.cpp
Normal file
366
editor/libeditor/InternetCiter.cpp
Normal 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
|
||||
40
editor/libeditor/InternetCiter.h
Normal file
40
editor/libeditor/InternetCiter.h
Normal 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
|
||||
109
editor/libeditor/JoinNodeTransaction.cpp
Normal file
109
editor/libeditor/JoinNodeTransaction.cpp
Normal 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
|
||||
68
editor/libeditor/JoinNodeTransaction.h
Normal file
68
editor/libeditor/JoinNodeTransaction.h
Normal 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
|
||||
270
editor/libeditor/PlaceholderTransaction.cpp
Normal file
270
editor/libeditor/PlaceholderTransaction.cpp
Normal 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
|
||||
92
editor/libeditor/PlaceholderTransaction.h
Normal file
92
editor/libeditor/PlaceholderTransaction.h
Normal 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
|
||||
695
editor/libeditor/SelectionState.cpp
Normal file
695
editor/libeditor/SelectionState.cpp
Normal 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
|
||||
357
editor/libeditor/SelectionState.h
Normal file
357
editor/libeditor/SelectionState.h
Normal 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
|
||||
229
editor/libeditor/SetDocumentTitleTransaction.cpp
Normal file
229
editor/libeditor/SetDocumentTitleTransaction.cpp
Normal 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
|
||||
60
editor/libeditor/SetDocumentTitleTransaction.h
Normal file
60
editor/libeditor/SetDocumentTitleTransaction.h
Normal 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
|
||||
128
editor/libeditor/SplitNodeTransaction.cpp
Normal file
128
editor/libeditor/SplitNodeTransaction.cpp
Normal 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
|
||||
71
editor/libeditor/SplitNodeTransaction.h
Normal file
71
editor/libeditor/SplitNodeTransaction.h
Normal 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
|
||||
157
editor/libeditor/StyleSheetTransactions.cpp
Normal file
157
editor/libeditor/StyleSheetTransactions.cpp
Normal 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
|
||||
73
editor/libeditor/StyleSheetTransactions.h
Normal file
73
editor/libeditor/StyleSheetTransactions.h
Normal 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
|
||||
1494
editor/libeditor/TextEditRules.cpp
Normal file
1494
editor/libeditor/TextEditRules.cpp
Normal file
File diff suppressed because it is too large
Load diff
357
editor/libeditor/TextEditRules.h
Normal file
357
editor/libeditor/TextEditRules.h
Normal 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
|
||||
100
editor/libeditor/TextEditRulesBidi.cpp
Normal file
100
editor/libeditor/TextEditRulesBidi.cpp
Normal 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
|
||||
113
editor/libeditor/TextEditUtils.cpp
Normal file
113
editor/libeditor/TextEditUtils.cpp
Normal 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
|
||||
47
editor/libeditor/TextEditUtils.h
Normal file
47
editor/libeditor/TextEditUtils.h
Normal 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
|
||||
1638
editor/libeditor/TextEditor.cpp
Normal file
1638
editor/libeditor/TextEditor.cpp
Normal file
File diff suppressed because it is too large
Load diff
246
editor/libeditor/TextEditor.h
Normal file
246
editor/libeditor/TextEditor.h
Normal 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
|
||||
472
editor/libeditor/TextEditorDataTransfer.cpp
Normal file
472
editor/libeditor/TextEditorDataTransfer.cpp
Normal 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
|
||||
259
editor/libeditor/TextEditorTest.cpp
Normal file
259
editor/libeditor/TextEditorTest.cpp
Normal 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
|
||||
42
editor/libeditor/TextEditorTest.h
Normal file
42
editor/libeditor/TextEditorTest.h
Normal 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
|
||||
397
editor/libeditor/TypeInState.cpp
Normal file
397
editor/libeditor/TypeInState.cpp
Normal 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
|
||||
111
editor/libeditor/TypeInState.h
Normal file
111
editor/libeditor/TypeInState.h
Normal 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
|
||||
|
||||
1926
editor/libeditor/WSRunObject.cpp
Normal file
1926
editor/libeditor/WSRunObject.cpp
Normal file
File diff suppressed because it is too large
Load diff
411
editor/libeditor/WSRunObject.h
Normal file
411
editor/libeditor/WSRunObject.h
Normal 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
|
||||
9
editor/libeditor/crashtests/1057677.html
Normal file
9
editor/libeditor/crashtests/1057677.html
Normal 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>
|
||||
17
editor/libeditor/crashtests/1128787.html
Normal file
17
editor/libeditor/crashtests/1128787.html
Normal 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>
|
||||
22
editor/libeditor/crashtests/1134545.html
Normal file
22
editor/libeditor/crashtests/1134545.html
Normal 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>
|
||||
10
editor/libeditor/crashtests/1158452.html
Normal file
10
editor/libeditor/crashtests/1158452.html
Normal 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>
|
||||
18
editor/libeditor/crashtests/1158651.html
Normal file
18
editor/libeditor/crashtests/1158651.html
Normal 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>
|
||||
21
editor/libeditor/crashtests/1244894.xhtml
Normal file
21
editor/libeditor/crashtests/1244894.xhtml
Normal 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>
|
||||
20
editor/libeditor/crashtests/1272490.html
Normal file
20
editor/libeditor/crashtests/1272490.html
Normal 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>
|
||||
36
editor/libeditor/crashtests/1317704.html
Normal file
36
editor/libeditor/crashtests/1317704.html
Normal 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>
|
||||
52
editor/libeditor/crashtests/336081-1.xhtml
Normal file
52
editor/libeditor/crashtests/336081-1.xhtml
Normal 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>
|
||||
37
editor/libeditor/crashtests/336104.html
Normal file
37
editor/libeditor/crashtests/336104.html
Normal 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>
|
||||
58
editor/libeditor/crashtests/382527-1.html
Normal file
58
editor/libeditor/crashtests/382527-1.html
Normal 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>
|
||||
53
editor/libeditor/crashtests/382778-1.html
Normal file
53
editor/libeditor/crashtests/382778-1.html
Normal 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>
|
||||
23
editor/libeditor/crashtests/402172-1.html
Normal file
23
editor/libeditor/crashtests/402172-1.html
Normal 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>
|
||||
7
editor/libeditor/crashtests/403965-1.xhtml
Normal file
7
editor/libeditor/crashtests/403965-1.xhtml
Normal 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>
|
||||
7
editor/libeditor/crashtests/407074-1.html
Normal file
7
editor/libeditor/crashtests/407074-1.html
Normal 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>
|
||||
15
editor/libeditor/crashtests/407079-1.html
Normal file
15
editor/libeditor/crashtests/407079-1.html
Normal 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>
|
||||
23
editor/libeditor/crashtests/407256-1.html
Normal file
23
editor/libeditor/crashtests/407256-1.html
Normal 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
Loading…
Add table
Add a link
Reference in a new issue