1 /*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32 #include "core/dom/shadow/InsertionPoint.h"
33
34 #include "HTMLNames.h"
35 #include "core/dom/ElementTraversal.h"
36 #include "core/dom/QualifiedName.h"
37 #include "core/dom/StaticNodeList.h"
38 #include "core/dom/shadow/ElementShadow.h"
39 #include "core/html/shadow/HTMLContentElement.h"
40 #include "core/html/shadow/HTMLShadowElement.h"
41
42 namespace WebCore {
43
44 using namespace HTMLNames;
45
InsertionPoint(const QualifiedName & tagName,Document & document)46 InsertionPoint::InsertionPoint(const QualifiedName& tagName, Document& document)
47 : HTMLElement(tagName, document, CreateInsertionPoint)
48 , m_registeredWithShadowRoot(false)
49 {
50 setHasCustomStyleCallbacks();
51 }
52
~InsertionPoint()53 InsertionPoint::~InsertionPoint()
54 {
55 }
56
setDistribution(ContentDistribution & distribution)57 void InsertionPoint::setDistribution(ContentDistribution& distribution)
58 {
59 if (shouldUseFallbackElements()) {
60 for (Node* child = firstChild(); child; child = child->nextSibling())
61 child->lazyReattachIfAttached();
62 }
63
64 // Attempt not to reattach nodes that would be distributed to the exact same
65 // location by comparing the old and new distributions.
66
67 size_t i = 0;
68 size_t j = 0;
69
70 for ( ; i < m_distribution.size() && j < distribution.size(); ++i, ++j) {
71 if (m_distribution.size() < distribution.size()) {
72 // If the new distribution is larger than the old one, reattach all nodes in
73 // the new distribution that were inserted.
74 for ( ; j < distribution.size() && m_distribution.at(i) != distribution.at(j); ++j)
75 distribution.at(j)->lazyReattachIfAttached();
76 } else if (m_distribution.size() > distribution.size()) {
77 // If the old distribution is larger than the new one, reattach all nodes in
78 // the old distribution that were removed.
79 for ( ; i < m_distribution.size() && m_distribution.at(i) != distribution.at(j); ++i)
80 m_distribution.at(i)->lazyReattachIfAttached();
81 } else if (m_distribution.at(i) != distribution.at(j)) {
82 // If both distributions are the same length reattach both old and new.
83 m_distribution.at(i)->lazyReattachIfAttached();
84 distribution.at(j)->lazyReattachIfAttached();
85 }
86 }
87
88 // If we hit the end of either list above we need to reattach all remaining nodes.
89
90 for ( ; i < m_distribution.size(); ++i)
91 m_distribution.at(i)->lazyReattachIfAttached();
92
93 for ( ; j < distribution.size(); ++j)
94 distribution.at(j)->lazyReattachIfAttached();
95
96 m_distribution.swap(distribution);
97 m_distribution.shrinkToFit();
98 }
99
attach(const AttachContext & context)100 void InsertionPoint::attach(const AttachContext& context)
101 {
102 // We need to attach the distribution here so that they're inserted in the right order
103 // otherwise the n^2 protection inside NodeRenderingContext will cause them to be
104 // inserted in the wrong place later. This also lets distributed nodes benefit from
105 // the n^2 protection.
106 for (size_t i = 0; i < m_distribution.size(); ++i) {
107 if (m_distribution.at(i)->needsAttach())
108 m_distribution.at(i)->attach(context);
109 }
110
111 HTMLElement::attach(context);
112 }
113
detach(const AttachContext & context)114 void InsertionPoint::detach(const AttachContext& context)
115 {
116 for (size_t i = 0; i < m_distribution.size(); ++i)
117 m_distribution.at(i)->lazyReattachIfAttached();
118
119 HTMLElement::detach(context);
120 }
121
willRecalcStyle(StyleRecalcChange change)122 void InsertionPoint::willRecalcStyle(StyleRecalcChange change)
123 {
124 if (change < Inherit)
125 return;
126 for (size_t i = 0; i < m_distribution.size(); ++i)
127 m_distribution.at(i)->setNeedsStyleRecalc(LocalStyleChange);
128 }
129
shouldUseFallbackElements() const130 bool InsertionPoint::shouldUseFallbackElements() const
131 {
132 return isActive() && !hasDistribution();
133 }
134
canBeActive() const135 bool InsertionPoint::canBeActive() const
136 {
137 if (!isInShadowTree())
138 return false;
139 bool foundShadowElementInAncestors = false;
140 bool thisIsContentHTMLElement = isHTMLContentElement(this);
141 for (Node* node = parentNode(); node; node = node->parentNode()) {
142 if (node->isInsertionPoint()) {
143 // For HTMLContentElement, at most one HTMLShadowElement may appear in its ancestors.
144 if (thisIsContentHTMLElement && isHTMLShadowElement(node) && !foundShadowElementInAncestors)
145 foundShadowElementInAncestors = true;
146 else
147 return false;
148 }
149 }
150 return true;
151 }
152
isActive() const153 bool InsertionPoint::isActive() const
154 {
155 if (!canBeActive())
156 return false;
157 ShadowRoot* shadowRoot = containingShadowRoot();
158 ASSERT(shadowRoot);
159 if (!isHTMLShadowElement(this) || shadowRoot->descendantShadowElementCount() <= 1)
160 return true;
161
162 // Slow path only when there are more than one shadow elements in a shadow tree. That should be a rare case.
163 const Vector<RefPtr<InsertionPoint> >& insertionPoints = shadowRoot->descendantInsertionPoints();
164 for (size_t i = 0; i < insertionPoints.size(); ++i) {
165 InsertionPoint* point = insertionPoints[i].get();
166 if (isHTMLShadowElement(point))
167 return point == this;
168 }
169 return true;
170 }
171
isShadowInsertionPoint() const172 bool InsertionPoint::isShadowInsertionPoint() const
173 {
174 return isHTMLShadowElement(this) && isActive();
175 }
176
isContentInsertionPoint() const177 bool InsertionPoint::isContentInsertionPoint() const
178 {
179 return isHTMLContentElement(this) && isActive();
180 }
181
getDistributedNodes()182 PassRefPtr<NodeList> InsertionPoint::getDistributedNodes()
183 {
184 document().updateDistributionForNodeIfNeeded(this);
185
186 Vector<RefPtr<Node> > nodes;
187 nodes.reserveInitialCapacity(m_distribution.size());
188 for (size_t i = 0; i < m_distribution.size(); ++i)
189 nodes.uncheckedAppend(m_distribution.at(i));
190
191 return StaticNodeList::adopt(nodes);
192 }
193
rendererIsNeeded(const RenderStyle & style)194 bool InsertionPoint::rendererIsNeeded(const RenderStyle& style)
195 {
196 return !isActive() && HTMLElement::rendererIsNeeded(style);
197 }
198
childrenChanged(bool changedByParser,Node * beforeChange,Node * afterChange,int childCountDelta)199 void InsertionPoint::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
200 {
201 HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
202 if (ShadowRoot* root = containingShadowRoot()) {
203 if (ElementShadow* rootOwner = root->owner())
204 rootOwner->setNeedsDistributionRecalc();
205 }
206 }
207
insertedInto(ContainerNode * insertionPoint)208 Node::InsertionNotificationRequest InsertionPoint::insertedInto(ContainerNode* insertionPoint)
209 {
210 HTMLElement::insertedInto(insertionPoint);
211 if (ShadowRoot* root = containingShadowRoot()) {
212 if (ElementShadow* rootOwner = root->owner()) {
213 rootOwner->setNeedsDistributionRecalc();
214 if (canBeActive() && !m_registeredWithShadowRoot && insertionPoint->treeScope().rootNode() == root) {
215 m_registeredWithShadowRoot = true;
216 root->didAddInsertionPoint(this);
217 rootOwner->didAffectApplyAuthorStyles();
218 if (canAffectSelector())
219 rootOwner->willAffectSelector();
220 }
221 }
222 }
223
224 return InsertionDone;
225 }
226
removedFrom(ContainerNode * insertionPoint)227 void InsertionPoint::removedFrom(ContainerNode* insertionPoint)
228 {
229 ShadowRoot* root = containingShadowRoot();
230 if (!root)
231 root = insertionPoint->containingShadowRoot();
232
233 if (root) {
234 if (ElementShadow* rootOwner = root->owner())
235 rootOwner->setNeedsDistributionRecalc();
236 }
237
238 // host can be null when removedFrom() is called from ElementShadow destructor.
239 ElementShadow* rootOwner = root ? root->owner() : 0;
240
241 // Since this insertion point is no longer visible from the shadow subtree, it need to clean itself up.
242 clearDistribution();
243
244 if (m_registeredWithShadowRoot && insertionPoint->treeScope().rootNode() == root) {
245 ASSERT(root);
246 m_registeredWithShadowRoot = false;
247 root->didRemoveInsertionPoint(this);
248 if (rootOwner) {
249 rootOwner->didAffectApplyAuthorStyles();
250 if (canAffectSelector())
251 rootOwner->willAffectSelector();
252 }
253 }
254
255 HTMLElement::removedFrom(insertionPoint);
256 }
257
parseAttribute(const QualifiedName & name,const AtomicString & value)258 void InsertionPoint::parseAttribute(const QualifiedName& name, const AtomicString& value)
259 {
260 if (name == reset_style_inheritanceAttr) {
261 if (!inDocument() || !isActive())
262 return;
263 containingShadowRoot()->host()->setNeedsStyleRecalc();
264 } else
265 HTMLElement::parseAttribute(name, value);
266 }
267
resetStyleInheritance() const268 bool InsertionPoint::resetStyleInheritance() const
269 {
270 return fastHasAttribute(reset_style_inheritanceAttr);
271 }
272
setResetStyleInheritance(bool value)273 void InsertionPoint::setResetStyleInheritance(bool value)
274 {
275 setBooleanAttribute(reset_style_inheritanceAttr, value);
276 }
277
resolveReprojection(const Node * projectedNode)278 const InsertionPoint* resolveReprojection(const Node* projectedNode)
279 {
280 ASSERT(projectedNode);
281 const InsertionPoint* insertionPoint = 0;
282 const Node* current = projectedNode;
283 ElementShadow* lastElementShadow = 0;
284 while (true) {
285 ElementShadow* shadow = shadowWhereNodeCanBeDistributed(*current);
286 if (!shadow || shadow == lastElementShadow)
287 break;
288 lastElementShadow = shadow;
289 const InsertionPoint* insertedTo = shadow->finalDestinationInsertionPointFor(projectedNode);
290 if (!insertedTo)
291 break;
292 ASSERT(current != insertedTo);
293 current = insertedTo;
294 insertionPoint = insertedTo;
295 }
296 return insertionPoint;
297 }
298
collectDestinationInsertionPoints(const Node & node,Vector<InsertionPoint *,8> & results)299 void collectDestinationInsertionPoints(const Node& node, Vector<InsertionPoint*, 8>& results)
300 {
301 const Node* current = &node;
302 ElementShadow* lastElementShadow = 0;
303 while (true) {
304 ElementShadow* shadow = shadowWhereNodeCanBeDistributed(*current);
305 if (!shadow || shadow == lastElementShadow)
306 return;
307 lastElementShadow = shadow;
308 const DestinationInsertionPoints* insertionPoints = shadow->destinationInsertionPointsFor(&node);
309 if (!insertionPoints)
310 return;
311 for (size_t i = 0; i < insertionPoints->size(); ++i)
312 results.append(insertionPoints->at(i).get());
313 ASSERT(current != insertionPoints->last().get());
314 current = insertionPoints->last().get();
315 }
316 }
317
318 } // namespace WebCore
319