Files
palemoon27/editor/libeditor/nsHTMLEditorStyle.cpp
T
roytam1 136569692c import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1212148 - Outline DialogValueHolder::Get() and create the variant directly. r=bz (1f3eb24a70)
- Bug 1210591, part 1 - Use nsVariantCC in various places. r=smaug (960ec7f9d3)
- Bug 1210591, part 2 - Cycle collect DataTransfer::mItems. r=smaug (3bc922fd42)
- Bug 931283, part 1 - Rename nsVariant to nsVariantBase. r=froydnj (1ba409004e)
- Bug 931283, part 2 - Split out nsVariant into a subclass. r=froydnj (6fc4012db0)
- Bug 931283, part 3 - Implement a cycle collected version of nsVariant. r=froydnj (9dd0e97d28)
- Bug 1208815 - Use the legacy opt-out in GetMozSourceNode to avoid crashes. r=bholley (b8aaa945fd)
- Bug 1217614 - Remove nsIDOMDataTransfer::mozGetDataAt and refactor associated code. r=bz (6a5a403776)
- Bug 1197002 - Use channel->Open2() in editor/libeditor/nsHTMLDataTransfer.cpp (r=sicking) (4995576e25)
- Bug 1169268 - Don't crash when pasting files. r=ndeakin (b52b5e9971)
- Bug 1156062 part 1b - Convert some callers of nsEditor::SplitNodeDeep; r=ehsan (c0ecbdf8ec)
- Bug 1156062 part 1c - Convert remaining callers of nsEditor::SplitNodeDeep; r=ehsan (e9cb446bef)
- Bug 1156062 part 1d - Clean up nsEditor::SplitNodeDeep; r=ehsan (d8513a605c)
- Bug 1156062 part 2 - Make GetBlockNodeParent and GetEnclosingTable return regular pointers; r=ehsan (8e80c4c53c)
- Bug 1156062 part 3 - New helper nsHTMLEditor::GetBlock; r=ehsan (9466c926f3)
- Bug 1145395 - Call nsINode::Length directly in nsHTMLEditor::CreateDOMFragmentFromPaste; r=ehsan (5b9d9b208c)
- Bug 1152229 Define FnLock as a KeyboardEvent.code value r=smaug (0f0ba16ba2)
- Bug 1192774 - Rename nsOuterWindowProxy::GetWindow to GetOuterWindow and add an assertion; r=khuey (5bbad7f98a)
- Bug 920734 - Part 1: Implement window.orientation and window.onorientationchange. r=baku (3f350de924)
- Bug 920734 - Part 2: Update tests to avoid using orientation as variable name. r=seth (a5c98bcc6f)
- Bug 1216072 - Pass aCallerIsChrome in more places. r=bz Bug 1215398 - Hoist the IsCallerChrome call in CanMoveResizeWindows to the script-only entry points. r=bz (3ff7d3d1a2)
- Bug 1107443. Make WindowProxy throw if you attempt to explicitly define a non-configurable property. r=peterv (d4659731fe)
- Bug 1196317 - Optimize CallbackObject::mCreationStack out from cycle collection graph when possible, r=mccr8 (6221298ec7)
- Bug 1184058 - AudioChannelAgent should be muted when used without a proper window object. r=alwu (3bc66c7727)
- Bug 1196361 - Remove the media.useAudioChannelService pref; r=baku (ba976d4402)
- Bug 1190040 - Part 2: Teach the audio channel service how to not notify audio-playback, and do that when a media element has no audio track; r=padenot (b163cbd6e2)
- Bug 1180940 - Changed return type of AudioDestinationNode::CreateAudioChannelAgent method to return errors, if any methods that it calls fail. Added code to handle the return value in AudioContext::Init(), and its callers. r=baku (03671277dc)
- Bug 1183304 - Unregister the actor when unlinked by CC. r=ehsan (ad0a3a7050)
- Bug 1191814 - WebSpeech synthesis API and AudioChannelService, r=eeejay (2140326485)
- Bug 1043165 - Part 1: Add TelephonyCall.disconnectedReason (webidl). r=hsinyi (a08a2e3b19)
- Bug 1043165 - Part 2: Add TelephonyCall.disconnectedReason (dom). r=hsinyi (1403f942fd)
- Bug 1043165 - Part 3: Add TelephonyCall.disconnectedReason (ril). r=hsinyi (cdb3991ed0)
- Bug 1043165 - Part 4: Update test cases. r=hsinyi (aa3f784b07)
- Bug 1131047 - Part 1: Obsolete some events (WebIDL). r=hsinyi (64e098a773)
- Bug 1131047 - Part 2: Obsolete some events (DOM). r=aknow (75caf5fa03)
- Bug 1161403 - Part 1: Deprecate internal states. r=aknow (dfd955bf8b)
- Bug 1161403 - Part 2: A new testcase. r=aknow (4798f3ec44)
- Bug 1129882 - create agent in telephony object. r=baku, r=hsinyi (1d7e7e44a4)
- Bug 1129882 - add mozInterrupt in telephony object. r=baku (f52465229b)
- Bug 1211511 - AudioChannelAgent::NotifyStartedPlaying and NotifyStoppedPlaying should use the same level of playback notification, r=roc (a73b0fda70)
- Bug 1187204 - AudioChannelService must exist before calling IsAudioChannelMutedByDefault(), r=alwu (665d082d4f)
- Bug 1089526 - Correct function name. r=baku (0706ced01f)
- Bug 1183925 - Part 1: Clean up the SpeakerManagerService shutdown; r=baku (7aefa655d5)
- Bug 1223734 - AudioChannelService should not be re-initialized after the XPCOM shutdown, r=smaug (1e35895d8d)
- Bug 1223734 - Add crashtest. r=baku (cfb753dcf2)
- Bug 1151992 - Use call state to decide the usages of output device. r=baku (37ea21895f)
- Bug 1215684 - Shut down AudioChannelService in the child process. r=mccr8 (389b137630)
- Bug 1183033 - Don't mute the system channel type. r=baku (da88f69119)
- Bug 1218396 - By default window-less AudioChannelAgents should be muted, r=alwu (3a74b6e8e0)
- Bug 1222902 - Create log system for the AudioChannel. r=baku. (4734efd27b)
- var-let (9849b3f3a9)
- Bug 1138354 - Add proxy authencation info for 'mozbrowserusernameandpasswordrequired' event. r=mayhemer (e1717f0113)
- Bug 1195801 - part 2: Tests for getStructuredData. r=kanru (221d9b4fa8)
- Bug 1169633 - [Browser API] getWebManifest(). r=kanru, r=bholley (904c043d16)
- Bug 1206581 - Implement notifyChannel() on AudioChannel API. r=kanru, r=baku. (42554161e7)
- Bug 1196654 - navigator.mozBrowserElementProxy for access Browser API methods from the embedded, r=kanru, sr=sicking (878b701d4b)
- Bug 1214133 - Preload ExtensionContent.jsm to get rid of the overhead. r=fabrice (50f6584735)
- Bug 1212321 - Stop painting on app launch. r=fabrice (a4cadc9577)
- Bug 1203068 - Add system type into kMozAudioChannelAttributeTable. r=baku (c908fef580)
- Bug 1208353 - Fix -Wshadow warnings in dom/audiochannel. r=padenot (85193d75a2)
- Bug 1184970 - Remove unused struct in AudioChannelService, r=ehsan (f0511a942b)
- Bug 1214491 - Rejigger entry points for nsGlobalWindow::Close. r=bz (6eccbffe1f)
- Bug 1212360. Split up the MozStorageChanged event into separate events for sessionstorage and localstorage. r=khuey (ff0047fe22)
- Bug 1162013. Process the Promise queue between adjacent setTimeout callback invocations when we're going through the callback list without returning to the event loop. r=smaug (f4966fa41e)
- Bug 1214491 - Remove CanCallerAccess check for XPIDL version of nsGlobalWindow::AddEventListener. r=bz (5bb993e1ea)
- Bug 1181176 - Pass along x and y position for context menus on Graphene. r=fabrice (652b528a92)
- Bug 1211481 - Expose screenX and screenY to context menu events. r=fabrice (e9679e9d6c)
- Bug 952456 - Part 2: Support copy image in BrowserElement. r=kchen (f91eb8ae95)
- Bug 952456 - Part 3: Enable test_copyimage.html on gonk/cocoa r=fabrice (be0d20fd99)
- Bug 1121463 - Support copy link on context menu. r=kanru (a981b7e05c)
- Backout c7c68c4389d6 (bug 1125713) for gaia app startup regression r=me (ed1d13993f)
- Bug 1178484 - Part 2: Regenerate gecko's in-tree parser. r=hsivone (42d5b50cf1)
- Bug 1164123 - Add support for a theme-group meta to make theme-color"themable". r=fabrice (40757cf461)
- Bug 1178484 - Part 4: Fire metachange event when a <meta> tag has Open Graph data. r=kanru (111cf1c43f)
- Bug 1178484 - Part 5: Test for metachange event when Open Graph property is loaded. r=kanru (0b464de5de)
- Bug 1156615 - (Browser API) ctrl/cmd/middle-click doesn't work if <a> element has children. r=kchen (673a7031f9)
- Bug 1210265 - Add a pref toggle for copying/pasting only plain text on B2G. r=timdream (4fbbad858e)
- Bug 1198522 - Add a flag to make sure customized menu is added. r=timdream, r=kanru (81e611997d)
- Bug 1168658 - Enable tracking protection in b2g r=francois (fc04c160ae)
- Bug 1189494 - Pass tracking information to BrowserElement's mozbrowsersecuritychange event. r=fabrice (bb24e94178)
- Bug 1170644 - Fix forced reflow in BrowserElementChildPreload. r=bz (73078e79fa)
- Bug 1178069 - Check devices capability before enable use of vp8 hardware acceleration using android.media.MediaCodecList and android.media.MediaCodecInfo r=jrmuizel (883351b87c)
- Bug 1164036 - Add an xpcshell test for graphics blocklist versioning. r=kats (c846c5b142)
- Bug 1183725 - Windows 10 not recognized in downloadable blocklist. r=bas (6a07f03ea3)
- Bug 1191404 - Update the comments to avoid confusion about what happens with blocklisting when the OS is unknown. No code change. r=milan (40bec16ce6)
- Bug 1215892 - Fix clang's -Wimplicit-fallthrough warnings in gfx. r=BenWa (83d160740c)
- var/const crashreporter (c9e5ccbb70)
- Bug 1225233 - Make MovableCellHasher a public interface, usable by embedders; r=jonco (603b00a836)
- Bug 1214508: SharedStubs - Part 1: Move the getprop stubs in to shared stub files, r=jandem (7cddae3b9e)
- Bug 1214508: SharedStubs - Part 2: Adjust the getprop stubs to report which engine they use, r=jandem (03ae066cad)
- Bug 1214508: SharedStubs - Part 3: Enable the getprop stubs in ionmonkey, r=jandem (e002db87c2)
- Bug 1214508: SharedStubs - Part 4: Make sure the frame pushed are set correctly for ion vmcalls, r=jandem (6238ed03c4)
- Bug 1214508: SharedStubs - Part 5: Fix ion vmcalls in shared stub, r=jandem (5a914ccfe6)
- Bug 1214508: SharedStubs - Part 6: Move the monitor stubs in to shared stub files, r=jandem (6f03cfaa1f)
- Bug 1214508: SharedStubs - Part 7: Adjust the getprop stubs to report which engine they use, r=jandem (e3849fde89)
- Bug 1214508: SharedStubs - Part 8: Dont sync for the decompiler in ion, r=jandem (7fe1a01bca)
- Bug 1214508: SharedStubs - Fix bustage in arm64, r=bustage on CLOSED TREE (0b60ba5974)
- Bug 1214508: SharedStubs - fix bustage, r=bustage (968a63544d)
- Bug 1212624 - Make WeakMapBase be a LinkedListElement, r=Waldo (744bb6eb8d)
- Bug 1223519 - Remove unused rekeying logic from WeakMap; r=sfink (3a4fe3b5df)
- Bug 1219288 - Ensure module environment object is not extensible and contains only non-configurable properties r=shu (0b9ac212b9)
2023-01-05 23:57:13 +08:00

