/src/mozilla-central/dom/html/HTMLTableElement.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozilla/dom/HTMLTableElement.h" |
8 | | #include "mozilla/MappedDeclarations.h" |
9 | | #include "nsAttrValueInlines.h" |
10 | | #include "nsHTMLStyleSheet.h" |
11 | | #include "nsMappedAttributes.h" |
12 | | #include "mozilla/dom/HTMLCollectionBinding.h" |
13 | | #include "mozilla/dom/HTMLTableElementBinding.h" |
14 | | #include "nsContentUtils.h" |
15 | | #include "jsfriendapi.h" |
16 | | |
17 | | NS_IMPL_NS_NEW_HTML_ELEMENT(Table) |
18 | | |
19 | | namespace mozilla { |
20 | | namespace dom { |
21 | | |
22 | | /* ------------------------------ TableRowsCollection -------------------------------- */ |
23 | | /** |
24 | | * This class provides a late-bound collection of rows in a table. |
25 | | * mParent is NOT ref-counted to avoid circular references |
26 | | */ |
27 | | class TableRowsCollection final : public nsIHTMLCollection |
28 | | , public nsStubMutationObserver |
29 | | , public nsWrapperCache |
30 | | { |
31 | | public: |
32 | | explicit TableRowsCollection(HTMLTableElement* aParent); |
33 | | |
34 | | NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
35 | | |
36 | | NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED |
37 | | NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED |
38 | | NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED |
39 | | NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED |
40 | | |
41 | | virtual uint32_t Length() override; |
42 | | virtual Element* GetElementAt(uint32_t aIndex) override; |
43 | | virtual nsINode* GetParentObject() override |
44 | 0 | { |
45 | 0 | return mParent; |
46 | 0 | } |
47 | | |
48 | | virtual Element* |
49 | | GetFirstNamedElement(const nsAString& aName, bool& aFound) override; |
50 | | virtual void GetSupportedNames(nsTArray<nsString>& aNames) override; |
51 | | |
52 | | NS_IMETHOD ParentDestroyed(); |
53 | | |
54 | | NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(TableRowsCollection, nsIHTMLCollection) |
55 | | |
56 | | // nsWrapperCache |
57 | | using nsWrapperCache::GetWrapperPreserveColor; |
58 | | using nsWrapperCache::PreserveWrapper; |
59 | | virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; |
60 | | protected: |
61 | | // Unregister ourselves as a mutation observer, and clear our internal state. |
62 | | void CleanUp(); |
63 | | void LastRelease() |
64 | 0 | { |
65 | 0 | CleanUp(); |
66 | 0 | } |
67 | | virtual ~TableRowsCollection() |
68 | 0 | { |
69 | 0 | // we do NOT have a ref-counted reference to mParent, so do NOT |
70 | 0 | // release it! this is to avoid circular references. The |
71 | 0 | // instantiator who provided mParent is responsible for managing our |
72 | 0 | // reference for us. |
73 | 0 | CleanUp(); |
74 | 0 | } |
75 | | |
76 | | virtual JSObject* GetWrapperPreserveColorInternal() override |
77 | 0 | { |
78 | 0 | return nsWrapperCache::GetWrapperPreserveColor(); |
79 | 0 | } |
80 | | virtual void PreserveWrapperInternal(nsISupports* aScriptObjectHolder) override |
81 | 0 | { |
82 | 0 | nsWrapperCache::PreserveWrapper(aScriptObjectHolder); |
83 | 0 | } |
84 | | |
85 | | // Ensure that HTMLTableElement is in a valid state. This must be called |
86 | | // before inspecting the mRows object. |
87 | | void EnsureInitialized(); |
88 | | |
89 | | // Checks if the passed-in container is interesting for the purposes of |
90 | | // invalidation due to a mutation observer. |
91 | | bool InterestingContainer(nsIContent* aContainer); |
92 | | |
93 | | // Check if the passed-in nsIContent is a <tr> within the section defined by |
94 | | // `aSection`. The root of the table is considered to be part of the `<tbody>` |
95 | | // section. |
96 | | bool IsAppropriateRow(nsAtom* aSection, nsIContent* aContent); |
97 | | |
98 | | // Scan backwards starting from `aCurrent` in the table, looking for the |
99 | | // previous row in the table which is within the section `aSection`. |
100 | | nsIContent* PreviousRow(nsAtom* aSection, nsIContent* aCurrent); |
101 | | |
102 | | // Handle the insertion of the child `aChild` into the container `aContainer` |
103 | | // within the tree. The container must be an `InterestingContainer`. This |
104 | | // method updates the mRows, mBodyStart, and mFootStart member variables. |
105 | | // |
106 | | // HandleInsert returns an integer which can be passed to the next call of the |
107 | | // method in a loop inserting children into the same container. This will |
108 | | // optimize subsequent insertions to require less work. This can either be -1, |
109 | | // in which case we don't know where to insert the next row, and When passed |
110 | | // to HandleInsert, it will use `PreviousRow` to locate the index to insert. |
111 | | // Or, it can be an index to insert the next <tr> in the same container at. |
112 | | int32_t HandleInsert(nsIContent* aContainer, |
113 | | nsIContent* aChild, |
114 | | int32_t aIndexGuess = -1); |
115 | | |
116 | | // The HTMLTableElement which this TableRowsCollection tracks the rows for. |
117 | | HTMLTableElement* mParent; |
118 | | |
119 | | // The current state of the TableRowsCollection. mBodyStart and mFootStart are |
120 | | // indices into mRows which represent the location of the first row in the |
121 | | // body or foot section. If there are no rows in a section, the index points |
122 | | // at the location where the first element in that section would be inserted. |
123 | | nsTArray<nsCOMPtr<nsIContent>> mRows; |
124 | | uint32_t mBodyStart; |
125 | | uint32_t mFootStart; |
126 | | bool mInitialized; |
127 | | }; |
128 | | |
129 | | |
130 | | TableRowsCollection::TableRowsCollection(HTMLTableElement *aParent) |
131 | | : mParent(aParent) |
132 | | , mBodyStart(0) |
133 | | , mFootStart(0) |
134 | | , mInitialized(false) |
135 | 0 | { |
136 | 0 | MOZ_ASSERT(mParent); |
137 | 0 | } |
138 | | |
139 | | void |
140 | | TableRowsCollection::EnsureInitialized() |
141 | 0 | { |
142 | 0 | if (mInitialized) { |
143 | 0 | return; |
144 | 0 | } |
145 | 0 | mInitialized = true; |
146 | 0 |
|
147 | 0 | // Initialize mRows as the TableRowsCollection is created. The mutation |
148 | 0 | // observer should keep it up to date. |
149 | 0 | // |
150 | 0 | // It should be extremely unlikely that anyone creates a TableRowsCollection |
151 | 0 | // without calling a method on it, so lazily performing this initialization |
152 | 0 | // seems unnecessary. |
153 | 0 | AutoTArray<nsCOMPtr<nsIContent>, 32> body; |
154 | 0 | AutoTArray<nsCOMPtr<nsIContent>, 32> foot; |
155 | 0 | mRows.Clear(); |
156 | 0 |
|
157 | 0 | auto addRowChildren = [&] (nsTArray<nsCOMPtr<nsIContent>>& aArray, nsIContent* aNode) { |
158 | 0 | for (nsIContent* inner = aNode->nsINode::GetFirstChild(); |
159 | 0 | inner; inner = inner->GetNextSibling()) { |
160 | 0 | if (inner->IsHTMLElement(nsGkAtoms::tr)) { |
161 | 0 | aArray.AppendElement(inner); |
162 | 0 | } |
163 | 0 | } |
164 | 0 | }; |
165 | 0 |
|
166 | 0 | for (nsIContent* node = mParent->nsINode::GetFirstChild(); |
167 | 0 | node; node = node->GetNextSibling()) { |
168 | 0 | if (node->IsHTMLElement(nsGkAtoms::thead)) { |
169 | 0 | addRowChildren(mRows, node); |
170 | 0 | } else if (node->IsHTMLElement(nsGkAtoms::tbody)) { |
171 | 0 | addRowChildren(body, node); |
172 | 0 | } else if (node->IsHTMLElement(nsGkAtoms::tfoot)) { |
173 | 0 | addRowChildren(foot, node); |
174 | 0 | } else if (node->IsHTMLElement(nsGkAtoms::tr)) { |
175 | 0 | body.AppendElement(node); |
176 | 0 | } |
177 | 0 | } |
178 | 0 |
|
179 | 0 | mBodyStart = mRows.Length(); |
180 | 0 | mRows.AppendElements(std::move(body)); |
181 | 0 | mFootStart = mRows.Length(); |
182 | 0 | mRows.AppendElements(std::move(foot)); |
183 | 0 |
|
184 | 0 | mParent->AddMutationObserver(this); |
185 | 0 | } |
186 | | |
187 | | void |
188 | | TableRowsCollection::CleanUp() |
189 | 0 | { |
190 | 0 | // Unregister ourselves as a mutation observer. |
191 | 0 | if (mInitialized && mParent) { |
192 | 0 | mParent->RemoveMutationObserver(this); |
193 | 0 | } |
194 | 0 |
|
195 | 0 | // Clean up all of our internal state and make it empty in case someone looks |
196 | 0 | // at us. |
197 | 0 | mRows.Clear(); |
198 | 0 | mBodyStart = 0; |
199 | 0 | mFootStart = 0; |
200 | 0 |
|
201 | 0 | // We set mInitialized to true in case someone still has a reference to us, as |
202 | 0 | // we don't need to try to initialize first. |
203 | 0 | mInitialized = true; |
204 | 0 | mParent = nullptr; |
205 | 0 | } |
206 | | |
207 | | JSObject* |
208 | | TableRowsCollection::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
209 | 0 | { |
210 | 0 | return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto); |
211 | 0 | } |
212 | | |
213 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TableRowsCollection, mRows) |
214 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(TableRowsCollection) |
215 | | NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(TableRowsCollection, |
216 | | LastRelease()) |
217 | | |
218 | 0 | NS_INTERFACE_TABLE_HEAD(TableRowsCollection) |
219 | 0 | NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY |
220 | 0 | NS_INTERFACE_TABLE(TableRowsCollection, nsIHTMLCollection, nsIMutationObserver) |
221 | 0 | NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(TableRowsCollection) |
222 | 0 | NS_INTERFACE_MAP_END |
223 | | |
224 | | uint32_t |
225 | | TableRowsCollection::Length() |
226 | 0 | { |
227 | 0 | EnsureInitialized(); |
228 | 0 | return mRows.Length(); |
229 | 0 | } |
230 | | |
231 | | Element* |
232 | | TableRowsCollection::GetElementAt(uint32_t aIndex) |
233 | 0 | { |
234 | 0 | EnsureInitialized(); |
235 | 0 | if (aIndex < mRows.Length()) { |
236 | 0 | return mRows[aIndex]->AsElement(); |
237 | 0 | } |
238 | 0 | return nullptr; |
239 | 0 | } |
240 | | |
241 | | Element* |
242 | | TableRowsCollection::GetFirstNamedElement(const nsAString& aName, bool& aFound) |
243 | 0 | { |
244 | 0 | EnsureInitialized(); |
245 | 0 | aFound = false; |
246 | 0 | RefPtr<nsAtom> nameAtom = NS_Atomize(aName); |
247 | 0 | NS_ENSURE_TRUE(nameAtom, nullptr); |
248 | 0 |
|
249 | 0 | for (auto& node : mRows) { |
250 | 0 | if (node->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, |
251 | 0 | nameAtom, eCaseMatters) || |
252 | 0 | node->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, |
253 | 0 | nameAtom, eCaseMatters)) { |
254 | 0 | aFound = true; |
255 | 0 | return node->AsElement(); |
256 | 0 | } |
257 | 0 | } |
258 | 0 |
|
259 | 0 | return nullptr; |
260 | 0 | } |
261 | | |
262 | | void |
263 | | TableRowsCollection::GetSupportedNames(nsTArray<nsString>& aNames) |
264 | 0 | { |
265 | 0 | EnsureInitialized(); |
266 | 0 | for (auto& node : mRows) { |
267 | 0 | if (node->HasID()) { |
268 | 0 | nsAtom* idAtom = node->GetID(); |
269 | 0 | MOZ_ASSERT(idAtom != nsGkAtoms::_empty, |
270 | 0 | "Empty ids don't get atomized"); |
271 | 0 | nsDependentAtomString idStr(idAtom); |
272 | 0 | if (!aNames.Contains(idStr)) { |
273 | 0 | aNames.AppendElement(idStr); |
274 | 0 | } |
275 | 0 | } |
276 | 0 |
|
277 | 0 | nsGenericHTMLElement* el = nsGenericHTMLElement::FromNode(node); |
278 | 0 | if (el) { |
279 | 0 | const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name); |
280 | 0 | if (val && val->Type() == nsAttrValue::eAtom) { |
281 | 0 | nsAtom* nameAtom = val->GetAtomValue(); |
282 | 0 | MOZ_ASSERT(nameAtom != nsGkAtoms::_empty, |
283 | 0 | "Empty names don't get atomized"); |
284 | 0 | nsDependentAtomString nameStr(nameAtom); |
285 | 0 | if (!aNames.Contains(nameStr)) { |
286 | 0 | aNames.AppendElement(nameStr); |
287 | 0 | } |
288 | 0 | } |
289 | 0 | } |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | | |
294 | | NS_IMETHODIMP |
295 | | TableRowsCollection::ParentDestroyed() |
296 | 0 | { |
297 | 0 | CleanUp(); |
298 | 0 | return NS_OK; |
299 | 0 | } |
300 | | |
301 | | bool |
302 | | TableRowsCollection::InterestingContainer(nsIContent* aContainer) |
303 | 0 | { |
304 | 0 | return mParent && aContainer && |
305 | 0 | (aContainer == mParent || |
306 | 0 | (aContainer->GetParent() == mParent && |
307 | 0 | aContainer->IsAnyOfHTMLElements(nsGkAtoms::thead, |
308 | 0 | nsGkAtoms::tbody, |
309 | 0 | nsGkAtoms::tfoot))); |
310 | 0 | } |
311 | | |
312 | | bool |
313 | | TableRowsCollection::IsAppropriateRow(nsAtom* aSection, nsIContent* aContent) |
314 | 0 | { |
315 | 0 | if (!aContent->IsHTMLElement(nsGkAtoms::tr)) { |
316 | 0 | return false; |
317 | 0 | } |
318 | 0 | // If it's in the root, then we consider it to be in a tbody. |
319 | 0 | nsIContent* parent = aContent->GetParent(); |
320 | 0 | if (aSection == nsGkAtoms::tbody && parent == mParent) { |
321 | 0 | return true; |
322 | 0 | } |
323 | 0 | return parent->IsHTMLElement(aSection); |
324 | 0 | } |
325 | | |
326 | | nsIContent* |
327 | | TableRowsCollection::PreviousRow(nsAtom* aSection, nsIContent* aCurrent) |
328 | 0 | { |
329 | 0 | // Keep going backwards until we've found a `tr` element. We want to always |
330 | 0 | // run at least once, as we don't want to find ourselves. |
331 | 0 | // |
332 | 0 | // Each spin of the loop we step backwards one element. If we're at the top of |
333 | 0 | // a section, we step out of it into the root, and if we step onto a section |
334 | 0 | // matching `aSection`, we step into it. We keep spinning the loop until |
335 | 0 | // either we reach the first element in mParent, or find a <tr> in an |
336 | 0 | // appropriate section. |
337 | 0 | nsIContent* prev = aCurrent; |
338 | 0 | do { |
339 | 0 | nsIContent* parent = prev->GetParent(); |
340 | 0 | prev = prev->GetPreviousSibling(); |
341 | 0 |
|
342 | 0 | // Ascend out of any sections we're currently in, if we've run out of |
343 | 0 | // elements. |
344 | 0 | if (!prev && parent != mParent) { |
345 | 0 | prev = parent->GetPreviousSibling(); |
346 | 0 | } |
347 | 0 |
|
348 | 0 | // Descend into a section if we stepped onto one. |
349 | 0 | if (prev && prev->GetParent() == mParent && prev->IsHTMLElement(aSection)) { |
350 | 0 | prev = prev->GetLastChild(); |
351 | 0 | } |
352 | 0 | } while (prev && !IsAppropriateRow(aSection, prev)); |
353 | 0 | return prev; |
354 | 0 | } |
355 | | |
356 | | int32_t |
357 | | TableRowsCollection::HandleInsert(nsIContent* aContainer, |
358 | | nsIContent* aChild, |
359 | | int32_t aIndexGuess) |
360 | 0 | { |
361 | 0 | if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild)) { |
362 | 0 | return aIndexGuess; // Nothing inserted, guess hasn't changed. |
363 | 0 | } |
364 | 0 | |
365 | 0 | // If we're adding a section to the root, add each of the rows in that section |
366 | 0 | // individually. |
367 | 0 | if (aContainer == mParent && |
368 | 0 | aChild->IsAnyOfHTMLElements(nsGkAtoms::thead, |
369 | 0 | nsGkAtoms::tbody, |
370 | 0 | nsGkAtoms::tfoot)) { |
371 | 0 | // If we're entering a tbody, we can persist the index guess we were passed, |
372 | 0 | // as the newly added items are in the same section as us, however, if we're |
373 | 0 | // entering thead or tfoot we will have to re-scan. |
374 | 0 | bool isTBody = aChild->IsHTMLElement(nsGkAtoms::tbody); |
375 | 0 | int32_t indexGuess = isTBody ? aIndexGuess : -1; |
376 | 0 |
|
377 | 0 | for (nsIContent* inner = aChild->GetFirstChild(); |
378 | 0 | inner; inner = inner->GetNextSibling()) { |
379 | 0 | indexGuess = HandleInsert(aChild, inner, indexGuess); |
380 | 0 | } |
381 | 0 |
|
382 | 0 | return isTBody ? indexGuess : -1; |
383 | 0 | } |
384 | 0 | if (!aChild->IsHTMLElement(nsGkAtoms::tr)) { |
385 | 0 | return aIndexGuess; // Nothing inserted, guess hasn't changed. |
386 | 0 | } |
387 | 0 | |
388 | 0 | // We should have only been passed an insertion from an interesting container, |
389 | 0 | // so we can get the container we're inserting to fairly easily. |
390 | 0 | nsAtom* section = aContainer == mParent |
391 | 0 | ? nsGkAtoms::tbody |
392 | 0 | : aContainer->NodeInfo()->NameAtom(); |
393 | 0 |
|
394 | 0 | // Determine the default index we would to insert after if we don't find any |
395 | 0 | // previous row, and offset our section boundaries based on the section we're |
396 | 0 | // planning to insert into. |
397 | 0 | size_t index = 0; |
398 | 0 | if (section == nsGkAtoms::thead) { |
399 | 0 | mBodyStart++; |
400 | 0 | mFootStart++; |
401 | 0 | } else if (section == nsGkAtoms::tbody) { |
402 | 0 | index = mBodyStart; |
403 | 0 | mFootStart++; |
404 | 0 | } else if (section == nsGkAtoms::tfoot) { |
405 | 0 | index = mFootStart; |
406 | 0 | } else { |
407 | 0 | MOZ_ASSERT(false, "section should be one of thead, tbody, or tfoot"); |
408 | 0 | } |
409 | 0 |
|
410 | 0 | // If we already have an index guess, we can skip scanning for the previous row. |
411 | 0 | if (aIndexGuess >= 0) { |
412 | 0 | index = aIndexGuess; |
413 | 0 | } else { |
414 | 0 | // Find the previous row in the section we're inserting into. If we find it, |
415 | 0 | // we can use it to override our insertion index. We don't need to modify |
416 | 0 | // mBodyStart or mFootStart anymore, as they have already been correctly |
417 | 0 | // updated based only on section. |
418 | 0 | nsIContent* insertAfter = PreviousRow(section, aChild); |
419 | 0 | if (insertAfter) { |
420 | 0 | // NOTE: We want to ensure that appending elements is quick, so we search |
421 | 0 | // from the end rather than from the beginning. |
422 | 0 | index = mRows.LastIndexOf(insertAfter) + 1; |
423 | 0 | MOZ_ASSERT(index != nsTArray<nsCOMPtr<nsIContent>>::NoIndex); |
424 | 0 | } |
425 | 0 | } |
426 | 0 |
|
427 | | #ifdef DEBUG |
428 | | // Assert that we're inserting into the correct section. |
429 | | if (section == nsGkAtoms::thead) { |
430 | | MOZ_ASSERT(index < mBodyStart); |
431 | | } else if (section == nsGkAtoms::tbody) { |
432 | | MOZ_ASSERT(index >= mBodyStart); |
433 | | MOZ_ASSERT(index < mFootStart); |
434 | | } else if (section == nsGkAtoms::tfoot) { |
435 | | MOZ_ASSERT(index >= mFootStart); |
436 | | MOZ_ASSERT(index <= mRows.Length()); |
437 | | } |
438 | | |
439 | | MOZ_ASSERT(mBodyStart <= mFootStart); |
440 | | MOZ_ASSERT(mFootStart <= mRows.Length() + 1); |
441 | | #endif |
442 | |
|
443 | 0 | mRows.InsertElementAt(index, aChild); |
444 | 0 | return index + 1; |
445 | 0 | } |
446 | | |
447 | | // nsIMutationObserver |
448 | | |
449 | | void |
450 | | TableRowsCollection::ContentAppended(nsIContent* aFirstNewContent) |
451 | 0 | { |
452 | 0 | nsIContent* container = aFirstNewContent->GetParent(); |
453 | 0 | if (!nsContentUtils::IsInSameAnonymousTree(mParent, aFirstNewContent) || |
454 | 0 | !InterestingContainer(container)) { |
455 | 0 | return; |
456 | 0 | } |
457 | 0 | |
458 | 0 | // We usually can't guess where we need to start inserting, unless we're |
459 | 0 | // appending into mParent, in which case we can provide the guess that we |
460 | 0 | // should insert at the end of the body, which can help us avoid potentially |
461 | 0 | // expensive work in the common case. |
462 | 0 | int32_t indexGuess = mParent == container ? mFootStart : -1; |
463 | 0 |
|
464 | 0 | // Insert each of the newly added content one at a time. The indexGuess should |
465 | 0 | // make insertions of a large number of elements cheaper. |
466 | 0 | for (nsIContent* content = aFirstNewContent; |
467 | 0 | content; content = content->GetNextSibling()) { |
468 | 0 | indexGuess = HandleInsert(container, content, indexGuess); |
469 | 0 | } |
470 | 0 | } |
471 | | |
472 | | void |
473 | | TableRowsCollection::ContentInserted(nsIContent* aChild) |
474 | 0 | { |
475 | 0 | if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild) || |
476 | 0 | !InterestingContainer(aChild->GetParent())) { |
477 | 0 | return; |
478 | 0 | } |
479 | 0 | |
480 | 0 | HandleInsert(aChild->GetParent(), aChild); |
481 | 0 | } |
482 | | |
483 | | void |
484 | | TableRowsCollection::ContentRemoved(nsIContent* aChild, |
485 | | nsIContent* aPreviousSibling) |
486 | 0 | { |
487 | 0 | if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild) || |
488 | 0 | !InterestingContainer(aChild->GetParent())) { |
489 | 0 | return; |
490 | 0 | } |
491 | 0 | |
492 | 0 | // If the element being removed is a `tr`, we can just remove it from our |
493 | 0 | // list. It shouldn't change the order of anything. |
494 | 0 | if (aChild->IsHTMLElement(nsGkAtoms::tr)) { |
495 | 0 | size_t index = mRows.IndexOf(aChild); |
496 | 0 | if (index != nsTArray<nsCOMPtr<nsIContent>>::NoIndex) { |
497 | 0 | mRows.RemoveElementAt(index); |
498 | 0 | if (mBodyStart > index) { |
499 | 0 | mBodyStart--; |
500 | 0 | } |
501 | 0 | if (mFootStart > index) { |
502 | 0 | mFootStart--; |
503 | 0 | } |
504 | 0 | } |
505 | 0 | return; |
506 | 0 | } |
507 | 0 |
|
508 | 0 | // If the element being removed is a `thead`, `tbody`, or `tfoot`, we can |
509 | 0 | // remove any `tr`s in our list which have that element as its parent node. In |
510 | 0 | // any other situation, the removal won't affect us, so we can ignore it. |
511 | 0 | if (!aChild->IsAnyOfHTMLElements(nsGkAtoms::thead, nsGkAtoms::tbody, nsGkAtoms::tfoot)) { |
512 | 0 | return; |
513 | 0 | } |
514 | 0 | |
515 | 0 | size_t beforeLength = mRows.Length(); |
516 | 0 | mRows.RemoveElementsBy([&] (nsIContent* element) { |
517 | 0 | return element->GetParent() == aChild; |
518 | 0 | }); |
519 | 0 | size_t removed = beforeLength - mRows.Length(); |
520 | 0 | if (aChild->IsHTMLElement(nsGkAtoms::thead)) { |
521 | 0 | // NOTE: Need to move both tbody and tfoot, as we removed from head. |
522 | 0 | mBodyStart -= removed; |
523 | 0 | mFootStart -= removed; |
524 | 0 | } else if (aChild->IsHTMLElement(nsGkAtoms::tbody)) { |
525 | 0 | // NOTE: Need to move tfoot, as we removed from body. |
526 | 0 | mFootStart -= removed; |
527 | 0 | } |
528 | 0 | } |
529 | | |
530 | | void |
531 | | TableRowsCollection::NodeWillBeDestroyed(const nsINode* aNode) |
532 | 0 | { |
533 | 0 | // Set mInitialized to false so CleanUp doesn't try to remove our mutation |
534 | 0 | // observer, as we're going away. CleanUp() will reset mInitialized to true as |
535 | 0 | // it returns. |
536 | 0 | mInitialized = false; |
537 | 0 | CleanUp(); |
538 | 0 | } |
539 | | |
540 | | /* --------------------------- HTMLTableElement ---------------------------- */ |
541 | | |
542 | | HTMLTableElement::HTMLTableElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) |
543 | | : nsGenericHTMLElement(std::move(aNodeInfo)), |
544 | | mTableInheritedAttributes(nullptr) |
545 | 0 | { |
546 | 0 | SetHasWeirdParserInsertionMode(); |
547 | 0 | } |
548 | | |
549 | | HTMLTableElement::~HTMLTableElement() |
550 | 0 | { |
551 | 0 | if (mRows) { |
552 | 0 | mRows->ParentDestroyed(); |
553 | 0 | } |
554 | 0 | ReleaseInheritedAttributes(); |
555 | 0 | } |
556 | | |
557 | | JSObject* |
558 | | HTMLTableElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) |
559 | 0 | { |
560 | 0 | return HTMLTableElement_Binding::Wrap(aCx, this, aGivenProto); |
561 | 0 | } |
562 | | |
563 | | NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTableElement) |
564 | | |
565 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLTableElement, nsGenericHTMLElement) |
566 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mTBodies) |
567 | 0 | if (tmp->mRows) { |
568 | 0 | tmp->mRows->ParentDestroyed(); |
569 | 0 | } |
570 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mRows) |
571 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
572 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTableElement, |
573 | 0 | nsGenericHTMLElement) |
574 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTBodies) |
575 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRows) |
576 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
577 | | |
578 | | NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLTableElement, |
579 | | nsGenericHTMLElement) |
580 | | |
581 | | NS_IMPL_ELEMENT_CLONE(HTMLTableElement) |
582 | | |
583 | | |
584 | | // the DOM spec says border, cellpadding, cellSpacing are all "wstring" |
585 | | // in fact, they are integers or they are meaningless. so we store them |
586 | | // here as ints. |
587 | | |
588 | | nsIHTMLCollection* |
589 | | HTMLTableElement::Rows() |
590 | 0 | { |
591 | 0 | if (!mRows) { |
592 | 0 | mRows = new TableRowsCollection(this); |
593 | 0 | } |
594 | 0 |
|
595 | 0 | return mRows; |
596 | 0 | } |
597 | | |
598 | | nsIHTMLCollection* |
599 | | HTMLTableElement::TBodies() |
600 | 0 | { |
601 | 0 | if (!mTBodies) { |
602 | 0 | // Not using NS_GetContentList because this should not be cached |
603 | 0 | mTBodies = new nsContentList(this, |
604 | 0 | kNameSpaceID_XHTML, |
605 | 0 | nsGkAtoms::tbody, |
606 | 0 | nsGkAtoms::tbody, |
607 | 0 | false); |
608 | 0 | } |
609 | 0 |
|
610 | 0 | return mTBodies; |
611 | 0 | } |
612 | | |
613 | | already_AddRefed<nsGenericHTMLElement> |
614 | | HTMLTableElement::CreateTHead() |
615 | 0 | { |
616 | 0 | RefPtr<nsGenericHTMLElement> head = GetTHead(); |
617 | 0 | if (!head) { |
618 | 0 | // Create a new head rowgroup. |
619 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo; |
620 | 0 | nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::thead, |
621 | 0 | getter_AddRefs(nodeInfo)); |
622 | 0 |
|
623 | 0 | head = NS_NewHTMLTableSectionElement(nodeInfo.forget()); |
624 | 0 | if (!head) { |
625 | 0 | return nullptr; |
626 | 0 | } |
627 | 0 | |
628 | 0 | nsCOMPtr<nsIContent> refNode = nullptr; |
629 | 0 | for (refNode = nsINode::GetFirstChild(); |
630 | 0 | refNode; |
631 | 0 | refNode = refNode->GetNextSibling()) { |
632 | 0 |
|
633 | 0 | if (refNode->IsHTMLElement() && |
634 | 0 | !refNode->IsHTMLElement(nsGkAtoms::caption) && |
635 | 0 | !refNode->IsHTMLElement(nsGkAtoms::colgroup)) { |
636 | 0 | break; |
637 | 0 | } |
638 | 0 | } |
639 | 0 |
|
640 | 0 | nsINode::InsertBefore(*head, refNode, IgnoreErrors()); |
641 | 0 | } |
642 | 0 | return head.forget(); |
643 | 0 | } |
644 | | |
645 | | void |
646 | | HTMLTableElement::DeleteTHead() |
647 | 0 | { |
648 | 0 | HTMLTableSectionElement* tHead = GetTHead(); |
649 | 0 | if (tHead) { |
650 | 0 | mozilla::ErrorResult rv; |
651 | 0 | nsINode::RemoveChild(*tHead, rv); |
652 | 0 | MOZ_ASSERT(!rv.Failed()); |
653 | 0 | } |
654 | 0 | } |
655 | | |
656 | | already_AddRefed<nsGenericHTMLElement> |
657 | | HTMLTableElement::CreateTFoot() |
658 | 0 | { |
659 | 0 | RefPtr<nsGenericHTMLElement> foot = GetTFoot(); |
660 | 0 | if (!foot) { |
661 | 0 | // create a new foot rowgroup |
662 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo; |
663 | 0 | nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tfoot, |
664 | 0 | getter_AddRefs(nodeInfo)); |
665 | 0 |
|
666 | 0 | foot = NS_NewHTMLTableSectionElement(nodeInfo.forget()); |
667 | 0 | if (!foot) { |
668 | 0 | return nullptr; |
669 | 0 | } |
670 | 0 | AppendChildTo(foot, true); |
671 | 0 | } |
672 | 0 |
|
673 | 0 | return foot.forget(); |
674 | 0 | } |
675 | | |
676 | | void |
677 | | HTMLTableElement::DeleteTFoot() |
678 | 0 | { |
679 | 0 | HTMLTableSectionElement* tFoot = GetTFoot(); |
680 | 0 | if (tFoot) { |
681 | 0 | mozilla::ErrorResult rv; |
682 | 0 | nsINode::RemoveChild(*tFoot, rv); |
683 | 0 | MOZ_ASSERT(!rv.Failed()); |
684 | 0 | } |
685 | 0 | } |
686 | | |
687 | | already_AddRefed<nsGenericHTMLElement> |
688 | | HTMLTableElement::CreateCaption() |
689 | 0 | { |
690 | 0 | RefPtr<nsGenericHTMLElement> caption = GetCaption(); |
691 | 0 | if (!caption) { |
692 | 0 | // Create a new caption. |
693 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo; |
694 | 0 | nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::caption, |
695 | 0 | getter_AddRefs(nodeInfo)); |
696 | 0 |
|
697 | 0 | caption = NS_NewHTMLTableCaptionElement(nodeInfo.forget()); |
698 | 0 | if (!caption) { |
699 | 0 | return nullptr; |
700 | 0 | } |
701 | 0 | |
702 | 0 | nsCOMPtr<nsINode> firsChild = nsINode::GetFirstChild(); |
703 | 0 | nsINode::InsertBefore(*caption, firsChild, IgnoreErrors()); |
704 | 0 | } |
705 | 0 | return caption.forget(); |
706 | 0 | } |
707 | | |
708 | | void |
709 | | HTMLTableElement::DeleteCaption() |
710 | 0 | { |
711 | 0 | HTMLTableCaptionElement* caption = GetCaption(); |
712 | 0 | if (caption) { |
713 | 0 | mozilla::ErrorResult rv; |
714 | 0 | nsINode::RemoveChild(*caption, rv); |
715 | 0 | MOZ_ASSERT(!rv.Failed()); |
716 | 0 | } |
717 | 0 | } |
718 | | |
719 | | already_AddRefed<nsGenericHTMLElement> |
720 | | HTMLTableElement::CreateTBody() |
721 | 0 | { |
722 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo = |
723 | 0 | OwnerDoc()->NodeInfoManager()->GetNodeInfo(nsGkAtoms::tbody, nullptr, |
724 | 0 | kNameSpaceID_XHTML, |
725 | 0 | ELEMENT_NODE); |
726 | 0 | MOZ_ASSERT(nodeInfo); |
727 | 0 |
|
728 | 0 | RefPtr<nsGenericHTMLElement> newBody = |
729 | 0 | NS_NewHTMLTableSectionElement(nodeInfo.forget()); |
730 | 0 | MOZ_ASSERT(newBody); |
731 | 0 |
|
732 | 0 | nsCOMPtr<nsIContent> referenceNode = nullptr; |
733 | 0 | for (nsIContent* child = nsINode::GetLastChild(); |
734 | 0 | child; |
735 | 0 | child = child->GetPreviousSibling()) { |
736 | 0 | if (child->IsHTMLElement(nsGkAtoms::tbody)) { |
737 | 0 | referenceNode = child->GetNextSibling(); |
738 | 0 | break; |
739 | 0 | } |
740 | 0 | } |
741 | 0 |
|
742 | 0 | nsINode::InsertBefore(*newBody, referenceNode, IgnoreErrors()); |
743 | 0 |
|
744 | 0 | return newBody.forget(); |
745 | 0 | } |
746 | | |
747 | | already_AddRefed<nsGenericHTMLElement> |
748 | | HTMLTableElement::InsertRow(int32_t aIndex, ErrorResult& aError) |
749 | 0 | { |
750 | 0 | /* get the ref row at aIndex |
751 | 0 | if there is one, |
752 | 0 | get its parent |
753 | 0 | insert the new row just before the ref row |
754 | 0 | else |
755 | 0 | get the first row group |
756 | 0 | insert the new row as its first child |
757 | 0 | */ |
758 | 0 | if (aIndex < -1) { |
759 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
760 | 0 | return nullptr; |
761 | 0 | } |
762 | 0 | |
763 | 0 | nsIHTMLCollection* rows = Rows(); |
764 | 0 | uint32_t rowCount = rows->Length(); |
765 | 0 | if ((uint32_t)aIndex > rowCount && aIndex != -1) { |
766 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
767 | 0 | return nullptr; |
768 | 0 | } |
769 | 0 | |
770 | 0 | // use local variable refIndex so we can remember original aIndex |
771 | 0 | uint32_t refIndex = (uint32_t)aIndex; |
772 | 0 |
|
773 | 0 | RefPtr<nsGenericHTMLElement> newRow; |
774 | 0 | if (rowCount > 0) { |
775 | 0 | if (refIndex == rowCount || aIndex == -1) { |
776 | 0 | // we set refIndex to the last row so we can get the last row's |
777 | 0 | // parent we then do an AppendChild below if (rowCount<aIndex) |
778 | 0 |
|
779 | 0 | refIndex = rowCount - 1; |
780 | 0 | } |
781 | 0 |
|
782 | 0 | RefPtr<Element> refRow = rows->Item(refIndex); |
783 | 0 | nsCOMPtr<nsINode> parent = refRow->GetParentNode(); |
784 | 0 |
|
785 | 0 | // create the row |
786 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo; |
787 | 0 | nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tr, |
788 | 0 | getter_AddRefs(nodeInfo)); |
789 | 0 |
|
790 | 0 | newRow = NS_NewHTMLTableRowElement(nodeInfo.forget()); |
791 | 0 |
|
792 | 0 | if (newRow) { |
793 | 0 | // If aIndex is -1 or equal to the number of rows, the new row |
794 | 0 | // is appended. |
795 | 0 | if (aIndex == -1 || uint32_t(aIndex) == rowCount) { |
796 | 0 | parent->AppendChild(*newRow, aError); |
797 | 0 | } else { |
798 | 0 | // insert the new row before the reference row we found above |
799 | 0 | parent->InsertBefore(*newRow, refRow, aError); |
800 | 0 | } |
801 | 0 |
|
802 | 0 | if (aError.Failed()) { |
803 | 0 | return nullptr; |
804 | 0 | } |
805 | 0 | } |
806 | 0 | } else { |
807 | 0 | // the row count was 0, so |
808 | 0 | // find the last row group and insert there as first child |
809 | 0 | nsCOMPtr<nsIContent> rowGroup; |
810 | 0 | for (nsIContent* child = nsINode::GetLastChild(); |
811 | 0 | child; |
812 | 0 | child = child->GetPreviousSibling()) { |
813 | 0 | if (child->IsHTMLElement(nsGkAtoms::tbody)) { |
814 | 0 | rowGroup = child; |
815 | 0 | break; |
816 | 0 | } |
817 | 0 | } |
818 | 0 |
|
819 | 0 | if (!rowGroup) { // need to create a TBODY |
820 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo; |
821 | 0 | nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tbody, |
822 | 0 | getter_AddRefs(nodeInfo)); |
823 | 0 |
|
824 | 0 | rowGroup = NS_NewHTMLTableSectionElement(nodeInfo.forget()); |
825 | 0 | if (rowGroup) { |
826 | 0 | aError = AppendChildTo(rowGroup, true); |
827 | 0 | if (aError.Failed()) { |
828 | 0 | return nullptr; |
829 | 0 | } |
830 | 0 | } |
831 | 0 | } |
832 | 0 | |
833 | 0 | if (rowGroup) { |
834 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo; |
835 | 0 | nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tr, |
836 | 0 | getter_AddRefs(nodeInfo)); |
837 | 0 |
|
838 | 0 | newRow = NS_NewHTMLTableRowElement(nodeInfo.forget()); |
839 | 0 | if (newRow) { |
840 | 0 | HTMLTableSectionElement* section = |
841 | 0 | static_cast<HTMLTableSectionElement*>(rowGroup.get()); |
842 | 0 | nsIHTMLCollection* rows = section->Rows(); |
843 | 0 | nsCOMPtr<nsINode> refNode = rows->Item(0); |
844 | 0 | rowGroup->InsertBefore(*newRow, refNode, aError); |
845 | 0 | } |
846 | 0 | } |
847 | 0 | } |
848 | 0 |
|
849 | 0 | return newRow.forget(); |
850 | 0 | } |
851 | | |
852 | | void |
853 | | HTMLTableElement::DeleteRow(int32_t aIndex, ErrorResult& aError) |
854 | 0 | { |
855 | 0 | if (aIndex < -1) { |
856 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
857 | 0 | return; |
858 | 0 | } |
859 | 0 | |
860 | 0 | nsIHTMLCollection* rows = Rows(); |
861 | 0 | uint32_t refIndex; |
862 | 0 | if (aIndex == -1) { |
863 | 0 | refIndex = rows->Length(); |
864 | 0 | if (refIndex == 0) { |
865 | 0 | return; |
866 | 0 | } |
867 | 0 | |
868 | 0 | --refIndex; |
869 | 0 | } else { |
870 | 0 | refIndex = (uint32_t)aIndex; |
871 | 0 | } |
872 | 0 |
|
873 | 0 | nsCOMPtr<nsIContent> row = rows->Item(refIndex); |
874 | 0 | if (!row) { |
875 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
876 | 0 | return; |
877 | 0 | } |
878 | 0 | |
879 | 0 | row->RemoveFromParent(); |
880 | 0 | } |
881 | | |
882 | | bool |
883 | | HTMLTableElement::ParseAttribute(int32_t aNamespaceID, |
884 | | nsAtom* aAttribute, |
885 | | const nsAString& aValue, |
886 | | nsIPrincipal* aMaybeScriptedPrincipal, |
887 | | nsAttrValue& aResult) |
888 | 0 | { |
889 | 0 | /* ignore summary, just a string */ |
890 | 0 | if (aNamespaceID == kNameSpaceID_None) { |
891 | 0 | if (aAttribute == nsGkAtoms::cellspacing || |
892 | 0 | aAttribute == nsGkAtoms::cellpadding || |
893 | 0 | aAttribute == nsGkAtoms::border) { |
894 | 0 | return aResult.ParseNonNegativeIntValue(aValue); |
895 | 0 | } |
896 | 0 | if (aAttribute == nsGkAtoms::height) { |
897 | 0 | return aResult.ParseSpecialIntValue(aValue); |
898 | 0 | } |
899 | 0 | if (aAttribute == nsGkAtoms::width) { |
900 | 0 | if (aResult.ParseSpecialIntValue(aValue)) { |
901 | 0 | // treat 0 width as auto |
902 | 0 | nsAttrValue::ValueType type = aResult.Type(); |
903 | 0 | return !((type == nsAttrValue::eInteger && |
904 | 0 | aResult.GetIntegerValue() == 0) || |
905 | 0 | (type == nsAttrValue::ePercent && |
906 | 0 | aResult.GetPercentValue() == 0.0f)); |
907 | 0 | } |
908 | 0 | return false; |
909 | 0 | } |
910 | 0 | |
911 | 0 | if (aAttribute == nsGkAtoms::align) { |
912 | 0 | return ParseTableHAlignValue(aValue, aResult); |
913 | 0 | } |
914 | 0 | if (aAttribute == nsGkAtoms::bgcolor || |
915 | 0 | aAttribute == nsGkAtoms::bordercolor) { |
916 | 0 | return aResult.ParseColor(aValue); |
917 | 0 | } |
918 | 0 | if (aAttribute == nsGkAtoms::hspace || |
919 | 0 | aAttribute == nsGkAtoms::vspace) { |
920 | 0 | return aResult.ParseIntWithBounds(aValue, 0); |
921 | 0 | } |
922 | 0 | } |
923 | 0 | |
924 | 0 | return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID, |
925 | 0 | aAttribute, aValue, |
926 | 0 | aResult) || |
927 | 0 | nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, |
928 | 0 | aMaybeScriptedPrincipal, aResult); |
929 | 0 | } |
930 | | |
931 | | |
932 | | |
933 | | void |
934 | | HTMLTableElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes, |
935 | | MappedDeclarations& aDecls) |
936 | 0 | { |
937 | 0 | // XXX Bug 211636: This function is used by a single style rule |
938 | 0 | // that's used to match two different type of elements -- tables, and |
939 | 0 | // table cells. (nsHTMLTableCellElement overrides |
940 | 0 | // WalkContentStyleRules so that this happens.) This violates the |
941 | 0 | // nsIStyleRule contract, since it's the same style rule object doing |
942 | 0 | // the mapping in two different ways. It's also incorrect since it's |
943 | 0 | // testing the display type of the ComputedStyle rather than checking |
944 | 0 | // which *element* it's matching (style rules should not stop matching |
945 | 0 | // when the display type is changed). |
946 | 0 |
|
947 | 0 | nsCompatibility mode = aDecls.Document()->GetCompatibilityMode(); |
948 | 0 |
|
949 | 0 | // cellspacing |
950 | 0 | const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::cellspacing); |
951 | 0 | if (value && value->Type() == nsAttrValue::eInteger && |
952 | 0 | !aDecls.PropertyIsSet(eCSSProperty_border_spacing)) { |
953 | 0 | aDecls.SetPixelValue(eCSSProperty_border_spacing, float(value->GetIntegerValue())); |
954 | 0 | } |
955 | 0 | // align; Check for enumerated type (it may be another type if |
956 | 0 | // illegal) |
957 | 0 | value = aAttributes->GetAttr(nsGkAtoms::align); |
958 | 0 | if (value && value->Type() == nsAttrValue::eEnum) { |
959 | 0 | if (value->GetEnumValue() == NS_STYLE_TEXT_ALIGN_CENTER || |
960 | 0 | value->GetEnumValue() == NS_STYLE_TEXT_ALIGN_MOZ_CENTER) { |
961 | 0 | aDecls.SetAutoValueIfUnset(eCSSProperty_margin_left); |
962 | 0 | aDecls.SetAutoValueIfUnset(eCSSProperty_margin_right); |
963 | 0 | } |
964 | 0 | } |
965 | 0 |
|
966 | 0 | // hspace is mapped into left and right margin, |
967 | 0 | // vspace is mapped into top and bottom margins |
968 | 0 | // - *** Quirks Mode only *** |
969 | 0 | if (eCompatibility_NavQuirks == mode) { |
970 | 0 | value = aAttributes->GetAttr(nsGkAtoms::hspace); |
971 | 0 |
|
972 | 0 | if (value && value->Type() == nsAttrValue::eInteger) { |
973 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_margin_left, (float)value->GetIntegerValue()); |
974 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_margin_right, (float)value->GetIntegerValue()); |
975 | 0 | } |
976 | 0 |
|
977 | 0 | value = aAttributes->GetAttr(nsGkAtoms::vspace); |
978 | 0 |
|
979 | 0 | if (value && value->Type() == nsAttrValue::eInteger) { |
980 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_margin_top, (float)value->GetIntegerValue()); |
981 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_margin_bottom, (float)value->GetIntegerValue()); |
982 | 0 | } |
983 | 0 | } |
984 | 0 | // bordercolor |
985 | 0 | value = aAttributes->GetAttr(nsGkAtoms::bordercolor); |
986 | 0 | nscolor color; |
987 | 0 | if (value && value->GetColorValue(color)) { |
988 | 0 | aDecls.SetColorValueIfUnset(eCSSProperty_border_top_color, color); |
989 | 0 | aDecls.SetColorValueIfUnset(eCSSProperty_border_left_color, color); |
990 | 0 | aDecls.SetColorValueIfUnset(eCSSProperty_border_bottom_color, color); |
991 | 0 | aDecls.SetColorValueIfUnset(eCSSProperty_border_right_color, color); |
992 | 0 | } |
993 | 0 |
|
994 | 0 | // border |
995 | 0 | const nsAttrValue* borderValue = aAttributes->GetAttr(nsGkAtoms::border); |
996 | 0 | if (borderValue) { |
997 | 0 | // border = 1 pixel default |
998 | 0 | int32_t borderThickness = 1; |
999 | 0 |
|
1000 | 0 | if (borderValue->Type() == nsAttrValue::eInteger) |
1001 | 0 | borderThickness = borderValue->GetIntegerValue(); |
1002 | 0 |
|
1003 | 0 | // by default, set all border sides to the specified width |
1004 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_border_top_width, (float)borderThickness); |
1005 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_border_left_width, (float)borderThickness); |
1006 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_border_bottom_width, (float)borderThickness); |
1007 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_border_right_width, (float)borderThickness); |
1008 | 0 | } |
1009 | 0 |
|
1010 | 0 | nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aDecls); |
1011 | 0 | nsGenericHTMLElement::MapBackgroundAttributesInto(aAttributes, aDecls); |
1012 | 0 | nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aDecls); |
1013 | 0 | } |
1014 | | |
1015 | | NS_IMETHODIMP_(bool) |
1016 | | HTMLTableElement::IsAttributeMapped(const nsAtom* aAttribute) const |
1017 | 0 | { |
1018 | 0 | static const MappedAttributeEntry attributes[] = { |
1019 | 0 | { &nsGkAtoms::cellpadding }, |
1020 | 0 | { &nsGkAtoms::cellspacing }, |
1021 | 0 | { &nsGkAtoms::border }, |
1022 | 0 | { &nsGkAtoms::width }, |
1023 | 0 | { &nsGkAtoms::height }, |
1024 | 0 | { &nsGkAtoms::hspace }, |
1025 | 0 | { &nsGkAtoms::vspace }, |
1026 | 0 |
|
1027 | 0 | { &nsGkAtoms::bordercolor }, |
1028 | 0 |
|
1029 | 0 | { &nsGkAtoms::align }, |
1030 | 0 | { nullptr } |
1031 | 0 | }; |
1032 | 0 |
|
1033 | 0 | static const MappedAttributeEntry* const map[] = { |
1034 | 0 | attributes, |
1035 | 0 | sCommonAttributeMap, |
1036 | 0 | sBackgroundAttributeMap, |
1037 | 0 | }; |
1038 | 0 |
|
1039 | 0 | return FindAttributeDependence(aAttribute, map); |
1040 | 0 | } |
1041 | | |
1042 | | nsMapRuleToAttributesFunc |
1043 | | HTMLTableElement::GetAttributeMappingFunction() const |
1044 | 0 | { |
1045 | 0 | return &MapAttributesIntoRule; |
1046 | 0 | } |
1047 | | |
1048 | | static void |
1049 | | MapInheritedTableAttributesIntoRule(const nsMappedAttributes* aAttributes, |
1050 | | MappedDeclarations& aDecls) |
1051 | 0 | { |
1052 | 0 | const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::cellpadding); |
1053 | 0 | if (value && value->Type() == nsAttrValue::eInteger) { |
1054 | 0 | // We have cellpadding. This will override our padding values if we |
1055 | 0 | // don't have any set. |
1056 | 0 | float pad = float(value->GetIntegerValue()); |
1057 | 0 |
|
1058 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_padding_top, pad); |
1059 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_padding_right, pad); |
1060 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_padding_bottom, pad); |
1061 | 0 | aDecls.SetPixelValueIfUnset(eCSSProperty_padding_left, pad); |
1062 | 0 | } |
1063 | 0 | } |
1064 | | |
1065 | | nsMappedAttributes* |
1066 | | HTMLTableElement::GetAttributesMappedForCell() |
1067 | 0 | { |
1068 | 0 | return mTableInheritedAttributes; |
1069 | 0 | } |
1070 | | |
1071 | | void |
1072 | | HTMLTableElement::BuildInheritedAttributes() |
1073 | 0 | { |
1074 | 0 | NS_ASSERTION(!mTableInheritedAttributes, |
1075 | 0 | "potential leak, plus waste of work"); |
1076 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1077 | 0 | nsIDocument *document = GetComposedDoc(); |
1078 | 0 | nsHTMLStyleSheet* sheet = document ? |
1079 | 0 | document->GetAttributeStyleSheet() : nullptr; |
1080 | 0 | RefPtr<nsMappedAttributes> newAttrs; |
1081 | 0 | if (sheet) { |
1082 | 0 | const nsAttrValue* value = mAttrs.GetAttr(nsGkAtoms::cellpadding); |
1083 | 0 | if (value) { |
1084 | 0 | RefPtr<nsMappedAttributes> modifiableMapped = new |
1085 | 0 | nsMappedAttributes(sheet, MapInheritedTableAttributesIntoRule); |
1086 | 0 |
|
1087 | 0 | if (modifiableMapped) { |
1088 | 0 | nsAttrValue val(*value); |
1089 | 0 | bool oldValueSet; |
1090 | 0 | modifiableMapped->SetAndSwapAttr(nsGkAtoms::cellpadding, val, |
1091 | 0 | &oldValueSet); |
1092 | 0 | } |
1093 | 0 | newAttrs = sheet->UniqueMappedAttributes(modifiableMapped); |
1094 | 0 | NS_ASSERTION(newAttrs, "out of memory, but handling gracefully"); |
1095 | 0 |
|
1096 | 0 | if (newAttrs != modifiableMapped) { |
1097 | 0 | // Reset the stylesheet of modifiableMapped so that it doesn't |
1098 | 0 | // spend time trying to remove itself from the hash. There is no |
1099 | 0 | // risk that modifiableMapped is in the hash since we created |
1100 | 0 | // it ourselves and it didn't come from the stylesheet (in which |
1101 | 0 | // case it would not have been modifiable). |
1102 | 0 | modifiableMapped->DropStyleSheetReference(); |
1103 | 0 | } |
1104 | 0 | } |
1105 | 0 | mTableInheritedAttributes = newAttrs; |
1106 | 0 | NS_IF_ADDREF(mTableInheritedAttributes); |
1107 | 0 | } |
1108 | 0 | } |
1109 | | |
1110 | | void |
1111 | | HTMLTableElement::ReleaseInheritedAttributes() |
1112 | 0 | { |
1113 | 0 | NS_IF_RELEASE(mTableInheritedAttributes); |
1114 | 0 | } |
1115 | | |
1116 | | nsresult |
1117 | | HTMLTableElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, |
1118 | | nsIContent* aBindingParent) |
1119 | 0 | { |
1120 | 0 | ReleaseInheritedAttributes(); |
1121 | 0 | nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent, |
1122 | 0 | aBindingParent); |
1123 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1124 | 0 | BuildInheritedAttributes(); |
1125 | 0 | return NS_OK; |
1126 | 0 | } |
1127 | | |
1128 | | void |
1129 | | HTMLTableElement::UnbindFromTree(bool aDeep, bool aNullParent) |
1130 | 0 | { |
1131 | 0 | ReleaseInheritedAttributes(); |
1132 | 0 | nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); |
1133 | 0 | } |
1134 | | |
1135 | | nsresult |
1136 | | HTMLTableElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, |
1137 | | const nsAttrValueOrString* aValue, |
1138 | | bool aNotify) |
1139 | 0 | { |
1140 | 0 | if (aName == nsGkAtoms::cellpadding && aNameSpaceID == kNameSpaceID_None) { |
1141 | 0 | ReleaseInheritedAttributes(); |
1142 | 0 | } |
1143 | 0 | return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue, |
1144 | 0 | aNotify); |
1145 | 0 | } |
1146 | | |
1147 | | nsresult |
1148 | | HTMLTableElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, |
1149 | | const nsAttrValue* aValue, |
1150 | | const nsAttrValue* aOldValue, |
1151 | | nsIPrincipal* aSubjectPrincipal, |
1152 | | bool aNotify) |
1153 | 0 | { |
1154 | 0 | if (aName == nsGkAtoms::cellpadding && aNameSpaceID == kNameSpaceID_None) { |
1155 | 0 | BuildInheritedAttributes(); |
1156 | 0 | } |
1157 | 0 | return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, |
1158 | 0 | aOldValue, aSubjectPrincipal, aNotify); |
1159 | 0 | } |
1160 | | |
1161 | | } // namespace dom |
1162 | | } // namespace mozilla |