зеркало из https://github.com/mozilla/gecko-dev.git
1306 строки
47 KiB
C++
1306 строки
47 KiB
C++
/* -*- 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 "CSSEditUtils.h"
|
|
|
|
#include "ChangeStyleTransaction.h"
|
|
#include "HTMLEditHelpers.h"
|
|
#include "HTMLEditor.h"
|
|
#include "HTMLEditUtils.h"
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/DeclarationBlock.h"
|
|
#include "mozilla/mozalloc.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/ServoCSSParser.h"
|
|
#include "mozilla/StaticPrefs_editor.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "nsAString.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsCSSProps.h"
|
|
#include "nsColor.h"
|
|
#include "nsComputedDOMStyle.h"
|
|
#include "nsDebug.h"
|
|
#include "nsDependentSubstring.h"
|
|
#include "nsError.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsAtom.h"
|
|
#include "nsIContent.h"
|
|
#include "nsICSSDeclaration.h"
|
|
#include "nsINode.h"
|
|
#include "nsISupportsImpl.h"
|
|
#include "nsISupportsUtils.h"
|
|
#include "nsLiteralString.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsString.h"
|
|
#include "nsStringFwd.h"
|
|
#include "nsStringIterator.h"
|
|
#include "nsStyledElement.h"
|
|
#include "nsUnicharUtils.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace dom;
|
|
|
|
static void ProcessBValue(const nsAString* aInputString,
|
|
nsAString& aOutputString,
|
|
const char* aDefaultValueString,
|
|
const char* aPrependString,
|
|
const char* aAppendString) {
|
|
if (aInputString && aInputString->EqualsLiteral("-moz-editor-invert-value")) {
|
|
aOutputString.AssignLiteral("normal");
|
|
} else {
|
|
aOutputString.AssignLiteral("bold");
|
|
}
|
|
}
|
|
|
|
static void ProcessDefaultValue(const nsAString* aInputString,
|
|
nsAString& aOutputString,
|
|
const char* aDefaultValueString,
|
|
const char* aPrependString,
|
|
const char* aAppendString) {
|
|
CopyASCIItoUTF16(MakeStringSpan(aDefaultValueString), aOutputString);
|
|
}
|
|
|
|
static void ProcessSameValue(const nsAString* aInputString,
|
|
nsAString& aOutputString,
|
|
const char* aDefaultValueString,
|
|
const char* aPrependString,
|
|
const char* aAppendString) {
|
|
if (aInputString) {
|
|
aOutputString.Assign(*aInputString);
|
|
} else
|
|
aOutputString.Truncate();
|
|
}
|
|
|
|
static void ProcessExtendedValue(const nsAString* aInputString,
|
|
nsAString& aOutputString,
|
|
const char* aDefaultValueString,
|
|
const char* aPrependString,
|
|
const char* aAppendString) {
|
|
aOutputString.Truncate();
|
|
if (aInputString) {
|
|
if (aPrependString) {
|
|
AppendASCIItoUTF16(MakeStringSpan(aPrependString), aOutputString);
|
|
}
|
|
aOutputString.Append(*aInputString);
|
|
if (aAppendString) {
|
|
AppendASCIItoUTF16(MakeStringSpan(aAppendString), aOutputString);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ProcessLengthValue(const nsAString* aInputString,
|
|
nsAString& aOutputString,
|
|
const char* aDefaultValueString,
|
|
const char* aPrependString,
|
|
const char* aAppendString) {
|
|
aOutputString.Truncate();
|
|
if (aInputString) {
|
|
aOutputString.Append(*aInputString);
|
|
if (-1 == aOutputString.FindChar(char16_t('%'))) {
|
|
aOutputString.AppendLiteral("px");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ProcessListStyleTypeValue(const nsAString* aInputString,
|
|
nsAString& aOutputString,
|
|
const char* aDefaultValueString,
|
|
const char* aPrependString,
|
|
const char* aAppendString) {
|
|
aOutputString.Truncate();
|
|
if (aInputString) {
|
|
if (aInputString->EqualsLiteral("1")) {
|
|
aOutputString.AppendLiteral("decimal");
|
|
} else if (aInputString->EqualsLiteral("a")) {
|
|
aOutputString.AppendLiteral("lower-alpha");
|
|
} else if (aInputString->EqualsLiteral("A")) {
|
|
aOutputString.AppendLiteral("upper-alpha");
|
|
} else if (aInputString->EqualsLiteral("i")) {
|
|
aOutputString.AppendLiteral("lower-roman");
|
|
} else if (aInputString->EqualsLiteral("I")) {
|
|
aOutputString.AppendLiteral("upper-roman");
|
|
} else if (aInputString->EqualsLiteral("square") ||
|
|
aInputString->EqualsLiteral("circle") ||
|
|
aInputString->EqualsLiteral("disc")) {
|
|
aOutputString.Append(*aInputString);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ProcessMarginLeftValue(const nsAString* aInputString,
|
|
nsAString& aOutputString,
|
|
const char* aDefaultValueString,
|
|
const char* aPrependString,
|
|
const char* aAppendString) {
|
|
aOutputString.Truncate();
|
|
if (aInputString) {
|
|
if (aInputString->EqualsLiteral("center") ||
|
|
aInputString->EqualsLiteral("-moz-center")) {
|
|
aOutputString.AppendLiteral("auto");
|
|
} else if (aInputString->EqualsLiteral("right") ||
|
|
aInputString->EqualsLiteral("-moz-right")) {
|
|
aOutputString.AppendLiteral("auto");
|
|
} else {
|
|
aOutputString.AppendLiteral("0px");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ProcessMarginRightValue(const nsAString* aInputString,
|
|
nsAString& aOutputString,
|
|
const char* aDefaultValueString,
|
|
const char* aPrependString,
|
|
const char* aAppendString) {
|
|
aOutputString.Truncate();
|
|
if (aInputString) {
|
|
if (aInputString->EqualsLiteral("center") ||
|
|
aInputString->EqualsLiteral("-moz-center")) {
|
|
aOutputString.AppendLiteral("auto");
|
|
} else if (aInputString->EqualsLiteral("left") ||
|
|
aInputString->EqualsLiteral("-moz-left")) {
|
|
aOutputString.AppendLiteral("auto");
|
|
} else {
|
|
aOutputString.AppendLiteral("0px");
|
|
}
|
|
}
|
|
}
|
|
|
|
#define CSS_EQUIV_TABLE_NONE \
|
|
{ CSSEditUtils::eCSSEditableProperty_NONE, 0 }
|
|
|
|
const CSSEditUtils::CSSEquivTable boldEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_font_weight, true, false, ProcessBValue,
|
|
nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable italicEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_font_style, true, false,
|
|
ProcessDefaultValue, "italic", nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable underlineEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_text_decoration, true, false,
|
|
ProcessDefaultValue, "underline", nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable strikeEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_text_decoration, true, false,
|
|
ProcessDefaultValue, "line-through", nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable ttEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_font_family, true, false,
|
|
ProcessDefaultValue, "monospace", nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable fontColorEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_color, true, false, ProcessSameValue,
|
|
nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable fontFaceEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_font_family, true, false,
|
|
ProcessSameValue, nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable fontSizeEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_font_size, true, false,
|
|
ProcessSameValue, nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable bgcolorEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_background_color, true, false,
|
|
ProcessSameValue, nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable backgroundImageEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_background_image, true, true,
|
|
ProcessExtendedValue, nullptr, "url(", ")"},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable textColorEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_color, true, false, ProcessSameValue,
|
|
nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable borderEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_border, true, false,
|
|
ProcessExtendedValue, nullptr, nullptr, "px solid"},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable textAlignEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_text_align, true, false,
|
|
ProcessSameValue, nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable captionAlignEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_caption_side, true, false,
|
|
ProcessSameValue, nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable verticalAlignEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_vertical_align, true, false,
|
|
ProcessSameValue, nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable nowrapEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_whitespace, true, false,
|
|
ProcessDefaultValue, "nowrap", nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable widthEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_width, true, false, ProcessLengthValue,
|
|
nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable heightEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_height, true, false, ProcessLengthValue,
|
|
nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable listStyleTypeEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_list_style_type, true, true,
|
|
ProcessListStyleTypeValue, nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable tableAlignEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_text_align, false, false,
|
|
ProcessDefaultValue, "left", nullptr, nullptr},
|
|
{CSSEditUtils::eCSSEditableProperty_margin_left, true, false,
|
|
ProcessMarginLeftValue, nullptr, nullptr, nullptr},
|
|
{CSSEditUtils::eCSSEditableProperty_margin_right, true, false,
|
|
ProcessMarginRightValue, nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
const CSSEditUtils::CSSEquivTable hrAlignEquivTable[] = {
|
|
{CSSEditUtils::eCSSEditableProperty_margin_left, true, false,
|
|
ProcessMarginLeftValue, nullptr, nullptr, nullptr},
|
|
{CSSEditUtils::eCSSEditableProperty_margin_right, true, false,
|
|
ProcessMarginRightValue, nullptr, nullptr, nullptr},
|
|
CSS_EQUIV_TABLE_NONE};
|
|
|
|
#undef CSS_EQUIV_TABLE_NONE
|
|
|
|
// static
|
|
bool CSSEditUtils::IsCSSEditableStyle(const Element& aElement,
|
|
const EditorElementStyle& aStyle) {
|
|
return CSSEditUtils::IsCSSEditableStyle(*aElement.NodeInfo()->NameAtom(),
|
|
aStyle);
|
|
}
|
|
|
|
// static
|
|
bool CSSEditUtils::IsCSSEditableStyle(const nsAtom& aTagName,
|
|
const EditorElementStyle& aStyle) {
|
|
nsStaticAtom* const htmlProperty =
|
|
aStyle.IsInlineStyle() ? aStyle.AsInlineStyle().mHTMLProperty : nullptr;
|
|
nsAtom* const attributeOrStyle = aStyle.IsInlineStyle()
|
|
? aStyle.AsInlineStyle().mAttribute.get()
|
|
: aStyle.Style();
|
|
|
|
// HTML inline styles <b>, <i>, <tt> (chrome only), <u>, <strike>, <font
|
|
// color> and <font style>.
|
|
if (nsGkAtoms::b == htmlProperty || nsGkAtoms::i == htmlProperty ||
|
|
nsGkAtoms::tt == htmlProperty || nsGkAtoms::u == htmlProperty ||
|
|
nsGkAtoms::strike == htmlProperty ||
|
|
(nsGkAtoms::font == htmlProperty &&
|
|
(attributeOrStyle == nsGkAtoms::color ||
|
|
attributeOrStyle == nsGkAtoms::face))) {
|
|
return true;
|
|
}
|
|
|
|
// ALIGN attribute on elements supporting it
|
|
if (attributeOrStyle == nsGkAtoms::align &&
|
|
(&aTagName == nsGkAtoms::div || &aTagName == nsGkAtoms::p ||
|
|
&aTagName == nsGkAtoms::h1 || &aTagName == nsGkAtoms::h2 ||
|
|
&aTagName == nsGkAtoms::h3 || &aTagName == nsGkAtoms::h4 ||
|
|
&aTagName == nsGkAtoms::h5 || &aTagName == nsGkAtoms::h6 ||
|
|
&aTagName == nsGkAtoms::td || &aTagName == nsGkAtoms::th ||
|
|
&aTagName == nsGkAtoms::table || &aTagName == nsGkAtoms::hr ||
|
|
// For the above, why not use
|
|
// HTMLEditUtils::SupportsAlignAttr?
|
|
// It also checks for tbody, tfoot, thead.
|
|
// Let's add the following elements here even
|
|
// if "align" has a different meaning for them
|
|
&aTagName == nsGkAtoms::legend || &aTagName == nsGkAtoms::caption)) {
|
|
return true;
|
|
}
|
|
|
|
if (attributeOrStyle == nsGkAtoms::valign &&
|
|
(&aTagName == nsGkAtoms::col || &aTagName == nsGkAtoms::colgroup ||
|
|
&aTagName == nsGkAtoms::tbody || &aTagName == nsGkAtoms::td ||
|
|
&aTagName == nsGkAtoms::th || &aTagName == nsGkAtoms::tfoot ||
|
|
&aTagName == nsGkAtoms::thead || &aTagName == nsGkAtoms::tr)) {
|
|
return true;
|
|
}
|
|
|
|
// attributes TEXT, BACKGROUND and BGCOLOR on <body>
|
|
if (&aTagName == nsGkAtoms::body &&
|
|
(attributeOrStyle == nsGkAtoms::text ||
|
|
attributeOrStyle == nsGkAtoms::background ||
|
|
attributeOrStyle == nsGkAtoms::bgcolor)) {
|
|
return true;
|
|
}
|
|
|
|
// attribute BGCOLOR on other elements
|
|
if (attributeOrStyle == nsGkAtoms::bgcolor) {
|
|
return true;
|
|
}
|
|
|
|
// attributes HEIGHT, WIDTH and NOWRAP on <td> and <th>
|
|
if ((&aTagName == nsGkAtoms::td || &aTagName == nsGkAtoms::th) &&
|
|
(attributeOrStyle == nsGkAtoms::height ||
|
|
attributeOrStyle == nsGkAtoms::width ||
|
|
attributeOrStyle == nsGkAtoms::nowrap)) {
|
|
return true;
|
|
}
|
|
|
|
// attributes HEIGHT and WIDTH on <table>
|
|
if (&aTagName == nsGkAtoms::table && (attributeOrStyle == nsGkAtoms::height ||
|
|
attributeOrStyle == nsGkAtoms::width)) {
|
|
return true;
|
|
}
|
|
|
|
// attributes SIZE and WIDTH on <hr>
|
|
if (&aTagName == nsGkAtoms::hr && (attributeOrStyle == nsGkAtoms::size ||
|
|
attributeOrStyle == nsGkAtoms::width)) {
|
|
return true;
|
|
}
|
|
|
|
// attribute TYPE on <ol>, <ul> and <li>
|
|
if (attributeOrStyle == nsGkAtoms::type &&
|
|
(&aTagName == nsGkAtoms::ol || &aTagName == nsGkAtoms::ul ||
|
|
&aTagName == nsGkAtoms::li)) {
|
|
return true;
|
|
}
|
|
|
|
if (&aTagName == nsGkAtoms::img && (attributeOrStyle == nsGkAtoms::border ||
|
|
attributeOrStyle == nsGkAtoms::width ||
|
|
attributeOrStyle == nsGkAtoms::height)) {
|
|
return true;
|
|
}
|
|
|
|
// other elements that we can align using CSS even if they
|
|
// can't carry the html ALIGN attribute
|
|
if (attributeOrStyle == nsGkAtoms::align &&
|
|
(&aTagName == nsGkAtoms::ul || &aTagName == nsGkAtoms::ol ||
|
|
&aTagName == nsGkAtoms::dl || &aTagName == nsGkAtoms::li ||
|
|
&aTagName == nsGkAtoms::dd || &aTagName == nsGkAtoms::dt ||
|
|
&aTagName == nsGkAtoms::address || &aTagName == nsGkAtoms::pre)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// The lowest level above the transaction; adds the CSS declaration
|
|
// "aProperty : aValue" to the inline styles carried by aStyledElement
|
|
|
|
// static
|
|
nsresult CSSEditUtils::SetCSSPropertyInternal(HTMLEditor& aHTMLEditor,
|
|
nsStyledElement& aStyledElement,
|
|
nsAtom& aProperty,
|
|
const nsAString& aValue,
|
|
bool aSuppressTxn) {
|
|
RefPtr<ChangeStyleTransaction> transaction =
|
|
ChangeStyleTransaction::Create(aStyledElement, aProperty, aValue);
|
|
if (aSuppressTxn) {
|
|
nsresult rv = transaction->DoTransaction();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"ChangeStyleTransaction::DoTransaction() failed");
|
|
return rv;
|
|
}
|
|
nsresult rv = aHTMLEditor.DoTransactionInternal(transaction);
|
|
if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
|
|
return NS_ERROR_EDITOR_DESTROYED;
|
|
}
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"EditorBase::DoTransactionInternal() failed");
|
|
return rv;
|
|
}
|
|
|
|
// static
|
|
nsresult CSSEditUtils::SetCSSPropertyPixelsWithTransaction(
|
|
HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom& aProperty,
|
|
int32_t aIntValue) {
|
|
nsAutoString s;
|
|
s.AppendInt(aIntValue);
|
|
nsresult rv = SetCSSPropertyWithTransaction(aHTMLEditor, aStyledElement,
|
|
aProperty, s + u"px"_ns);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"CSSEditUtils::SetCSSPropertyWithTransaction() failed");
|
|
return rv;
|
|
}
|
|
|
|
// static
|
|
nsresult CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction(
|
|
nsStyledElement& aStyledElement, const nsAtom& aProperty,
|
|
int32_t aIntValue) {
|
|
nsCOMPtr<nsICSSDeclaration> cssDecl = aStyledElement.Style();
|
|
|
|
nsAutoCString propertyNameString;
|
|
aProperty.ToUTF8String(propertyNameString);
|
|
|
|
nsAutoCString s;
|
|
s.AppendInt(aIntValue);
|
|
s.AppendLiteral("px");
|
|
|
|
ErrorResult error;
|
|
cssDecl->SetProperty(propertyNameString, s, EmptyCString(), error);
|
|
if (error.Failed()) {
|
|
NS_WARNING("nsICSSDeclaration::SetProperty() failed");
|
|
return error.StealNSResult();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// The lowest level above the transaction; removes the value aValue from the
|
|
// list of values specified for the CSS property aProperty, or totally remove
|
|
// the declaration if this property accepts only one value
|
|
|
|
// static
|
|
nsresult CSSEditUtils::RemoveCSSPropertyInternal(
|
|
HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom& aProperty,
|
|
const nsAString& aValue, bool aSuppressTxn) {
|
|
RefPtr<ChangeStyleTransaction> transaction =
|
|
ChangeStyleTransaction::CreateToRemove(aStyledElement, aProperty, aValue);
|
|
if (aSuppressTxn) {
|
|
nsresult rv = transaction->DoTransaction();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"ChangeStyleTransaction::DoTransaction() failed");
|
|
return rv;
|
|
}
|
|
nsresult rv = aHTMLEditor.DoTransactionInternal(transaction);
|
|
if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
|
|
return NS_ERROR_EDITOR_DESTROYED;
|
|
}
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"EditorBase::DoTransactionInternal() failed");
|
|
return rv;
|
|
}
|
|
|
|
// static
|
|
nsresult CSSEditUtils::GetSpecifiedProperty(nsIContent& aContent,
|
|
nsAtom& aCSSProperty,
|
|
nsAString& aValue) {
|
|
nsresult rv =
|
|
GetSpecifiedCSSInlinePropertyBase(aContent, aCSSProperty, aValue);
|
|
NS_WARNING_ASSERTION(
|
|
NS_SUCCEEDED(rv),
|
|
"CSSEditUtils::GeSpecifiedCSSInlinePropertyBase() failed");
|
|
return rv;
|
|
}
|
|
|
|
// static
|
|
nsresult CSSEditUtils::GetComputedProperty(nsIContent& aContent,
|
|
nsAtom& aCSSProperty,
|
|
nsAString& aValue) {
|
|
nsresult rv =
|
|
GetComputedCSSInlinePropertyBase(aContent, aCSSProperty, aValue);
|
|
NS_WARNING_ASSERTION(
|
|
NS_SUCCEEDED(rv),
|
|
"CSSEditUtils::GetComputedCSSInlinePropertyBase() failed");
|
|
return rv;
|
|
}
|
|
|
|
// static
|
|
nsresult CSSEditUtils::GetComputedCSSInlinePropertyBase(nsIContent& aContent,
|
|
nsAtom& aCSSProperty,
|
|
nsAString& aValue) {
|
|
aValue.Truncate();
|
|
|
|
RefPtr<Element> element = aContent.GetAsElementOrParentElement();
|
|
if (NS_WARN_IF(!element)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// Get the all the computed css styles attached to the element node
|
|
RefPtr<nsComputedDOMStyle> computedDOMStyle = GetComputedStyle(element);
|
|
if (NS_WARN_IF(!computedDOMStyle)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// from these declarations, get the one we want and that one only
|
|
//
|
|
// FIXME(bug 1606994): nsAtomCString copies, we should just keep around the
|
|
// property id.
|
|
//
|
|
// FIXME: Maybe we can avoid copying aValue too, though it's no worse than
|
|
// what we used to do.
|
|
nsAutoCString value;
|
|
computedDOMStyle->GetPropertyValue(nsAtomCString(&aCSSProperty), value);
|
|
CopyUTF8toUTF16(value, aValue);
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult CSSEditUtils::GetSpecifiedCSSInlinePropertyBase(nsIContent& aContent,
|
|
nsAtom& aCSSProperty,
|
|
nsAString& aValue) {
|
|
aValue.Truncate();
|
|
|
|
RefPtr<Element> element = aContent.GetAsElementOrParentElement();
|
|
if (NS_WARN_IF(!element)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
RefPtr<DeclarationBlock> decl = element->GetInlineStyleDeclaration();
|
|
if (!decl) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// FIXME: Same comments as above.
|
|
nsCSSPropertyID prop =
|
|
nsCSSProps::LookupProperty(nsAtomCString(&aCSSProperty));
|
|
MOZ_ASSERT(prop != eCSSProperty_UNKNOWN);
|
|
|
|
nsAutoCString value;
|
|
decl->GetPropertyValueByID(prop, value);
|
|
CopyUTF8toUTF16(value, aValue);
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<nsComputedDOMStyle> CSSEditUtils::GetComputedStyle(
|
|
Element* aElement) {
|
|
MOZ_ASSERT(aElement);
|
|
|
|
Document* document = aElement->GetComposedDoc();
|
|
if (NS_WARN_IF(!document)) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<nsComputedDOMStyle> computedDOMStyle = NS_NewComputedDOMStyle(
|
|
aElement, u""_ns, document, nsComputedDOMStyle::StyleType::All,
|
|
IgnoreErrors());
|
|
return computedDOMStyle.forget();
|
|
}
|
|
|
|
// remove the CSS style "aProperty : aPropertyValue" and possibly remove the
|
|
// whole node if it is a span and if its only attribute is _moz_dirty
|
|
|
|
// static
|
|
Result<EditorDOMPoint, nsresult>
|
|
CSSEditUtils::RemoveCSSInlineStyleWithTransaction(
|
|
HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom* aProperty,
|
|
const nsAString& aPropertyValue) {
|
|
// remove the property from the style attribute
|
|
nsresult rv = RemoveCSSPropertyWithTransaction(aHTMLEditor, aStyledElement,
|
|
*aProperty, aPropertyValue);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithTransaction() failed");
|
|
return Err(rv);
|
|
}
|
|
|
|
if (!aStyledElement.IsHTMLElement(nsGkAtoms::span) ||
|
|
HTMLEditUtils::ElementHasAttribute(aStyledElement)) {
|
|
return EditorDOMPoint();
|
|
}
|
|
|
|
Result<EditorDOMPoint, nsresult> unwrapStyledElementResult =
|
|
aHTMLEditor.RemoveContainerWithTransaction(aStyledElement);
|
|
NS_WARNING_ASSERTION(unwrapStyledElementResult.isOk(),
|
|
"HTMLEditor::RemoveContainerWithTransaction() failed");
|
|
return unwrapStyledElementResult;
|
|
}
|
|
|
|
// Get the default browser background color if we need it for
|
|
// GetCSSBackgroundColorState
|
|
|
|
// static
|
|
void CSSEditUtils::GetDefaultBackgroundColor(nsAString& aColor) {
|
|
if (MOZ_UNLIKELY(StaticPrefs::editor_use_custom_colors())) {
|
|
nsresult rv = Preferences::GetString("editor.background_color", aColor);
|
|
// XXX Why don't you validate the pref value?
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("failed to get editor.background_color");
|
|
aColor.AssignLiteral("#ffffff"); // Default to white
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (Preferences::GetBool("browser.display.use_system_colors", false)) {
|
|
return;
|
|
}
|
|
|
|
nsresult rv =
|
|
Preferences::GetString("browser.display.background_color", aColor);
|
|
// XXX Why don't you validate the pref value?
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("failed to get browser.display.background_color");
|
|
aColor.AssignLiteral("#ffffff"); // Default to white
|
|
}
|
|
}
|
|
|
|
// static
|
|
void CSSEditUtils::ParseLength(const nsAString& aString, float* aValue,
|
|
nsAtom** aUnit) {
|
|
if (aString.IsEmpty()) {
|
|
*aValue = 0;
|
|
*aUnit = NS_Atomize(aString).take();
|
|
return;
|
|
}
|
|
|
|
nsAString::const_iterator iter;
|
|
aString.BeginReading(iter);
|
|
|
|
float a = 10.0f, b = 1.0f, value = 0;
|
|
int8_t sign = 1;
|
|
int32_t i = 0, j = aString.Length();
|
|
char16_t c;
|
|
bool floatingPointFound = false;
|
|
c = *iter;
|
|
if (char16_t('-') == c) {
|
|
sign = -1;
|
|
iter++;
|
|
i++;
|
|
} else if (char16_t('+') == c) {
|
|
iter++;
|
|
i++;
|
|
}
|
|
while (i < j) {
|
|
c = *iter;
|
|
if ((char16_t('0') == c) || (char16_t('1') == c) || (char16_t('2') == c) ||
|
|
(char16_t('3') == c) || (char16_t('4') == c) || (char16_t('5') == c) ||
|
|
(char16_t('6') == c) || (char16_t('7') == c) || (char16_t('8') == c) ||
|
|
(char16_t('9') == c)) {
|
|
value = (value * a) + (b * (c - char16_t('0')));
|
|
b = b / 10 * a;
|
|
} else if (!floatingPointFound && (char16_t('.') == c)) {
|
|
floatingPointFound = true;
|
|
a = 1.0f;
|
|
b = 0.1f;
|
|
} else
|
|
break;
|
|
iter++;
|
|
i++;
|
|
}
|
|
*aValue = value * sign;
|
|
*aUnit = NS_Atomize(StringTail(aString, j - i)).take();
|
|
}
|
|
|
|
// static
|
|
nsStaticAtom* CSSEditUtils::GetCSSPropertyAtom(
|
|
nsCSSEditableProperty aProperty) {
|
|
switch (aProperty) {
|
|
case eCSSEditableProperty_background_color:
|
|
return nsGkAtoms::backgroundColor;
|
|
case eCSSEditableProperty_background_image:
|
|
return nsGkAtoms::background_image;
|
|
case eCSSEditableProperty_border:
|
|
return nsGkAtoms::border;
|
|
case eCSSEditableProperty_caption_side:
|
|
return nsGkAtoms::caption_side;
|
|
case eCSSEditableProperty_color:
|
|
return nsGkAtoms::color;
|
|
case eCSSEditableProperty_float:
|
|
return nsGkAtoms::_float;
|
|
case eCSSEditableProperty_font_family:
|
|
return nsGkAtoms::font_family;
|
|
case eCSSEditableProperty_font_size:
|
|
return nsGkAtoms::font_size;
|
|
case eCSSEditableProperty_font_style:
|
|
return nsGkAtoms::font_style;
|
|
case eCSSEditableProperty_font_weight:
|
|
return nsGkAtoms::fontWeight;
|
|
case eCSSEditableProperty_height:
|
|
return nsGkAtoms::height;
|
|
case eCSSEditableProperty_list_style_type:
|
|
return nsGkAtoms::list_style_type;
|
|
case eCSSEditableProperty_margin_left:
|
|
return nsGkAtoms::marginLeft;
|
|
case eCSSEditableProperty_margin_right:
|
|
return nsGkAtoms::marginRight;
|
|
case eCSSEditableProperty_text_align:
|
|
return nsGkAtoms::textAlign;
|
|
case eCSSEditableProperty_text_decoration:
|
|
return nsGkAtoms::text_decoration;
|
|
case eCSSEditableProperty_vertical_align:
|
|
return nsGkAtoms::vertical_align;
|
|
case eCSSEditableProperty_whitespace:
|
|
return nsGkAtoms::white_space;
|
|
case eCSSEditableProperty_width:
|
|
return nsGkAtoms::width;
|
|
case eCSSEditableProperty_NONE:
|
|
// intentionally empty
|
|
return nullptr;
|
|
}
|
|
MOZ_ASSERT_UNREACHABLE("Got unknown property");
|
|
return nullptr;
|
|
}
|
|
|
|
// static
|
|
void CSSEditUtils::GetCSSDeclarations(
|
|
const CSSEquivTable* aEquivTable, const nsAString* aValue,
|
|
HandlingFor aHandlingFor, nsTArray<CSSDeclaration>& aOutCSSDeclarations) {
|
|
// clear arrays
|
|
aOutCSSDeclarations.Clear();
|
|
|
|
// if we have an input value, let's use it
|
|
nsAutoString value, lowerCasedValue;
|
|
if (aValue) {
|
|
value.Assign(*aValue);
|
|
lowerCasedValue.Assign(*aValue);
|
|
ToLowerCase(lowerCasedValue);
|
|
}
|
|
|
|
for (size_t index = 0;; index++) {
|
|
const nsCSSEditableProperty cssProperty = aEquivTable[index].cssProperty;
|
|
if (!cssProperty) {
|
|
break;
|
|
}
|
|
if (aHandlingFor == HandlingFor::SettingStyle ||
|
|
aEquivTable[index].gettable) {
|
|
nsAutoString cssValue, cssPropertyString;
|
|
// find the equivalent css value for the index-th property in
|
|
// the equivalence table
|
|
(*aEquivTable[index].processValueFunctor)(
|
|
(aHandlingFor == HandlingFor::SettingStyle ||
|
|
aEquivTable[index].caseSensitiveValue)
|
|
? &value
|
|
: &lowerCasedValue,
|
|
cssValue, aEquivTable[index].defaultValue,
|
|
aEquivTable[index].prependValue, aEquivTable[index].appendValue);
|
|
nsStaticAtom* const propertyAtom = GetCSSPropertyAtom(cssProperty);
|
|
if (MOZ_LIKELY(propertyAtom)) {
|
|
aOutCSSDeclarations.AppendElement(
|
|
CSSDeclaration{*propertyAtom, cssValue});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
void CSSEditUtils::GetCSSDeclarations(
|
|
Element& aElement, const EditorElementStyle& aStyle,
|
|
const nsAString* aValue, HandlingFor aHandlingFor,
|
|
nsTArray<CSSDeclaration>& aOutCSSDeclarations) {
|
|
nsStaticAtom* const htmlProperty =
|
|
aStyle.IsInlineStyle() ? aStyle.AsInlineStyle().mHTMLProperty : nullptr;
|
|
const RefPtr<nsAtom> attributeOrStyle =
|
|
aStyle.IsInlineStyle() ? aStyle.AsInlineStyle().mAttribute
|
|
: aStyle.Style();
|
|
|
|
const auto* equivTable = [&]() -> const CSSEditUtils::CSSEquivTable* {
|
|
if (nsGkAtoms::b == htmlProperty) {
|
|
return boldEquivTable;
|
|
}
|
|
if (nsGkAtoms::i == htmlProperty) {
|
|
return italicEquivTable;
|
|
}
|
|
if (nsGkAtoms::u == htmlProperty) {
|
|
return underlineEquivTable;
|
|
}
|
|
if (nsGkAtoms::strike == htmlProperty) {
|
|
return strikeEquivTable;
|
|
}
|
|
if (nsGkAtoms::tt == htmlProperty) {
|
|
return ttEquivTable;
|
|
}
|
|
if (!attributeOrStyle) {
|
|
return nullptr;
|
|
}
|
|
if (nsGkAtoms::font == htmlProperty) {
|
|
if (attributeOrStyle == nsGkAtoms::color) {
|
|
return fontColorEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::face) {
|
|
return fontFaceEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::size) {
|
|
return fontSizeEquivTable;
|
|
}
|
|
MOZ_ASSERT(attributeOrStyle == nsGkAtoms::bgcolor);
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::bgcolor) {
|
|
return bgcolorEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::background) {
|
|
return backgroundImageEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::text) {
|
|
return textColorEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::border) {
|
|
return borderEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::align) {
|
|
if (aElement.IsHTMLElement(nsGkAtoms::table)) {
|
|
return tableAlignEquivTable;
|
|
}
|
|
if (aElement.IsHTMLElement(nsGkAtoms::hr)) {
|
|
return hrAlignEquivTable;
|
|
}
|
|
if (aElement.IsAnyOfHTMLElements(nsGkAtoms::legend, nsGkAtoms::caption)) {
|
|
return captionAlignEquivTable;
|
|
}
|
|
return textAlignEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::valign) {
|
|
return verticalAlignEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::nowrap) {
|
|
return nowrapEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::width) {
|
|
return widthEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::height ||
|
|
(aElement.IsHTMLElement(nsGkAtoms::hr) &&
|
|
attributeOrStyle == nsGkAtoms::size)) {
|
|
return heightEquivTable;
|
|
}
|
|
if (attributeOrStyle == nsGkAtoms::type &&
|
|
aElement.IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul,
|
|
nsGkAtoms::li)) {
|
|
return listStyleTypeEquivTable;
|
|
}
|
|
return nullptr;
|
|
}();
|
|
if (equivTable) {
|
|
GetCSSDeclarations(equivTable, aValue, aHandlingFor, aOutCSSDeclarations);
|
|
}
|
|
}
|
|
|
|
// Add to aNode the CSS inline style equivalent to HTMLProperty/aAttribute/
|
|
// aValue for the node, and return in aCount the number of CSS properties set
|
|
// by the call. The Element version returns aCount instead.
|
|
Result<size_t, nsresult> CSSEditUtils::SetCSSEquivalentToStyle(
|
|
WithTransaction aWithTransaction, HTMLEditor& aHTMLEditor,
|
|
nsStyledElement& aStyledElement, const EditorElementStyle& aStyleToSet,
|
|
const nsAString* aValue) {
|
|
MOZ_DIAGNOSTIC_ASSERT(aStyleToSet.IsCSSSettable(aStyledElement));
|
|
|
|
// we can apply the styles only if the node is an element and if we have
|
|
// an equivalence for the requested HTML style in this implementation
|
|
|
|
// Find the CSS equivalence to the HTML style
|
|
AutoTArray<CSSDeclaration, 4> cssDeclarations;
|
|
GetCSSDeclarations(aStyledElement, aStyleToSet, aValue,
|
|
HandlingFor::SettingStyle, cssDeclarations);
|
|
|
|
// set the individual CSS inline styles
|
|
for (const CSSDeclaration& cssDeclaration : cssDeclarations) {
|
|
nsresult rv = SetCSSPropertyInternal(
|
|
aHTMLEditor, aStyledElement, MOZ_KnownLive(cssDeclaration.mProperty),
|
|
cssDeclaration.mValue, aWithTransaction == WithTransaction::No);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("CSSEditUtils::SetCSSPropertyInternal() failed");
|
|
return Err(rv);
|
|
}
|
|
}
|
|
return cssDeclarations.Length();
|
|
}
|
|
|
|
// static
|
|
nsresult CSSEditUtils::RemoveCSSEquivalentToStyle(
|
|
WithTransaction aWithTransaction, HTMLEditor& aHTMLEditor,
|
|
nsStyledElement& aStyledElement, const EditorElementStyle& aStyleToRemove,
|
|
const nsAString* aValue) {
|
|
MOZ_DIAGNOSTIC_ASSERT(aStyleToRemove.IsCSSRemovable(aStyledElement));
|
|
|
|
// we can apply the styles only if the node is an element and if we have
|
|
// an equivalence for the requested HTML style in this implementation
|
|
|
|
// Find the CSS equivalence to the HTML style
|
|
AutoTArray<CSSDeclaration, 4> cssDeclarations;
|
|
GetCSSDeclarations(aStyledElement, aStyleToRemove, aValue,
|
|
HandlingFor::RemovingStyle, cssDeclarations);
|
|
|
|
// remove the individual CSS inline styles
|
|
for (const CSSDeclaration& cssDeclaration : cssDeclarations) {
|
|
nsresult rv = RemoveCSSPropertyInternal(
|
|
aHTMLEditor, aStyledElement, MOZ_KnownLive(cssDeclaration.mProperty),
|
|
cssDeclaration.mValue, aWithTransaction == WithTransaction::No);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithoutTransaction() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult CSSEditUtils::GetComputedCSSEquivalentTo(
|
|
Element& aElement, const EditorElementStyle& aStyle, nsAString& aOutValue) {
|
|
return GetCSSEquivalentTo(aElement, aStyle, aOutValue, StyleType::Computed);
|
|
}
|
|
|
|
// static
|
|
nsresult CSSEditUtils::GetCSSEquivalentTo(Element& aElement,
|
|
const EditorElementStyle& aStyle,
|
|
nsAString& aOutValue,
|
|
StyleType aStyleType) {
|
|
MOZ_ASSERT_IF(aStyle.IsInlineStyle(),
|
|
!aStyle.AsInlineStyle().IsStyleToClearAllInlineStyles());
|
|
MOZ_DIAGNOSTIC_ASSERT(aStyle.IsCSSSettable(aElement) ||
|
|
aStyle.IsCSSRemovable(aElement));
|
|
|
|
aOutValue.Truncate();
|
|
AutoTArray<CSSDeclaration, 4> cssDeclarations;
|
|
GetCSSDeclarations(aElement, aStyle, nullptr, HandlingFor::GettingStyle,
|
|
cssDeclarations);
|
|
nsAutoString valueString;
|
|
for (const CSSDeclaration& cssDeclaration : cssDeclarations) {
|
|
valueString.Truncate();
|
|
// retrieve the specified/computed value of the property
|
|
if (aStyleType == StyleType::Computed) {
|
|
nsresult rv = GetComputedCSSInlinePropertyBase(
|
|
aElement, MOZ_KnownLive(cssDeclaration.mProperty), valueString);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("CSSEditUtils::GetComputedCSSInlinePropertyBase() failed");
|
|
return rv;
|
|
}
|
|
} else {
|
|
nsresult rv = GetSpecifiedCSSInlinePropertyBase(
|
|
aElement, cssDeclaration.mProperty, valueString);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("CSSEditUtils::GetSpecifiedCSSInlinePropertyBase() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
// append the value to aOutValue (possibly with a leading white-space)
|
|
if (!aOutValue.IsEmpty()) {
|
|
aOutValue.Append(HTMLEditUtils::kSpace);
|
|
}
|
|
aOutValue.Append(valueString);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// Does the node aContent (or its parent, if it's not an element node) have a
|
|
// CSS style equivalent to the HTML style
|
|
// aHTMLProperty/aAttribute/valueString? The value of aStyleType controls
|
|
// the styles we retrieve: specified or computed. The return value aIsSet is
|
|
// true if the CSS styles are set.
|
|
//
|
|
// The nsIContent variant returns aIsSet instead of using an out parameter, and
|
|
// does not modify aValue.
|
|
|
|
// static
|
|
Result<bool, nsresult> CSSEditUtils::IsComputedCSSEquivalentTo(
|
|
const HTMLEditor& aHTMLEditor, nsIContent& aContent,
|
|
const EditorInlineStyle& aStyle, nsAString& aInOutValue) {
|
|
return IsCSSEquivalentTo(aHTMLEditor, aContent, aStyle, aInOutValue,
|
|
StyleType::Computed);
|
|
}
|
|
|
|
// static
|
|
Result<bool, nsresult> CSSEditUtils::IsSpecifiedCSSEquivalentTo(
|
|
const HTMLEditor& aHTMLEditor, nsIContent& aContent,
|
|
const EditorInlineStyle& aStyle, nsAString& aInOutValue) {
|
|
return IsCSSEquivalentTo(aHTMLEditor, aContent, aStyle, aInOutValue,
|
|
StyleType::Specified);
|
|
}
|
|
|
|
// static
|
|
Result<bool, nsresult> CSSEditUtils::IsCSSEquivalentTo(
|
|
const HTMLEditor& aHTMLEditor, nsIContent& aContent,
|
|
const EditorInlineStyle& aStyle, nsAString& aInOutValue,
|
|
StyleType aStyleType) {
|
|
MOZ_ASSERT(!aStyle.IsStyleToClearAllInlineStyles());
|
|
|
|
nsAutoString htmlValueString(aInOutValue);
|
|
bool isSet = false;
|
|
// FYI: Cannot use InclusiveAncestorsOfType here because
|
|
// GetCSSEquivalentTo() may flush pending notifications.
|
|
for (RefPtr<Element> element = aContent.GetAsElementOrParentElement();
|
|
element; element = element->GetParentElement()) {
|
|
nsCOMPtr<nsINode> parentNode = element->GetParentNode();
|
|
aInOutValue.Assign(htmlValueString);
|
|
// get the value of the CSS equivalent styles
|
|
nsresult rv = GetCSSEquivalentTo(*element, aStyle, aInOutValue, aStyleType);
|
|
if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
|
|
return Err(NS_ERROR_EDITOR_DESTROYED);
|
|
}
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING(
|
|
"CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() "
|
|
"failed");
|
|
return Err(rv);
|
|
}
|
|
if (NS_WARN_IF(parentNode != element->GetParentNode())) {
|
|
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
|
}
|
|
|
|
// early way out if we can
|
|
if (aInOutValue.IsEmpty()) {
|
|
return isSet;
|
|
}
|
|
|
|
if (nsGkAtoms::b == aStyle.mHTMLProperty) {
|
|
if (aInOutValue.EqualsLiteral("bold")) {
|
|
isSet = true;
|
|
} else if (aInOutValue.EqualsLiteral("normal")) {
|
|
isSet = false;
|
|
} else if (aInOutValue.EqualsLiteral("bolder")) {
|
|
isSet = true;
|
|
aInOutValue.AssignLiteral("bold");
|
|
} else {
|
|
int32_t weight = 0;
|
|
nsresult rvIgnored;
|
|
nsAutoString value(aInOutValue);
|
|
weight = value.ToInteger(&rvIgnored);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
|
"nsAString::ToInteger() failed, but ignored");
|
|
if (400 < weight) {
|
|
isSet = true;
|
|
aInOutValue.AssignLiteral(u"bold");
|
|
} else {
|
|
isSet = false;
|
|
aInOutValue.AssignLiteral(u"normal");
|
|
}
|
|
}
|
|
} else if (nsGkAtoms::i == aStyle.mHTMLProperty) {
|
|
if (aInOutValue.EqualsLiteral(u"italic") ||
|
|
aInOutValue.EqualsLiteral(u"oblique")) {
|
|
isSet = true;
|
|
}
|
|
} else if (nsGkAtoms::u == aStyle.mHTMLProperty) {
|
|
isSet = ChangeStyleTransaction::ValueIncludes(
|
|
NS_ConvertUTF16toUTF8(aInOutValue), "underline"_ns);
|
|
} else if (nsGkAtoms::strike == aStyle.mHTMLProperty) {
|
|
isSet = ChangeStyleTransaction::ValueIncludes(
|
|
NS_ConvertUTF16toUTF8(aInOutValue), "line-through"_ns);
|
|
} else if ((nsGkAtoms::font == aStyle.mHTMLProperty &&
|
|
aStyle.mAttribute == nsGkAtoms::color) ||
|
|
aStyle.mAttribute == nsGkAtoms::bgcolor) {
|
|
isSet = htmlValueString.IsEmpty() ||
|
|
HTMLEditUtils::IsSameCSSColorValue(htmlValueString, aInOutValue);
|
|
} else if (nsGkAtoms::tt == aStyle.mHTMLProperty) {
|
|
isSet = StringBeginsWith(aInOutValue, u"monospace"_ns);
|
|
} else if (nsGkAtoms::font == aStyle.mHTMLProperty &&
|
|
aStyle.mAttribute == nsGkAtoms::face) {
|
|
if (!htmlValueString.IsEmpty()) {
|
|
const char16_t commaSpace[] = {char16_t(','), HTMLEditUtils::kSpace, 0};
|
|
const char16_t comma[] = {char16_t(','), 0};
|
|
htmlValueString.ReplaceSubstring(commaSpace, comma);
|
|
nsAutoString valueStringNorm(aInOutValue);
|
|
valueStringNorm.ReplaceSubstring(commaSpace, comma);
|
|
isSet = htmlValueString.Equals(valueStringNorm,
|
|
nsCaseInsensitiveStringComparator);
|
|
} else {
|
|
isSet = true;
|
|
}
|
|
return isSet;
|
|
} else if (aStyle.IsStyleOfFontSize()) {
|
|
if (htmlValueString.IsEmpty()) {
|
|
return true;
|
|
}
|
|
switch (nsContentUtils::ParseLegacyFontSize(htmlValueString)) {
|
|
case 1:
|
|
return aInOutValue.EqualsLiteral("x-small");
|
|
case 2:
|
|
return aInOutValue.EqualsLiteral("small");
|
|
case 3:
|
|
return aInOutValue.EqualsLiteral("medium");
|
|
case 4:
|
|
return aInOutValue.EqualsLiteral("large");
|
|
case 5:
|
|
return aInOutValue.EqualsLiteral("x-large");
|
|
case 6:
|
|
return aInOutValue.EqualsLiteral("xx-large");
|
|
case 7:
|
|
return aInOutValue.EqualsLiteral("xxx-large");
|
|
}
|
|
return false;
|
|
} else if (aStyle.mAttribute == nsGkAtoms::align) {
|
|
isSet = true;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (!htmlValueString.IsEmpty() &&
|
|
htmlValueString.Equals(aInOutValue,
|
|
nsCaseInsensitiveStringComparator)) {
|
|
isSet = true;
|
|
}
|
|
|
|
if (htmlValueString.EqualsLiteral(u"-moz-editor-invert-value")) {
|
|
isSet = !isSet;
|
|
}
|
|
|
|
if (isSet) {
|
|
return true;
|
|
}
|
|
|
|
if (!aStyle.IsStyleOfTextDecoration(
|
|
EditorInlineStyle::IgnoreSElement::Yes)) {
|
|
return isSet;
|
|
}
|
|
|
|
// Unfortunately, the value of the text-decoration property is not
|
|
// inherited. that means that we have to look at ancestors of node to see
|
|
// if they are underlined.
|
|
}
|
|
return isSet;
|
|
}
|
|
|
|
// static
|
|
Result<bool, nsresult> CSSEditUtils::HaveComputedCSSEquivalentStyles(
|
|
const HTMLEditor& aHTMLEditor, nsIContent& aContent,
|
|
const EditorInlineStyle& aStyle) {
|
|
return HaveCSSEquivalentStyles(aHTMLEditor, aContent, aStyle,
|
|
StyleType::Computed);
|
|
}
|
|
|
|
// static
|
|
Result<bool, nsresult> CSSEditUtils::HaveSpecifiedCSSEquivalentStyles(
|
|
const HTMLEditor& aHTMLEditor, nsIContent& aContent,
|
|
const EditorInlineStyle& aStyle) {
|
|
return HaveCSSEquivalentStyles(aHTMLEditor, aContent, aStyle,
|
|
StyleType::Specified);
|
|
}
|
|
|
|
// static
|
|
Result<bool, nsresult> CSSEditUtils::HaveCSSEquivalentStyles(
|
|
const HTMLEditor& aHTMLEditor, nsIContent& aContent,
|
|
const EditorInlineStyle& aStyle, StyleType aStyleType) {
|
|
MOZ_ASSERT(!aStyle.IsStyleToClearAllInlineStyles());
|
|
|
|
// FYI: Unfortunately, we cannot use InclusiveAncestorsOfType here
|
|
// because GetCSSEquivalentTo() may flush pending notifications.
|
|
nsAutoString valueString;
|
|
for (RefPtr<Element> element = aContent.GetAsElementOrParentElement();
|
|
element; element = element->GetParentElement()) {
|
|
nsCOMPtr<nsINode> parentNode = element->GetParentNode();
|
|
// get the value of the CSS equivalent styles
|
|
nsresult rv = GetCSSEquivalentTo(*element, aStyle, valueString, aStyleType);
|
|
if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
|
|
return Err(NS_ERROR_EDITOR_DESTROYED);
|
|
}
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING(
|
|
"CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() "
|
|
"failed");
|
|
return Err(rv);
|
|
}
|
|
if (NS_WARN_IF(parentNode != element->GetParentNode())) {
|
|
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
|
}
|
|
|
|
if (!valueString.IsEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
if (!aStyle.IsStyleOfTextDecoration(
|
|
EditorInlineStyle::IgnoreSElement::Yes)) {
|
|
return false;
|
|
}
|
|
|
|
// Unfortunately, the value of the text-decoration property is not
|
|
// inherited.
|
|
// that means that we have to look at ancestors of node to see if they
|
|
// are underlined.
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ElementsSameStyle compares two elements and checks if they have the same
|
|
// specified CSS declarations in the STYLE attribute
|
|
// The answer is always negative if at least one of them carries an ID or a
|
|
// class
|
|
|
|
// static
|
|
bool CSSEditUtils::DoStyledElementsHaveSameStyle(
|
|
nsStyledElement& aStyledElement, nsStyledElement& aOtherStyledElement) {
|
|
if (aStyledElement.HasAttr(nsGkAtoms::id) ||
|
|
aOtherStyledElement.HasAttr(nsGkAtoms::id)) {
|
|
// at least one of the spans carries an ID ; suspect a CSS rule applies to
|
|
// it and refuse to merge the nodes
|
|
return false;
|
|
}
|
|
|
|
nsAutoString firstClass, otherClass;
|
|
bool isElementClassSet =
|
|
aStyledElement.GetAttr(nsGkAtoms::_class, firstClass);
|
|
bool isOtherElementClassSet = aOtherStyledElement.GetAttr(
|
|
kNameSpaceID_None, nsGkAtoms::_class, otherClass);
|
|
if (isElementClassSet && isOtherElementClassSet) {
|
|
// both spans carry a class, let's compare them
|
|
if (!firstClass.Equals(otherClass)) {
|
|
// WARNING : technically, the comparison just above is questionable :
|
|
// from a pure HTML/CSS point of view class="a b" is NOT the same than
|
|
// class="b a" because a CSS rule could test the exact value of the class
|
|
// attribute to be "a b" for instance ; from a user's point of view, a
|
|
// wysiwyg editor should probably NOT make any difference. CSS people
|
|
// need to discuss this issue before any modification.
|
|
return false;
|
|
}
|
|
} else if (isElementClassSet || isOtherElementClassSet) {
|
|
// one span only carries a class, early way out
|
|
return false;
|
|
}
|
|
|
|
// XXX If `GetPropertyValue()` won't run script, we can stop using
|
|
// nsCOMPtr here.
|
|
nsCOMPtr<nsICSSDeclaration> firstCSSDecl = aStyledElement.Style();
|
|
if (!firstCSSDecl) {
|
|
NS_WARNING("nsStyledElement::Style() failed");
|
|
return false;
|
|
}
|
|
nsCOMPtr<nsICSSDeclaration> otherCSSDecl = aOtherStyledElement.Style();
|
|
if (!otherCSSDecl) {
|
|
NS_WARNING("nsStyledElement::Style() failed");
|
|
return false;
|
|
}
|
|
|
|
const uint32_t firstLength = firstCSSDecl->Length();
|
|
const uint32_t otherLength = otherCSSDecl->Length();
|
|
if (firstLength != otherLength) {
|
|
// early way out if we can
|
|
return false;
|
|
}
|
|
|
|
if (!firstLength) {
|
|
// no inline style !
|
|
return true;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < firstLength; i++) {
|
|
nsAutoCString firstValue, otherValue;
|
|
nsAutoCString propertyNameString;
|
|
firstCSSDecl->Item(i, propertyNameString);
|
|
firstCSSDecl->GetPropertyValue(propertyNameString, firstValue);
|
|
otherCSSDecl->GetPropertyValue(propertyNameString, otherValue);
|
|
// FIXME: We need to handle all properties whose values are color.
|
|
// However, it's too expensive if we keep using string property names.
|
|
if (propertyNameString.EqualsLiteral("color") ||
|
|
propertyNameString.EqualsLiteral("background-color")) {
|
|
if (!HTMLEditUtils::IsSameCSSColorValue(firstValue, otherValue)) {
|
|
return false;
|
|
}
|
|
} else if (!firstValue.Equals(otherValue)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (uint32_t i = 0; i < otherLength; i++) {
|
|
nsAutoCString firstValue, otherValue;
|
|
nsAutoCString propertyNameString;
|
|
otherCSSDecl->Item(i, propertyNameString);
|
|
otherCSSDecl->GetPropertyValue(propertyNameString, otherValue);
|
|
firstCSSDecl->GetPropertyValue(propertyNameString, firstValue);
|
|
// FIXME: We need to handle all properties whose values are color.
|
|
// However, it's too expensive if we keep using string property names.
|
|
if (propertyNameString.EqualsLiteral("color") ||
|
|
propertyNameString.EqualsLiteral("background-color")) {
|
|
if (!HTMLEditUtils::IsSameCSSColorValue(firstValue, otherValue)) {
|
|
return false;
|
|
}
|
|
} else if (!firstValue.Equals(otherValue)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace mozilla
|