Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/editor/libeditor/ChangeStyleTransaction.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "mozilla/ChangeStyleTransaction.h"
7
8
#include "mozilla/dom/Element.h"        // for Element
9
#include "nsAString.h"                  // for nsAString::Append, etc.
10
#include "nsCRT.h"                      // for nsCRT::IsAsciiSpace
11
#include "nsDebug.h"                    // for NS_ENSURE_SUCCESS, etc.
12
#include "nsError.h"                    // for NS_ERROR_NULL_POINTER, etc.
13
#include "nsGkAtoms.h"                  // for nsGkAtoms, etc.
14
#include "nsICSSDeclaration.h"          // for nsICSSDeclaration.
15
#include "nsLiteralString.h"            // for NS_LITERAL_STRING, etc.
16
#include "nsReadableUtils.h"            // for ToNewUnicode
17
#include "nsString.h"                   // for nsAutoString, nsString, etc.
18
#include "nsStyledElement.h"            // for nsStyledElement.
19
#include "nsUnicharUtils.h"             // for nsCaseInsensitiveStringComparator
20
21
namespace mozilla {
22
23
using namespace dom;
24
25
// static
26
already_AddRefed<ChangeStyleTransaction>
27
ChangeStyleTransaction::Create(Element& aElement,
28
                               nsAtom& aProperty,
29
                               const nsAString& aValue)
30
0
{
31
0
  RefPtr<ChangeStyleTransaction> transaction =
32
0
    new ChangeStyleTransaction(aElement, aProperty, aValue, false);
33
0
  return transaction.forget();
34
0
}
35
36
// static
37
already_AddRefed<ChangeStyleTransaction>
38
ChangeStyleTransaction::CreateToRemove(Element& aElement,
39
                                       nsAtom& aProperty,
40
                                       const nsAString& aValue)
41
0
{
42
0
  RefPtr<ChangeStyleTransaction> transaction =
43
0
    new ChangeStyleTransaction(aElement, aProperty, aValue, true);
44
0
  return transaction.forget();
45
0
}
46
47
ChangeStyleTransaction::ChangeStyleTransaction(Element& aElement,
48
                                               nsAtom& aProperty,
49
                                               const nsAString& aValue,
50
                                               bool aRemove)
51
  : EditTransactionBase()
52
  , mElement(&aElement)
53
  , mProperty(&aProperty)
54
  , mValue(aValue)
55
  , mRemoveProperty(aRemove)
56
  , mUndoValue()
57
  , mRedoValue()
58
  , mUndoAttributeWasSet(false)
59
  , mRedoAttributeWasSet(false)
60
0
{
61
0
}
62
63
0
#define kNullCh (char16_t('\0'))
64
65
NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeStyleTransaction, EditTransactionBase,
66
                                   mElement)
67
68
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeStyleTransaction)
69
0
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
70
71
NS_IMPL_ADDREF_INHERITED(ChangeStyleTransaction, EditTransactionBase)
72
NS_IMPL_RELEASE_INHERITED(ChangeStyleTransaction, EditTransactionBase)
73
74
ChangeStyleTransaction::~ChangeStyleTransaction()
75
0
{
76
0
}
77
78
// Answers true if aValue is in the string list of white-space separated values
79
// aValueList.
80
bool
81
ChangeStyleTransaction::ValueIncludes(const nsAString& aValueList,
82
                                      const nsAString& aValue)
83
0
{
84
0
  nsAutoString valueList(aValueList);
85
0
  bool result = false;
86
0
87
0
  // put an extra null at the end
88
0
  valueList.Append(kNullCh);
89
0
90
0
  char16_t* value = ToNewUnicode(aValue);
91
0
  char16_t* start = valueList.BeginWriting();
92
0
  char16_t* end = start;
93
0
94
0
  while (kNullCh != *start) {
95
0
    while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) {
96
0
      // skip leading space
97
0
      start++;
98
0
    }
99
0
    end = start;
100
0
101
0
    while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) {
102
0
      // look for space or end
103
0
      end++;
104
0
    }
105
0
    // end string here
106
0
    *end = kNullCh;
107
0
108
0
    if (start < end) {
109
0
      if (nsDependentString(value).Equals(nsDependentString(start),
110
0
            nsCaseInsensitiveStringComparator())) {
111
0
        result = true;
112
0
        break;
113
0
      }
114
0
    }
115
0
    start = ++end;
116
0
  }
117
0
  free(value);
118
0
  return result;
119
0
}
120
121
// Removes the value aRemoveValue from the string list of white-space separated
122
// values aValueList
123
void
124
ChangeStyleTransaction::RemoveValueFromListOfValues(
125
                          nsAString& aValues,
126
                          const nsAString& aRemoveValue)
127
0
{
128
0
  nsAutoString classStr(aValues);
129
0
  nsAutoString outString;
130
0
  // put an extra null at the end
131
0
  classStr.Append(kNullCh);
132
0
133
0
  char16_t* start = classStr.BeginWriting();
134
0
  char16_t* end = start;
135
0
136
0
  while (kNullCh != *start) {
137
0
    while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) {
138
0
      // skip leading space
139
0
      start++;
140
0
    }
141
0
    end = start;
142
0
143
0
    while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) {
144
0
      // look for space or end
145
0
      end++;
146
0
    }
147
0
    // end string here
148
0
    *end = kNullCh;
149
0
150
0
    if (start < end && !aRemoveValue.Equals(start)) {
151
0
      outString.Append(start);
152
0
      outString.Append(char16_t(' '));
153
0
    }
154
0
155
0
    start = ++end;
156
0
  }
157
0
  aValues.Assign(outString);