1819 lines
63 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 "TypeInState.h"
#include "mozilla/Assertions.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Element.h"
#include "mozilla/mozalloc.h"
#include "nsAString.h"
#include "nsAttrName.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsCaseTreatment.h"
#include "nsComponentManagerUtils.h"
#include "nsDebug.h"
#include "nsEditRules.h"
#include "nsEditor.h"
#include "nsEditorUtils.h"
#include "nsError.h"
#include "nsGkAtoms.h"
#include "nsHTMLCSSUtils.h"
#include "nsHTMLEditUtils.h"
#include "nsHTMLEditor.h"
#include "nsIAtom.h"
#include "nsIContent.h"
#include "nsIContentIterator.h"
#include "nsIDOMCharacterData.h"
#include "nsIDOMElement.h"
#include "nsIDOMNode.h"
#include "nsIEditor.h"
#include "nsIEditorIMESupport.h"
#include "nsNameSpaceManager.h"
#include "nsINode.h"
#include "nsISupportsImpl.h"
#include "nsLiteralString.h"
#include "nsRange.h"
#include "nsReadableUtils.h"
#include "nsSelectionState.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsTextEditRules.h"
#include "nsTextEditUtils.h"
#include "nsUnicharUtils.h"
#include "nscore.h"
class nsISupports;
using namespace mozilla;
using namespace mozilla::dom;
static bool
IsEmptyTextNode(nsHTMLEditor* aThis, nsINode* aNode)
{
bool isEmptyTextNode = false;
return nsEditor::IsTextNode(aNode) &&
NS_SUCCEEDED(aThis->IsEmptyNode(aNode, &isEmptyTextNode)) &&
isEmptyTextNode;
}
NS_IMETHODIMP nsHTMLEditor::AddDefaultProperty(nsIAtom *aProperty,
const nsAString & aAttribute,
const nsAString & aValue)
{
nsString outValue;
int32_t index;
nsString attr(aAttribute);
if (TypeInState::FindPropInList(aProperty, attr, &outValue, mDefaultStyles, index))
{
PropItem *item = mDefaultStyles[index];
item->value = aValue;
}
else
{
nsString value(aValue);
PropItem *propItem = new PropItem(aProperty, attr, value);
mDefaultStyles.AppendElement(propItem);
}
return NS_OK;
}
NS_IMETHODIMP nsHTMLEditor::RemoveDefaultProperty(nsIAtom *aProperty,
const nsAString & aAttribute,
const nsAString & aValue)
{
nsString outValue;
int32_t index;
nsString attr(aAttribute);
if (TypeInState::FindPropInList(aProperty, attr, &outValue, mDefaultStyles, index))
{
delete mDefaultStyles[index];
mDefaultStyles.RemoveElementAt(index);
}
return NS_OK;
}
NS_IMETHODIMP nsHTMLEditor::RemoveAllDefaultProperties()
{
uint32_t j, defcon = mDefaultStyles.Length();
for (j=0; j<defcon; j++)
{
delete mDefaultStyles[j];
}
mDefaultStyles.Clear();
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::SetInlineProperty(nsIAtom* aProperty,
const nsAString& aAttribute,
const nsAString& aValue)
{
NS_ENSURE_TRUE(aProperty, NS_ERROR_NULL_POINTER);
NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
ForceCompositionEnd();
RefPtr<Selection> selection = GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
if (selection->Collapsed()) {
// Manipulating text attributes on a collapsed selection only sets state
// for the next text insertion
mTypeInState->SetProp(aProperty, aAttribute, aValue);
return NS_OK;
}
nsAutoEditBatch batchIt(this);
nsAutoRules beginRulesSniffing(this, EditAction::insertElement, nsIEditor::eNext);
nsAutoSelectionReset selectionResetter(selection, this);
nsAutoTxnsConserveSelection dontSpazMySelection(this);
bool cancel, handled;
nsTextRulesInfo ruleInfo(EditAction::setTextProperty);
// Protect the edit rules object from dying
nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
NS_ENSURE_SUCCESS(res, res);
if (!cancel && !handled) {
// Loop through the ranges in the selection
uint32_t rangeCount = selection->RangeCount();
for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; rangeIdx++) {
RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
// Adjust range to include any ancestors whose children are entirely
// selected
res = PromoteInlineRange(range);
NS_ENSURE_SUCCESS(res, res);
// Check for easy case: both range endpoints in same text node
nsCOMPtr<nsINode> startNode = range->GetStartParent();
nsCOMPtr<nsINode> endNode = range->GetEndParent();
if (startNode && startNode == endNode && startNode->GetAsText()) {
res = SetInlinePropertyOnTextNode(*startNode->GetAsText(),
range->StartOffset(),
range->EndOffset(),
*aProperty, &aAttribute, aValue);
NS_ENSURE_SUCCESS(res, res);
continue;
}
// Not the easy case. Range not contained in single text node. There
// are up to three phases here. There are all the nodes reported by the
// subtree iterator to be processed. And there are potentially a
// starting textnode and an ending textnode which are only partially
// contained by the range.
// Let's handle the nodes reported by the iterator. These nodes are
// entirely contained in the selection range. We build up a list of them
// (since doing operations on the document during iteration would perturb
// the iterator).
OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
// Iterate range and build up array
res = iter->Init(range);
// Init returns an error if there are no nodes in range. This can easily
// happen with the subtree iterator if the selection doesn't contain any
// *whole* nodes.
if (NS_SUCCEEDED(res)) {
for (; !iter->IsDone(); iter->Next()) {
OwningNonNull<nsINode> node = *iter->GetCurrentNode();
if (node->IsContent() && IsEditable(node)) {
arrayOfNodes.AppendElement(*node->AsContent());
}
}
}
// First check the start parent of the range to see if it needs to be
// separately handled (it does if it's a text node, due to how the
// subtree iterator works - it will not have reported it).
if (startNode && startNode->GetAsText() && IsEditable(startNode)) {
res = SetInlinePropertyOnTextNode(*startNode->GetAsText(),
range->StartOffset(),
startNode->Length(), *aProperty,
&aAttribute, aValue);
NS_ENSURE_SUCCESS(res, res);
}
// Then loop through the list, set the property on each node
for (auto& node : arrayOfNodes) {
res = SetInlinePropertyOnNode(*node, *aProperty, &aAttribute, aValue);
NS_ENSURE_SUCCESS(res, res);
}
// Last check the end parent of the range to see if it needs to be
// separately handled (it does if it's a text node, due to how the
// subtree iterator works - it will not have reported it).
if (endNode && endNode->GetAsText() && IsEditable(endNode)) {
res = SetInlinePropertyOnTextNode(*endNode->GetAsText(), 0,
range->EndOffset(), *aProperty,
&aAttribute, aValue);
NS_ENSURE_SUCCESS(res, res);
}
}
}
if (!cancel) {
// Post-process
return mRules->DidDoAction(selection, &ruleInfo, res);
}
return NS_OK;
}
// Helper function for SetInlinePropertyOn*: is aNode a simple old <b>, <font>,
// <span style="">, etc. that we can reuse instead of creating a new one?
bool
nsHTMLEditor::IsSimpleModifiableNode(nsIContent* aContent,
nsIAtom* aProperty,
const nsAString* aAttribute,
const nsAString* aValue)
{
// aContent can be null, in which case we'll return false in a few lines
MOZ_ASSERT(aProperty);
MOZ_ASSERT_IF(aAttribute, aValue);
nsCOMPtr<dom::Element> element = do_QueryInterface(aContent);
if (!element) {
return false;
}
// First check for <b>, <i>, etc.
if (element->IsHTMLElement(aProperty) && !element->GetAttrCount() &&
(!aAttribute || aAttribute->IsEmpty())) {
return true;
}
// Special cases for various equivalencies: <strong>, <em>, <s>
if (!element->GetAttrCount() &&
((aProperty == nsGkAtoms::b &&
element->IsHTMLElement(nsGkAtoms::strong)) ||
(aProperty == nsGkAtoms::i &&
element->IsHTMLElement(nsGkAtoms::em)) ||
(aProperty == nsGkAtoms::strike &&
element->IsHTMLElement(nsGkAtoms::s)))) {
return true;
}
// Now look for things like <font>
if (aAttribute && !aAttribute->IsEmpty()) {
nsCOMPtr<nsIAtom> atom = do_GetAtom(*aAttribute);
MOZ_ASSERT(atom);
nsString attrValue;
if (element->IsHTMLElement(aProperty) &&
IsOnlyAttribute(element, *aAttribute) &&
element->GetAttr(kNameSpaceID_None, atom, attrValue) &&
attrValue.Equals(*aValue, nsCaseInsensitiveStringComparator())) {
// This is not quite correct, because it excludes cases like
// <font face=000> being the same as <font face=#000000>.
// Property-specific handling is needed (bug 760211).
return true;
}
}
// No luck so far. Now we check for a <span> with a single style=""
// attribute that sets only the style we're looking for, if this type of
// style supports it
if (!mHTMLCSSUtils->IsCSSEditableProperty(element, aProperty, aAttribute) ||
!element->IsHTMLElement(nsGkAtoms::span) ||
element->GetAttrCount() != 1 ||
!element->HasAttr(kNameSpaceID_None, nsGkAtoms::style)) {
return false;
}
// Some CSS styles are not so simple. For instance, underline is
// "text-decoration: underline", which decomposes into four different text-*
// properties. So for now, we just create a span, add the desired style, and
// see if it matches.
nsCOMPtr<Element> newSpan = CreateHTMLContent(nsGkAtoms::span);
NS_ASSERTION(newSpan, "CreateHTMLContent failed");
NS_ENSURE_TRUE(newSpan, false);
mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(newSpan, aProperty,
aAttribute, aValue,
/*suppress transaction*/ true);
return mHTMLCSSUtils->ElementsSameStyle(newSpan, element);
}
nsresult
nsHTMLEditor::SetInlinePropertyOnTextNode(Text& aText,
int32_t aStartOffset,
int32_t aEndOffset,
nsIAtom& aProperty,
const nsAString* aAttribute,
const nsAString& aValue)
{
if (!aText.GetParentNode() ||
!CanContainTag(*aText.GetParentNode(), aProperty)) {
return NS_OK;
}
// Don't need to do anything if no characters actually selected
if (aStartOffset == aEndOffset) {
return NS_OK;
}
// Don't need to do anything if property already set on node
if (mHTMLCSSUtils->IsCSSEditableProperty(&aText, &aProperty, aAttribute)) {
// The HTML styles defined by aProperty/aAttribute have a CSS equivalence
// for node; let's check if it carries those CSS styles
if (mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(&aText, &aProperty,
aAttribute, aValue, nsHTMLCSSUtils::eComputed)) {
return NS_OK;
}
} else if (IsTextPropertySetByContent(&aText, &aProperty, aAttribute,
&aValue)) {
return NS_OK;
}
// Do we need to split the text node?
ErrorResult rv;
RefPtr<Text> text = &aText;
if (uint32_t(aEndOffset) != aText.Length()) {
// We need to split off back of text node
text = SplitNode(aText, aEndOffset, rv)->GetAsText();
NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
}
if (aStartOffset) {
// We need to split off front of text node
SplitNode(*text, aStartOffset, rv);
NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
}
if (aAttribute) {
// Look for siblings that are correct type of node
nsIContent* sibling = GetPriorHTMLSibling(text);
if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
// Previous sib is already right kind of inline node; slide this over
return MoveNode(text, sibling, -1);
}
sibling = GetNextHTMLSibling(text);
if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
// Following sib is already right kind of inline node; slide this over
return MoveNode(text, sibling, 0);
}
}
// Reparent the node inside inline node with appropriate {attribute,value}
return SetInlinePropertyOnNode(*text, aProperty, aAttribute, aValue);
}
nsresult
nsHTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent& aNode,
nsIAtom& aProperty,
const nsAString* aAttribute,
const nsAString& aValue)
{
nsCOMPtr<nsIAtom> attrAtom = aAttribute ? do_GetAtom(*aAttribute) : nullptr;
// If this is an element that can't be contained in a span, we have to
// recurse to its children.
if (!TagCanContain(*nsGkAtoms::span, aNode)) {
if (aNode.HasChildren()) {
nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
// Populate the list.
for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild();
child;
child = child->GetNextSibling()) {
if (IsEditable(child) && !IsEmptyTextNode(this, child)) {
arrayOfNodes.AppendElement(*child);
}
}
// Then loop through the list, set the property on each node.
for (auto& node : arrayOfNodes) {
nsresult rv = SetInlinePropertyOnNode(node, aProperty, aAttribute,
aValue);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
// First check if there's an adjacent sibling we can put our node into.
nsresult res;
nsCOMPtr<nsIContent> previousSibling = GetPriorHTMLSibling(&aNode);
nsCOMPtr<nsIContent> nextSibling = GetNextHTMLSibling(&aNode);
if (IsSimpleModifiableNode(previousSibling, &aProperty, aAttribute, &aValue)) {
res = MoveNode(&aNode, previousSibling, -1);
NS_ENSURE_SUCCESS(res, res);
if (IsSimpleModifiableNode(nextSibling, &aProperty, aAttribute, &aValue)) {
res = JoinNodes(*previousSibling, *nextSibling);
NS_ENSURE_SUCCESS(res, res);
}
return NS_OK;
}
if (IsSimpleModifiableNode(nextSibling, &aProperty, aAttribute, &aValue)) {
res = MoveNode(&aNode, nextSibling, 0);
NS_ENSURE_SUCCESS(res, res);
return NS_OK;
}
// Don't need to do anything if property already set on node
if (mHTMLCSSUtils->IsCSSEditableProperty(&aNode, &aProperty, aAttribute)) {
if (mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(
&aNode, &aProperty, aAttribute, aValue, nsHTMLCSSUtils::eComputed)) {
return NS_OK;
}
} else if (IsTextPropertySetByContent(&aNode, &aProperty,
aAttribute, &aValue)) {
return NS_OK;
}
bool useCSS = (IsCSSEnabled() &&
mHTMLCSSUtils->IsCSSEditableProperty(&aNode, &aProperty, aAttribute)) ||
// bgcolor is always done using CSS
aAttribute->EqualsLiteral("bgcolor");
if (useCSS) {
nsCOMPtr<dom::Element> tmp;
// We only add style="" to <span>s with no attributes (bug 746515). If we
// don't have one, we need to make one.
if (aNode.IsHTMLElement(nsGkAtoms::span) &&
!aNode.AsElement()->GetAttrCount()) {
tmp = aNode.AsElement();
} else {
tmp = InsertContainerAbove(&aNode, nsGkAtoms::span);
NS_ENSURE_STATE(tmp);
}
// Add the CSS styles corresponding to the HTML style request
int32_t count;
res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(tmp->AsDOMNode(),
&aProperty, aAttribute,
&aValue, &count, false);
NS_ENSURE_SUCCESS(res, res);
return NS_OK;
}
// is it already the right kind of node, but with wrong attribute?
if (aNode.IsHTMLElement(&aProperty)) {
// Just set the attribute on it.
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(&aNode);
return SetAttribute(elem, *aAttribute, aValue);
}
// ok, chuck it in its very own container
nsCOMPtr<Element> tmp = InsertContainerAbove(&aNode, &aProperty, attrAtom,
&aValue);
NS_ENSURE_STATE(tmp);
return NS_OK;
}
nsresult
nsHTMLEditor::SetInlinePropertyOnNode(nsIContent& aNode,
nsIAtom& aProperty,
const nsAString* aAttribute,
const nsAString& aValue)
{
nsCOMPtr<nsIContent> previousSibling = aNode.GetPreviousSibling(),
nextSibling = aNode.GetNextSibling();
NS_ENSURE_STATE(aNode.GetParentNode());
OwningNonNull<nsINode> parent = *aNode.GetParentNode();
nsresult res = RemoveStyleInside(aNode.AsDOMNode(), &aProperty, aAttribute);
NS_ENSURE_SUCCESS(res, res);
if (aNode.GetParentNode()) {
// The node is still where it was
return SetInlinePropertyOnNodeImpl(aNode, aProperty,
aAttribute, aValue);
}
// It's vanished. Use the old siblings for reference to construct a
// list. But first, verify that the previous/next siblings are still
// where we expect them; otherwise we have to give up.
if ((previousSibling && previousSibling->GetParentNode() != parent) ||
(nextSibling && nextSibling->GetParentNode() != parent)) {
return NS_ERROR_UNEXPECTED;
}
nsTArray<OwningNonNull<nsIContent>> nodesToSet;
nsCOMPtr<nsIContent> cur = previousSibling
? previousSibling->GetNextSibling() : parent->GetFirstChild();
for (; cur && cur != nextSibling; cur = cur->GetNextSibling()) {
if (IsEditable(cur)) {
nodesToSet.AppendElement(*cur);
}
}
for (auto& node : nodesToSet) {
res = SetInlinePropertyOnNodeImpl(node, aProperty, aAttribute, aValue);
NS_ENSURE_SUCCESS(res, res);
}
return NS_OK;
}
nsresult
nsHTMLEditor::SplitStyleAboveRange(nsRange* inRange, nsIAtom* aProperty,
const nsAString* aAttribute)
{
NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
nsresult res;
nsCOMPtr<nsIDOMNode> startNode, endNode, origStartNode;
int32_t startOffset, endOffset;
res = inRange->GetStartContainer(getter_AddRefs(startNode));
NS_ENSURE_SUCCESS(res, res);
res = inRange->GetStartOffset(&startOffset);
NS_ENSURE_SUCCESS(res, res);
res = inRange->GetEndContainer(getter_AddRefs(endNode));
NS_ENSURE_SUCCESS(res, res);
res = inRange->GetEndOffset(&endOffset);
NS_ENSURE_SUCCESS(res, res);
origStartNode = startNode;
// split any matching style nodes above the start of range
{
nsAutoTrackDOMPoint tracker(mRangeUpdater, address_of(endNode), &endOffset);
res = SplitStyleAbovePoint(address_of(startNode), &startOffset, aProperty, aAttribute);
NS_ENSURE_SUCCESS(res, res);
}
// second verse, same as the first...
res = SplitStyleAbovePoint(address_of(endNode), &endOffset, aProperty, aAttribute);
NS_ENSURE_SUCCESS(res, res);
// reset the range
res = inRange->SetStart(startNode, startOffset);
NS_ENSURE_SUCCESS(res, res);
res = inRange->SetEnd(endNode, endOffset);
return res;
}
nsresult nsHTMLEditor::SplitStyleAbovePoint(nsCOMPtr<nsIDOMNode> *aNode,
int32_t *aOffset,
nsIAtom *aProperty, // null here means we split all properties
const nsAString *aAttribute,
nsCOMPtr<nsIDOMNode> *outLeftNode,
nsCOMPtr<nsIDOMNode> *outRightNode)
{
NS_ENSURE_TRUE(aNode && *aNode && aOffset, NS_ERROR_NULL_POINTER);
if (outLeftNode) *outLeftNode = nullptr;
if (outRightNode) *outRightNode = nullptr;
// split any matching style nodes above the node/offset
nsCOMPtr<nsIContent> node = do_QueryInterface(*aNode);
NS_ENSURE_STATE(node);
int32_t offset;
bool useCSS = IsCSSEnabled();
bool isSet;
while (node && !IsBlockNode(node) && node->GetParentNode() &&
IsEditable(node->GetParentNode())) {
isSet = false;
if (useCSS && mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty, aAttribute)) {
// the HTML style defined by aProperty/aAttribute has a CSS equivalence
// in this implementation for the node; let's check if it carries those css styles
nsAutoString firstValue;
mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(GetAsDOMNode(node),
aProperty, aAttribute, isSet, firstValue, nsHTMLCSSUtils::eSpecified);
}
if (// node is the correct inline prop
(aProperty && node->IsHTMLElement(aProperty)) ||
// node is href - test if really <a href=...
(aProperty == nsGkAtoms::href && nsHTMLEditUtils::IsLink(node)) ||
// or node is any prop, and we asked to split them all
(!aProperty && NodeIsProperty(GetAsDOMNode(node))) ||
// or the style is specified in the style attribute
isSet) {
// found a style node we need to split
nsCOMPtr<nsIContent> outLeftContent, outRightContent;
nsCOMPtr<nsIContent> nodeParam = do_QueryInterface(*aNode);
NS_ENSURE_STATE(nodeParam || !*aNode);
offset = SplitNodeDeep(*node, *nodeParam, *aOffset, EmptyContainers::yes,
getter_AddRefs(outLeftContent),
getter_AddRefs(outRightContent));
NS_ENSURE_TRUE(offset != -1, NS_ERROR_FAILURE);
// reset startNode/startOffset
*aNode = GetAsDOMNode(node->GetParent());
*aOffset = offset;
if (outLeftNode) {
*outLeftNode = GetAsDOMNode(outLeftContent);
}
if (outRightNode) {
*outRightNode = GetAsDOMNode(outRightContent);
}
}
node = node->GetParent();
}
return NS_OK;
}
nsresult
nsHTMLEditor::ClearStyle(nsCOMPtr<nsIDOMNode>* aNode, int32_t* aOffset,
nsIAtom* aProperty, const nsAString* aAttribute)
{
nsCOMPtr<nsIDOMNode> leftNode, rightNode, tmp;
nsresult res = SplitStyleAbovePoint(aNode, aOffset, aProperty, aAttribute,
address_of(leftNode),
address_of(rightNode));
NS_ENSURE_SUCCESS(res, res);
if (leftNode) {
bool bIsEmptyNode;
IsEmptyNode(leftNode, &bIsEmptyNode, false, true);
if (bIsEmptyNode) {
// delete leftNode if it became empty
res = DeleteNode(leftNode);
NS_ENSURE_SUCCESS(res, res);
}
}
if (rightNode) {
nsCOMPtr<nsINode> rightNode_ = do_QueryInterface(rightNode);
NS_ENSURE_STATE(rightNode_);
nsCOMPtr<nsIDOMNode> secondSplitParent =
GetAsDOMNode(GetLeftmostChild(rightNode_));
// don't try to split non-containers (br's, images, hr's, etc)
if (!secondSplitParent) {
secondSplitParent = rightNode;
}
nsCOMPtr<Element> savedBR;
if (!IsContainer(secondSplitParent)) {
if (nsTextEditUtils::IsBreak(secondSplitParent)) {
savedBR = do_QueryInterface(secondSplitParent);
NS_ENSURE_STATE(savedBR);
}
secondSplitParent->GetParentNode(getter_AddRefs(tmp));
secondSplitParent = tmp;
}
*aOffset = 0;
res = SplitStyleAbovePoint(address_of(secondSplitParent),
aOffset, aProperty, aAttribute,
address_of(leftNode), address_of(rightNode));
NS_ENSURE_SUCCESS(res, res);
// should be impossible to not get a new leftnode here
nsCOMPtr<nsINode> leftNode_ = do_QueryInterface(leftNode);
NS_ENSURE_TRUE(leftNode_, NS_ERROR_FAILURE);
nsCOMPtr<nsINode> newSelParent = GetLeftmostChild(leftNode_);
if (!newSelParent) {
newSelParent = do_QueryInterface(leftNode);
NS_ENSURE_STATE(newSelParent);
}
// If rightNode starts with a br, suck it out of right node and into
// leftNode. This is so we you don't revert back to the previous style
// if you happen to click at the end of a line.
if (savedBR) {
res = MoveNode(savedBR, newSelParent, 0);
NS_ENSURE_SUCCESS(res, res);
}
bool bIsEmptyNode;
IsEmptyNode(rightNode, &bIsEmptyNode, false, true);
if (bIsEmptyNode) {
// delete rightNode if it became empty
res = DeleteNode(rightNode);
NS_ENSURE_SUCCESS(res, res);
}
// remove the style on this new hierarchy
int32_t newSelOffset = 0;
{
// Track the point at the new hierarchy. This is so we can know where
// to put the selection after we call RemoveStyleInside().
// RemoveStyleInside() could remove any and all of those nodes, so I
// have to use the range tracking system to find the right spot to put
// selection.
nsAutoTrackDOMPoint tracker(mRangeUpdater,
address_of(newSelParent), &newSelOffset);
res = RemoveStyleInside(leftNode, aProperty, aAttribute);
NS_ENSURE_SUCCESS(res, res);
}
// reset our node offset values to the resulting new sel point
*aNode = GetAsDOMNode(newSelParent);
*aOffset = newSelOffset;
}
return NS_OK;
}
bool nsHTMLEditor::NodeIsProperty(nsIDOMNode *aNode)
{
NS_ENSURE_TRUE(aNode, false);
if (!IsContainer(aNode)) return false;
if (!IsEditable(aNode)) return false;
if (IsBlockNode(aNode)) return false;
if (NodeIsType(aNode, nsGkAtoms::a)) {
return false;
}
return true;
}
nsresult nsHTMLEditor::ApplyDefaultProperties()
{
nsresult res = NS_OK;
uint32_t j, defcon = mDefaultStyles.Length();
for (j=0; j<defcon; j++)
{
PropItem *propItem = mDefaultStyles[j];
NS_ENSURE_TRUE(propItem, NS_ERROR_NULL_POINTER);
res = SetInlineProperty(propItem->tag, propItem->attr, propItem->value);
NS_ENSURE_SUCCESS(res, res);
}
return res;
}
nsresult nsHTMLEditor::RemoveStyleInside(nsIDOMNode *aNode,
// null here means remove all properties
nsIAtom *aProperty,
const nsAString *aAttribute,
const bool aChildrenOnly)
{
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
if (IsTextNode(aNode)) {
return NS_OK;
}
nsresult res;
nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
NS_ENSURE_STATE(content);
// first process the children
nsCOMPtr<nsIDOMNode> child, tmp;
aNode->GetFirstChild(getter_AddRefs(child));
while (child) {
// cache next sibling since we might remove child
child->GetNextSibling(getter_AddRefs(tmp));
res = RemoveStyleInside(child, aProperty, aAttribute);
NS_ENSURE_SUCCESS(res, res);
child = tmp;
}
// then process the node itself
if (!aChildrenOnly &&
(
// node is prop we asked for
(aProperty && NodeIsType(aNode, aProperty)) ||
// but check for link (<a href=...)
(aProperty == nsGkAtoms::href && nsHTMLEditUtils::IsLink(aNode)) ||
// and for named anchors
(aProperty == nsGkAtoms::name && nsHTMLEditUtils::IsNamedAnchor(aNode)) ||
// or node is any prop and we asked for that
(!aProperty && NodeIsProperty(aNode))
)
) {
// if we weren't passed an attribute, then we want to
// remove any matching inlinestyles entirely
if (!aAttribute || aAttribute->IsEmpty()) {
NS_NAMED_LITERAL_STRING(styleAttr, "style");
NS_NAMED_LITERAL_STRING(classAttr, "class");
bool hasStyleAttr = HasAttr(aNode, &styleAttr);
bool hasClassAttr = HasAttr(aNode, &classAttr);
if (aProperty && (hasStyleAttr || hasClassAttr)) {
// aNode carries inline styles or a class attribute so we can't
// just remove the element... We need to create above the element
// a span that will carry those styles or class, then we can delete
// the node.
nsCOMPtr<Element> spanNode =
InsertContainerAbove(content, nsGkAtoms::span);
NS_ENSURE_STATE(spanNode);
res = CloneAttribute(styleAttr, spanNode->AsDOMNode(), aNode);
NS_ENSURE_SUCCESS(res, res);
res = CloneAttribute(classAttr, spanNode->AsDOMNode(), aNode);
NS_ENSURE_SUCCESS(res, res);
}
res = RemoveContainer(content);
NS_ENSURE_SUCCESS(res, res);
} else {
// otherwise we just want to eliminate the attribute
if (HasAttr(aNode, aAttribute)) {
// if this matching attribute is the ONLY one on the node,
// then remove the whole node. Otherwise just nix the attribute.
if (IsOnlyAttribute(aNode, aAttribute)) {
res = RemoveContainer(content);
} else {
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(aNode);
NS_ENSURE_TRUE(elem, NS_ERROR_NULL_POINTER);
res = RemoveAttribute(elem, *aAttribute);
}
NS_ENSURE_SUCCESS(res, res);
}
}
}
if (!aChildrenOnly &&
mHTMLCSSUtils->IsCSSEditableProperty(content, aProperty, aAttribute)) {
// the HTML style defined by aProperty/aAttribute has a CSS equivalence in
// this implementation for the node aNode; let's check if it carries those
// css styles
nsAutoString propertyValue;
bool isSet;
mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(aNode, aProperty,
aAttribute, isSet, propertyValue, nsHTMLCSSUtils::eSpecified);
if (isSet) {
// yes, tmp has the corresponding css declarations in its style attribute
// let's remove them
mHTMLCSSUtils->RemoveCSSEquivalentToHTMLStyle(aNode,
aProperty,
aAttribute,
&propertyValue,
false);
// remove the node if it is a span or font, if its style attribute is
// empty or absent, and if it does not have a class nor an id
RemoveElementIfNoStyleOrIdOrClass(aNode);
}
}
if (!aChildrenOnly &&
(
// Or node is big or small and we are setting font size
aProperty == nsGkAtoms::font &&
(nsHTMLEditUtils::IsBig(aNode) || nsHTMLEditUtils::IsSmall(aNode)) &&
(aAttribute && aAttribute->LowerCaseEqualsLiteral("size"))
)
) {
// if we are setting font size, remove any nested bigs and smalls
return RemoveContainer(content);
}
return NS_OK;
}
bool nsHTMLEditor::IsOnlyAttribute(nsIDOMNode *aNode,
const nsAString *aAttribute)
{
NS_ENSURE_TRUE(aNode && aAttribute, false); // ooops
nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
NS_ENSURE_TRUE(content, false); // ooops
return IsOnlyAttribute(content, *aAttribute);
}
bool
nsHTMLEditor::IsOnlyAttribute(const nsIContent* aContent,
const nsAString& aAttribute)
{
MOZ_ASSERT(aContent);
uint32_t attrCount = aContent->GetAttrCount();
for (uint32_t i = 0; i < attrCount; ++i) {
const nsAttrName* name = aContent->GetAttrNameAt(i);
if (!name->NamespaceEquals(kNameSpaceID_None)) {
return false;
}
nsAutoString attrString;
name->LocalName()->ToString(attrString);
// if it's the attribute we know about, or a special _moz attribute,
// keep looking
if (!attrString.Equals(aAttribute, nsCaseInsensitiveStringComparator()) &&
!StringBeginsWith(attrString, NS_LITERAL_STRING("_moz"))) {
return false;
}
}
// if we made it through all of them without finding a real attribute
// other than aAttribute, then return true
return true;
}
bool nsHTMLEditor::HasAttr(nsIDOMNode* aNode,
const nsAString* aAttribute)
{
NS_ENSURE_TRUE(aNode, false);
if (!aAttribute || aAttribute->IsEmpty()) {
// everybody has the 'null' attribute
return true;
}
// get element
nsCOMPtr<dom::Element> element = do_QueryInterface(aNode);
NS_ENSURE_TRUE(element, false);
nsCOMPtr<nsIAtom> atom = do_GetAtom(*aAttribute);
NS_ENSURE_TRUE(atom, false);
return element->HasAttr(kNameSpaceID_None, atom);
}
nsresult
nsHTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange* inRange)
{
NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
nsresult res;
nsCOMPtr<nsIDOMNode> startNode, endNode, parent, tmp;
int32_t startOffset, endOffset, tmpOffset;
res = inRange->GetStartContainer(getter_AddRefs(startNode));
NS_ENSURE_SUCCESS(res, res);
res = inRange->GetStartOffset(&startOffset);
NS_ENSURE_SUCCESS(res, res);
res = inRange->GetEndContainer(getter_AddRefs(endNode));
NS_ENSURE_SUCCESS(res, res);
res = inRange->GetEndOffset(&endOffset);
NS_ENSURE_SUCCESS(res, res);
tmp = startNode;
while ( tmp &&
!nsTextEditUtils::IsBody(tmp) &&
!nsHTMLEditUtils::IsNamedAnchor(tmp))
{
parent = GetNodeLocation(tmp, &tmpOffset);
tmp = parent;
}
NS_ENSURE_TRUE(tmp, NS_ERROR_NULL_POINTER);
if (nsHTMLEditUtils::IsNamedAnchor(tmp))
{
parent = GetNodeLocation(tmp, &tmpOffset);
startNode = parent;
startOffset = tmpOffset;
}
tmp = endNode;
while ( tmp &&
!nsTextEditUtils::IsBody(tmp) &&
!nsHTMLEditUtils::IsNamedAnchor(tmp))
{
parent = GetNodeLocation(tmp, &tmpOffset);
tmp = parent;
}
NS_ENSURE_TRUE(tmp, NS_ERROR_NULL_POINTER);
if (nsHTMLEditUtils::IsNamedAnchor(tmp))
{
parent = GetNodeLocation(tmp, &tmpOffset);
endNode = parent;
endOffset = tmpOffset + 1;
}
res = inRange->SetStart(startNode, startOffset);
NS_ENSURE_SUCCESS(res, res);
res = inRange->SetEnd(endNode, endOffset);
return res;
}
nsresult
nsHTMLEditor::PromoteInlineRange(nsRange* inRange)
{
NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
nsresult res;
nsCOMPtr<nsIDOMNode> startNode, endNode, parent;
int32_t startOffset, endOffset;
res = inRange->GetStartContainer(getter_AddRefs(startNode));
NS_ENSURE_SUCCESS(res, res);
res = inRange->GetStartOffset(&startOffset);
NS_ENSURE_SUCCESS(res, res);
res = inRange->GetEndContainer(getter_AddRefs(endNode));
NS_ENSURE_SUCCESS(res, res);
res = inRange->GetEndOffset(&endOffset);
NS_ENSURE_SUCCESS(res, res);
while ( startNode &&
!nsTextEditUtils::IsBody(startNode) &&
IsEditable(startNode) &&
IsAtFrontOfNode(startNode, startOffset) )
{
parent = GetNodeLocation(startNode, &startOffset);
startNode = parent;
}
NS_ENSURE_TRUE(startNode, NS_ERROR_NULL_POINTER);
while ( endNode &&
!nsTextEditUtils::IsBody(endNode) &&
IsEditable(endNode) &&
IsAtEndOfNode(endNode, endOffset) )
{
parent = GetNodeLocation(endNode, &endOffset);
endNode = parent;
endOffset++; // we are AFTER this node
}
NS_ENSURE_TRUE(endNode, NS_ERROR_NULL_POINTER);
res = inRange->SetStart(startNode, startOffset);
NS_ENSURE_SUCCESS(res, res);
res = inRange->SetEnd(endNode, endOffset);
return res;
}
bool nsHTMLEditor::IsAtFrontOfNode(nsIDOMNode *aNode, int32_t aOffset)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
NS_ENSURE_TRUE(node, false);
if (!aOffset) {
return true;
}
if (IsTextNode(aNode))
{
return false;
}
else
{
nsCOMPtr<nsIContent> firstNode = GetFirstEditableChild(*node);
NS_ENSURE_TRUE(firstNode, true);
int32_t offset = node->IndexOf(firstNode);
if (offset < aOffset) return false;
return true;
}
}
bool nsHTMLEditor::IsAtEndOfNode(nsIDOMNode *aNode, int32_t aOffset)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
NS_ENSURE_TRUE(node, false);
uint32_t len = node->Length();
if (aOffset == (int32_t)len) return true;
if (IsTextNode(aNode))
{
return false;
}
else
{
nsCOMPtr<nsIContent> lastNode = GetLastEditableChild(*node);
NS_ENSURE_TRUE(lastNode, true);
int32_t offset = node->IndexOf(lastNode);
if (offset < aOffset) return true;
return false;
}
}
nsresult
nsHTMLEditor::GetInlinePropertyBase(nsIAtom& aProperty,
const nsAString* aAttribute,
const nsAString* aValue,
bool* aFirst,
bool* aAny,
bool* aAll,
nsAString* outValue,
bool aCheckDefaults)
{
*aAny = false;
*aAll = true;
*aFirst = false;
bool first = true;
RefPtr<Selection> selection = GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
bool isCollapsed = selection->Collapsed();
RefPtr<nsRange> range = selection->GetRangeAt(0);
// XXX: Should be a while loop, to get each separate range
// XXX: ERROR_HANDLING can currentItem be null?
if (range) {
// For each range, set a flag
bool firstNodeInRange = true;
if (isCollapsed) {
nsCOMPtr<nsINode> collapsedNode = range->GetStartParent();
NS_ENSURE_TRUE(collapsedNode, NS_ERROR_FAILURE);
bool isSet, theSetting;
nsString tOutString;
if (aAttribute) {
nsString tString(*aAttribute);
mTypeInState->GetTypingState(isSet, theSetting, &aProperty, tString,
&tOutString);
if (outValue) {
outValue->Assign(tOutString);
}
} else {
mTypeInState->GetTypingState(isSet, theSetting, &aProperty);
}
if (isSet) {
*aFirst = *aAny = *aAll = theSetting;
return NS_OK;
}
if (mHTMLCSSUtils->IsCSSEditableProperty(collapsedNode, &aProperty,
aAttribute)) {
*aFirst = *aAny = *aAll =
mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(collapsedNode,
&aProperty, aAttribute, tOutString, nsHTMLCSSUtils::eComputed);
if (outValue) {
outValue->Assign(tOutString);
}
return NS_OK;
}
isSet = IsTextPropertySetByContent(collapsedNode, &aProperty,
aAttribute, aValue, outValue);
*aFirst = *aAny = *aAll = isSet;
if (!isSet && aCheckDefaults) {
// Style not set, but if it is a default then it will appear if content
// is inserted, so we should report it as set (analogous to
// TypeInState).
int32_t index;
if (aAttribute && TypeInState::FindPropInList(&aProperty, *aAttribute,
outValue, mDefaultStyles,
index)) {
*aFirst = *aAny = *aAll = true;
if (outValue) {
outValue->Assign(mDefaultStyles[index]->value);
}
}
}
return NS_OK;
}
// Non-collapsed selection
nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
nsAutoString firstValue, theValue;
nsCOMPtr<nsINode> endNode = range->GetEndParent();
int32_t endOffset = range->EndOffset();
for (iter->Init(range); !iter->IsDone(); iter->Next()) {
if (!iter->GetCurrentNode()->IsContent()) {
continue;
}
nsCOMPtr<nsIContent> content = iter->GetCurrentNode()->AsContent();
if (content->IsHTMLElement(nsGkAtoms::body)) {
break;
}
// just ignore any non-editable nodes
if (content->GetAsText() && (!IsEditable(content) ||
IsEmptyTextNode(this, content))) {
continue;
}
if (content->GetAsText()) {
if (!isCollapsed && first && firstNodeInRange) {
firstNodeInRange = false;
if (range->StartOffset() == (int32_t)content->Length()) {
continue;
}
} else if (content == endNode && !endOffset) {
continue;
}
} else if (content->IsElement()) {
// handle non-text leaf nodes here
continue;
}
bool isSet = false;
if (first) {
if (mHTMLCSSUtils->IsCSSEditableProperty(content, &aProperty,
aAttribute)) {
// The HTML styles defined by aProperty/aAttribute have a CSS
// equivalence in this implementation for node; let's check if it
// carries those CSS styles
if (aValue) {
firstValue.Assign(*aValue);
}
isSet = mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(content,
&aProperty, aAttribute, firstValue, nsHTMLCSSUtils::eComputed);
} else {
isSet = IsTextPropertySetByContent(content, &aProperty, aAttribute,
aValue, &firstValue);
}
*aFirst = isSet;
first = false;
if (outValue) {
*outValue = firstValue;
}
} else {
if (mHTMLCSSUtils->IsCSSEditableProperty(content, &aProperty,
aAttribute)) {
// The HTML styles defined by aProperty/aAttribute have a CSS
// equivalence in this implementation for node; let's check if it
// carries those CSS styles
if (aValue) {
theValue.Assign(*aValue);
}
isSet = mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(content,
&aProperty, aAttribute, theValue, nsHTMLCSSUtils::eComputed);
} else {
isSet = IsTextPropertySetByContent(content, &aProperty, aAttribute,
aValue, &theValue);
}
if (firstValue != theValue) {
*aAll = false;
}
}
if (isSet) {
*aAny = true;
} else {
*aAll = false;
}
}
}
if (!*aAny) {
// make sure that if none of the selection is set, we don't report all is
// set
*aAll = false;
}
return NS_OK;
}
NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty,
const nsAString &aAttribute,
const nsAString &aValue,
bool *aFirst,
bool *aAny,
bool *aAll)
{
NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER);
const nsAString *att = nullptr;
if (!aAttribute.IsEmpty())
att = &aAttribute;
const nsAString *val = nullptr;
if (!aValue.IsEmpty())
val = &aValue;
return GetInlinePropertyBase(*aProperty, att, val, aFirst, aAny, aAll, nullptr);
}
NS_IMETHODIMP nsHTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom *aProperty,
const nsAString &aAttribute,
const nsAString &aValue,
bool *aFirst,
bool *aAny,
bool *aAll,
nsAString &outValue)
{
NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER);
const nsAString *att = nullptr;
if (!aAttribute.IsEmpty())
att = &aAttribute;
const nsAString *val = nullptr;
if (!aValue.IsEmpty())
val = &aValue;
return GetInlinePropertyBase(*aProperty, att, val, aFirst, aAny, aAll, &outValue);
}
NS_IMETHODIMP nsHTMLEditor::RemoveAllInlineProperties()
{
nsAutoEditBatch batchIt(this);
nsAutoRules beginRulesSniffing(this, EditAction::resetTextProperties, nsIEditor::eNext);
nsresult res = RemoveInlinePropertyImpl(nullptr, nullptr);
NS_ENSURE_SUCCESS(res, res);
return ApplyDefaultProperties();
}
NS_IMETHODIMP nsHTMLEditor::RemoveInlineProperty(nsIAtom *aProperty, const nsAString &aAttribute)
{
return RemoveInlinePropertyImpl(aProperty, &aAttribute);
}
nsresult
nsHTMLEditor::RemoveInlinePropertyImpl(nsIAtom* aProperty,
const nsAString* aAttribute)
{
MOZ_ASSERT_IF(aProperty, aAttribute);
NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
ForceCompositionEnd();
RefPtr<Selection> selection = GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
if (selection->Collapsed()) {
// Manipulating text attributes on a collapsed selection only sets state
// for the next text insertion
// For links, aProperty uses "href", use "a" instead
if (aProperty == nsGkAtoms::href || aProperty == nsGkAtoms::name) {
aProperty = nsGkAtoms::a;
}
if (aProperty) {
mTypeInState->ClearProp(aProperty, *aAttribute);
} else {
mTypeInState->ClearAllProps();
}
return NS_OK;
}
nsAutoEditBatch batchIt(this);
nsAutoRules beginRulesSniffing(this, EditAction::removeTextProperty,
nsIEditor::eNext);
nsAutoSelectionReset selectionResetter(selection, this);
nsAutoTxnsConserveSelection dontSpazMySelection(this);
bool cancel, handled;
nsTextRulesInfo ruleInfo(EditAction::removeTextProperty);
// Protect the edit rules object from dying
nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
NS_ENSURE_SUCCESS(res, res);
if (!cancel && !handled) {
// Loop through the ranges in the selection
uint32_t rangeCount = selection->RangeCount();
for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
if (aProperty == nsGkAtoms::name) {
// Promote range if it starts or end in a named anchor and we want to
// remove named anchors
res = PromoteRangeIfStartsOrEndsInNamedAnchor(range);
} else {
// Adjust range to include any ancestors whose children are entirely
// selected
res = PromoteInlineRange(range);
}
NS_ENSURE_SUCCESS(res, res);
// Remove this style from ancestors of our range endpoints, splitting
// them as appropriate
res = SplitStyleAboveRange(range, aProperty, aAttribute);
NS_ENSURE_SUCCESS(res, res);
// Check for easy case: both range endpoints in same text node
nsCOMPtr<nsINode> startNode = range->GetStartParent();
nsCOMPtr<nsINode> endNode = range->GetEndParent();
if (startNode && startNode == endNode && startNode->GetAsText()) {
// We're done with this range!
if (IsCSSEnabled() &&
mHTMLCSSUtils->IsCSSEditableProperty(startNode, aProperty,
aAttribute)) {
// The HTML style defined by aProperty/aAttribute has a CSS
// equivalence in this implementation for startNode
if (mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(startNode,
aProperty, aAttribute, EmptyString(),
nsHTMLCSSUtils::eComputed)) {
// startNode's computed style indicates the CSS equivalence to the
// HTML style to remove is applied; but we found no element in the
// ancestors of startNode carrying specified styles; assume it
// comes from a rule and try to insert a span "inverting" the style
if (mHTMLCSSUtils->IsCSSInvertible(*aProperty, aAttribute)) {
NS_NAMED_LITERAL_STRING(value, "-moz-editor-invert-value");
SetInlinePropertyOnTextNode(*startNode->GetAsText(),
range->StartOffset(),
range->EndOffset(), *aProperty,
aAttribute, value);
}
}
}
} else {
// Not the easy case. Range not contained in single text node.
nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
nsTArray<nsCOMPtr<nsINode>> arrayOfNodes;
// Iterate range and build up array
for (iter->Init(range); !iter->IsDone(); iter->Next()) {
nsCOMPtr<nsINode> node = iter->GetCurrentNode();
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
if (IsEditable(node)) {
arrayOfNodes.AppendElement(node);
}
}
// Loop through the list, remove the property on each node
for (auto& node : arrayOfNodes) {
res = RemoveStyleInside(GetAsDOMNode(node), aProperty, aAttribute);
NS_ENSURE_SUCCESS(res, res);
if (IsCSSEnabled() &&
mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty,
aAttribute) &&
mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(node,
aProperty, aAttribute, EmptyString(),
nsHTMLCSSUtils::eComputed) &&
// startNode's computed style indicates the CSS equivalence to
// the HTML style to remove is applied; but we found no element
// in the ancestors of startNode carrying specified styles;
// assume it comes from a rule and let's try to insert a span
// "inverting" the style
mHTMLCSSUtils->IsCSSInvertible(*aProperty, aAttribute)) {
NS_NAMED_LITERAL_STRING(value, "-moz-editor-invert-value");
SetInlinePropertyOnNode(*node->AsContent(), *aProperty,
aAttribute, value);
}
}
}
}
}
if (!cancel) {
// Post-process
res = mRules->DidDoAction(selection, &ruleInfo, res);
NS_ENSURE_SUCCESS(res, res);
}
return NS_OK;
}
NS_IMETHODIMP nsHTMLEditor::IncreaseFontSize()
{
return RelativeFontChange(FontSize::incr);
}
NS_IMETHODIMP nsHTMLEditor::DecreaseFontSize()
{
return RelativeFontChange(FontSize::decr);
}
nsresult
nsHTMLEditor::RelativeFontChange(FontSize aDir)
{
ForceCompositionEnd();
// Get the selection
RefPtr<Selection> selection = GetSelection();
NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
// If selection is collapsed, set typing state
if (selection->Collapsed()) {
nsIAtom& atom = aDir == FontSize::incr ? *nsGkAtoms::big :
*nsGkAtoms::small;
// Let's see in what kind of element the selection is
NS_ENSURE_TRUE(selection->RangeCount() &&
selection->GetRangeAt(0)->GetStartParent(), NS_OK);
OwningNonNull<nsINode> selectedNode =
*selection->GetRangeAt(0)->GetStartParent();
if (IsTextNode(selectedNode)) {
NS_ENSURE_TRUE(selectedNode->GetParentNode(), NS_OK);
selectedNode = *selectedNode->GetParentNode();
}
if (!CanContainTag(selectedNode, atom)) {
return NS_OK;
}
// Manipulating text attributes on a collapsed selection only sets state
// for the next text insertion
mTypeInState->SetProp(&atom, EmptyString(), EmptyString());
return NS_OK;
}
// Wrap with txn batching, rules sniffing, and selection preservation code
nsAutoEditBatch batchIt(this);
nsAutoRules beginRulesSniffing(this, EditAction::setTextProperty,
nsIEditor::eNext);
nsAutoSelectionReset selectionResetter(selection, this);
nsAutoTxnsConserveSelection dontSpazMySelection(this);
// Loop through the ranges in the selection
uint32_t rangeCount = selection->RangeCount();
for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
// Adjust range to include any ancestors with entirely selected children
nsresult res = PromoteInlineRange(range);
NS_ENSURE_SUCCESS(res, res);
// Check for easy case: both range endpoints in same text node
nsCOMPtr<nsINode> startNode = range->GetStartParent();
nsCOMPtr<nsINode> endNode = range->GetEndParent();
if (startNode == endNode && IsTextNode(startNode)) {
res = RelativeFontChangeOnTextNode(aDir == FontSize::incr ? +1 : -1,
static_cast<nsIDOMCharacterData*>(startNode->AsDOMNode()),
range->StartOffset(), range->EndOffset());
NS_ENSURE_SUCCESS(res, res);
} else {
// Not the easy case. Range not contained in single text node. There
// are up to three phases here. There are all the nodes reported by the
// subtree iterator to be processed. And there are potentially a
// starting textnode and an ending textnode which are only partially
// contained by the range.
// Let's handle the nodes reported by the iterator. These nodes are
// entirely contained in the selection range. We build up a list of them
// (since doing operations on the document during iteration would perturb
// the iterator).
OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
// Iterate range and build up array
res = iter->Init(range);
if (NS_SUCCEEDED(res)) {
nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
for (; !iter->IsDone(); iter->Next()) {
NS_ENSURE_TRUE(iter->GetCurrentNode()->IsContent(), NS_ERROR_FAILURE);
OwningNonNull<nsIContent> node = *iter->GetCurrentNode()->AsContent();
if (IsEditable(node)) {
arrayOfNodes.AppendElement(node);
}
}
// Now that we have the list, do the font size change on each node
for (auto& node : arrayOfNodes) {
res = RelativeFontChangeOnNode(aDir == FontSize::incr ? +1 : -1,
node);
NS_ENSURE_SUCCESS(res, res);
}
}
// Now check the start and end parents of the range to see if they need
// to be separately handled (they do if they are text nodes, due to how
// the subtree iterator works - it will not have reported them).
if (IsTextNode(startNode) && IsEditable(startNode)) {
res = RelativeFontChangeOnTextNode(aDir == FontSize::incr ? +1 : -1,
static_cast<nsIDOMCharacterData*>(startNode->AsDOMNode()),
range->StartOffset(), startNode->Length());
NS_ENSURE_SUCCESS(res, res);
}
if (IsTextNode(endNode) && IsEditable(endNode)) {
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(endNode);
int32_t endOffset;
range->GetEndOffset(&endOffset);
res = RelativeFontChangeOnTextNode(aDir == FontSize::incr ? +1 : -1,
static_cast<nsIDOMCharacterData*>(startNode->AsDOMNode()),
0, range->EndOffset());
NS_ENSURE_SUCCESS(res, res);
}
}
}
return NS_OK;
}
nsresult
nsHTMLEditor::RelativeFontChangeOnTextNode( int32_t aSizeChange,
nsIDOMCharacterData *aTextNode,
int32_t aStartOffset,
int32_t aEndOffset)
{
// Can only change font size by + or - 1
if ( !( (aSizeChange==1) || (aSizeChange==-1) ) )
return NS_ERROR_ILLEGAL_VALUE;
nsCOMPtr<nsIContent> textNode = do_QueryInterface(aTextNode);
NS_ENSURE_TRUE(textNode, NS_ERROR_NULL_POINTER);
// don't need to do anything if no characters actually selected
if (aStartOffset == aEndOffset) return NS_OK;
if (!textNode->GetParentNode() ||
!CanContainTag(*textNode->GetParentNode(), *nsGkAtoms::big)) {
return NS_OK;
}
nsCOMPtr<nsIDOMNode> tmp;
nsCOMPtr<nsIContent> node = do_QueryInterface(aTextNode);
NS_ENSURE_STATE(node);
// do we need to split the text node?
uint32_t textLen;
aTextNode->GetLength(&textLen);
// -1 is a magic value meaning to the end of node
if (aEndOffset == -1) aEndOffset = textLen;
nsresult res = NS_OK;
if ( (uint32_t)aEndOffset != textLen )
{
// we need to split off back of text node
res = SplitNode(GetAsDOMNode(node), aEndOffset, getter_AddRefs(tmp));
NS_ENSURE_SUCCESS(res, res);
// remember left node
node = do_QueryInterface(tmp);
}
if ( aStartOffset )
{
// we need to split off front of text node
res = SplitNode(GetAsDOMNode(node), aStartOffset, getter_AddRefs(tmp));
NS_ENSURE_SUCCESS(res, res);
}
// look for siblings that are correct type of node
nsIAtom* nodeType = aSizeChange == 1 ? nsGkAtoms::big : nsGkAtoms::small;
nsCOMPtr<nsIContent> sibling = GetPriorHTMLSibling(node);
if (sibling && sibling->IsHTMLElement(nodeType)) {
// previous sib is already right kind of inline node; slide this over into it
res = MoveNode(node, sibling, -1);
return res;
}
sibling = GetNextHTMLSibling(node);
if (sibling && sibling->IsHTMLElement(nodeType)) {
// following sib is already right kind of inline node; slide this over into it
res = MoveNode(node, sibling, 0);
return res;
}
// else reparent the node inside font node with appropriate relative size
nsCOMPtr<Element> newElement = InsertContainerAbove(node, nodeType);
NS_ENSURE_STATE(newElement);
return NS_OK;
}
nsresult
nsHTMLEditor::RelativeFontChangeHelper(int32_t aSizeChange, nsINode* aNode)
{
MOZ_ASSERT(aNode);
/* This routine looks for all the font nodes in the tree rooted by aNode,
including aNode itself, looking for font nodes that have the size attr
set. Any such nodes need to have big or small put inside them, since
they override any big/small that are above them.
*/
// Can only change font size by + or - 1
if (aSizeChange != 1 && aSizeChange != -1) {
return NS_ERROR_ILLEGAL_VALUE;
}
// If this is a font node with size, put big/small inside it.
if (aNode->IsHTMLElement(nsGkAtoms::font) &&
aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::size)) {
// Cycle through children and adjust relative font size.
for (uint32_t i = aNode->GetChildCount(); i--; ) {
nsresult rv = RelativeFontChangeOnNode(aSizeChange, aNode->GetChildAt(i));
NS_ENSURE_SUCCESS(rv, rv);
}
// RelativeFontChangeOnNode already calls us recursively,
// so we don't need to check our children again.
return NS_OK;
}
// Otherwise cycle through the children.
for (uint32_t i = aNode->GetChildCount(); i--; ) {
nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode->GetChildAt(i));
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
nsHTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange, nsIContent* aNode)
{
MOZ_ASSERT(aNode);
// Can only change font size by + or - 1
if (aSizeChange != 1 && aSizeChange != -1) {
return NS_ERROR_ILLEGAL_VALUE;
}
nsIAtom* atom;
if (aSizeChange == 1) {
atom = nsGkAtoms::big;
} else {
atom = nsGkAtoms::small;
}
// Is it the opposite of what we want?
if ((aSizeChange == 1 && aNode->IsHTMLElement(nsGkAtoms::small)) ||
(aSizeChange == -1 && aNode->IsHTMLElement(nsGkAtoms::big))) {
// first populate any nested font tags that have the size attr set
nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
NS_ENSURE_SUCCESS(rv, rv);
// in that case, just remove this node and pull up the children
return RemoveContainer(aNode);
}
// can it be put inside a "big" or "small"?
if (TagCanContain(*atom, *aNode)) {
// first populate any nested font tags that have the size attr set
nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
NS_ENSURE_SUCCESS(rv, rv);
// ok, chuck it in.
// first look at siblings of aNode for matching bigs or smalls.
// if we find one, move aNode into it.
nsIContent* sibling = GetPriorHTMLSibling(aNode);
if (sibling && sibling->IsHTMLElement(atom)) {
// previous sib is already right kind of inline node; slide this over into it
return MoveNode(aNode, sibling, -1);
}
sibling = GetNextHTMLSibling(aNode);
if (sibling && sibling->IsHTMLElement(atom)) {
// following sib is already right kind of inline node; slide this over into it
return MoveNode(aNode, sibling, 0);
}
// else insert it above aNode
nsCOMPtr<Element> newElement = InsertContainerAbove(aNode, atom);
NS_ENSURE_STATE(newElement);
return NS_OK;
}
// none of the above? then cycle through the children.
// MOOSE: we should group the children together if possible
// into a single "big" or "small". For the moment they are
// each getting their own.
for (uint32_t i = aNode->GetChildCount(); i--; ) {
nsresult rv = RelativeFontChangeOnNode(aSizeChange, aNode->GetChildAt(i));
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::GetFontFaceState(bool *aMixed, nsAString &outFace)
{
NS_ENSURE_TRUE(aMixed, NS_ERROR_FAILURE);
*aMixed = true;
outFace.Truncate();
nsresult res;
bool first, any, all;
NS_NAMED_LITERAL_STRING(attr, "face");
res = GetInlinePropertyBase(*nsGkAtoms::font, &attr, nullptr, &first, &any,
&all, &outFace);
NS_ENSURE_SUCCESS(res, res);
if (any && !all) return res; // mixed
if (all)
{
*aMixed = false;
return res;
}
// if there is no font face, check for tt
res = GetInlinePropertyBase(*nsGkAtoms::tt, nullptr, nullptr, &first, &any,
&all,nullptr);
NS_ENSURE_SUCCESS(res, res);
if (any && !all) return res; // mixed
if (all)
{
*aMixed = false;
outFace.AssignLiteral("tt");
}
if (!any)
{
// there was no font face attrs of any kind. We are in normal font.
outFace.Truncate();
*aMixed = false;
}
return res;
}
NS_IMETHODIMP
nsHTMLEditor::GetFontColorState(bool *aMixed, nsAString &aOutColor)
{
NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
*aMixed = true;
aOutColor.Truncate();
nsresult res;
NS_NAMED_LITERAL_STRING(colorStr, "color");
bool first, any, all;
res = GetInlinePropertyBase(*nsGkAtoms::font, &colorStr, nullptr, &first,
&any, &all, &aOutColor);
NS_ENSURE_SUCCESS(res, res);
if (any && !all) return res; // mixed
if (all)
{
*aMixed = false;
return res;
}
if (!any)
{
// there was no font color attrs of any kind..
aOutColor.Truncate();
*aMixed = false;
}
return res;
}
// the return value is true only if the instance of the HTML editor we created
// can handle CSS styles (for instance, Composer can, Messenger can't) and if
// the CSS preference is checked
nsresult
nsHTMLEditor::GetIsCSSEnabled(bool *aIsCSSEnabled)
{
*aIsCSSEnabled = IsCSSEnabled();
return NS_OK;
}
static bool
HasNonEmptyAttribute(dom::Element* aElement, nsIAtom* aName)
{
MOZ_ASSERT(aElement);
nsAutoString value;
return aElement->GetAttr(kNameSpaceID_None, aName, value) && !value.IsEmpty();
}
bool
nsHTMLEditor::HasStyleOrIdOrClass(dom::Element* aElement)
{
MOZ_ASSERT(aElement);
// remove the node if its style attribute is empty or absent,
// and if it does not have a class nor an id
return HasNonEmptyAttribute(aElement, nsGkAtoms::style) ||
HasNonEmptyAttribute(aElement, nsGkAtoms::_class) ||
HasNonEmptyAttribute(aElement, nsGkAtoms::id);
}
nsresult
nsHTMLEditor::RemoveElementIfNoStyleOrIdOrClass(nsIDOMNode* aElement)
{
nsCOMPtr<dom::Element> element = do_QueryInterface(aElement);
NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER);
// early way out if node is not the right kind of element
if ((!element->IsHTMLElement(nsGkAtoms::span) &&
!element->IsHTMLElement(nsGkAtoms::font)) ||
HasStyleOrIdOrClass(element)) {
return NS_OK;
}
return RemoveContainer(element);
}