158
0
}
159
160
NS_IMETHODIMP
161
ChangeStyleTransaction::DoTransaction()
162
0
{
163
0
  nsCOMPtr<nsStyledElement> inlineStyles = do_QueryInterface(mElement);
164
0
  NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER);
165
0
166
0
  nsCOMPtr<nsICSSDeclaration> cssDecl = inlineStyles->Style();
167
0
 
168
0
  nsAutoString propertyNameString;
169
0
  mProperty->ToString(propertyNameString);
170
0
171
0
  mUndoAttributeWasSet = mElement->HasAttr(kNameSpaceID_None,
172
0
                                           nsGkAtoms::style);
173
0
174
0
  nsAutoString values;
175
0
  nsresult rv = cssDecl->GetPropertyValue(propertyNameString, values);
176
0
  NS_ENSURE_SUCCESS(rv, rv);
177
0
  mUndoValue.Assign(values);
178
0
179
0
  // Does this property accept more than one value? (bug 62682)
180
0
  bool multiple = AcceptsMoreThanOneValue(*mProperty);
181
0
182
0
  if (mRemoveProperty) {
183
0
    nsAutoString returnString;
184
0
    if (multiple) {
185
0
      // Let's remove only the value we have to remove and not the others
186
0
      RemoveValueFromListOfValues(values, NS_LITERAL_STRING("none"));
187
0
      RemoveValueFromListOfValues(values, mValue);
188
0
      if (values.IsEmpty()) {
189
0
        rv = cssDecl->RemoveProperty(propertyNameString, returnString);
190
0
        NS_ENSURE_SUCCESS(rv, rv);
191
0
      } else {
192
0
        nsAutoString priority;
193
0
        cssDecl->GetPropertyPriority(propertyNameString, priority);
194
0
        rv = cssDecl->SetProperty(propertyNameString, values, priority);
195
0
        NS_ENSURE_SUCCESS(rv, rv);
196
0
      }
197
0
    } else {
198
0
      rv = cssDecl->RemoveProperty(propertyNameString, returnString);
199
0
      NS_ENSURE_SUCCESS(rv, rv);
200
0
    }
201
0
  } else {
202
0
    nsAutoString priority;
203
0
    cssDecl->GetPropertyPriority(propertyNameString, priority);
204
0
    if (multiple) {
205
0
      // Let's add the value we have to add to the others
206
0
      AddValueToMultivalueProperty(values, mValue);
207
0
    } else {
208
0
      values.Assign(mValue);
209
0
    }
210
0
    rv = cssDecl->SetProperty(propertyNameString, values, priority);
211
0
    NS_ENSURE_SUCCESS(rv, rv);
212
0
  }
213
0
214
0
  // Let's be sure we don't keep an empty style attribute
215
0
  uint32_t length = cssDecl->Length();
216
0
  if (!length) {
217
0
    rv = mElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true);
218
0
    NS_ENSURE_SUCCESS(rv, rv);
219
0
  } else {
220
0
    mRedoAttributeWasSet = true;
221
0
  }
222
0
223
0
  return cssDecl->GetPropertyValue(propertyNameString, mRedoValue);
224
0
}
225
226
nsresult
227
ChangeStyleTransaction::SetStyle(bool aAttributeWasSet,
228
                                 nsAString& aValue)
229
0
{
230
0
  if (aAttributeWasSet) {
231
0
    // The style attribute was not empty, let's recreate the declaration
232
0
    nsAutoString propertyNameString;
233
0
    mProperty->ToString(propertyNameString);
234
0
235
0
    nsCOMPtr<nsStyledElement> inlineStyles = do_QueryInterface(mElement);
236
0
    NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER);
237
0
    nsCOMPtr<nsICSSDeclaration> cssDecl = inlineStyles->Style();
238
0
239
0
    if (aValue.IsEmpty()) {
240
0
      // An empty value means we have to remove the property
241
0
      nsAutoString returnString;
242
0
      return cssDecl->RemoveProperty(propertyNameString, returnString);
243
0
    }
244
0
    // Let's recreate the declaration as it was
245
0
    nsAutoString priority;
246
0
    cssDecl->GetPropertyPriority(propertyNameString, priority);
247
0
    return cssDecl->SetProperty(propertyNameString, aValue, priority);
248
0
  }
249
0
  return mElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true);
250
0
}
251
252
NS_IMETHODIMP
253
ChangeStyleTransaction::UndoTransaction()
254
0
{
255
0
  return SetStyle(mUndoAttributeWasSet, mUndoValue);
256
0
}
257
258
NS_IMETHODIMP
259
ChangeStyleTransaction::RedoTransaction()
260
0
{
261
0
  return SetStyle(mRedoAttributeWasSet, mRedoValue);
262
0
}
263
264
// True if the CSS property accepts more than one value
265
bool
266
ChangeStyleTransaction::AcceptsMoreThanOneValue(nsAtom& aCSSProperty)
267
0
{
268
0
  return &aCSSProperty == nsGkAtoms::text_decoration;
269
0
}
270
271
// Adds the value aNewValue to the list of white-space separated values aValues
272
void
273
ChangeStyleTransaction::AddValueToMultivalueProperty(nsAString& aValues,
274
                                                     const nsAString& aNewValue)
275
0
{
276
0
  if (aValues.IsEmpty() || aValues.LowerCaseEqualsLiteral("none")) {
277
0
    aValues.Assign(aNewValue);
278
0
  } else if (!ValueIncludes(aValues, aNewValue)) {
279
0
    // We already have another value but not this one; add it
280
0
    aValues.Append(char16_t(' '));
281
0
    aValues.Append(aNewValue);
282
0
  }
283
0
}
284
285
} // namespace mozilla