1/* 2 * Copyright (C) 2009 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 33#include "RenderRuby.h" 34 35#include "RenderRubyRun.h" 36#include "RenderStyle.h" 37#include "StyleInheritedData.h" 38#include <wtf/RefPtr.h> 39 40namespace WebCore { 41 42//=== generic helper functions to avoid excessive code duplication === 43 44static inline bool isAnonymousRubyInlineBlock(const RenderObject* object) 45{ 46 ASSERT(!object 47 || !object->parent()->isRuby() 48 || object->isRubyRun() 49 || (object->isInline() && (object->isBeforeContent() || object->isAfterContent())) 50 || (object->isAnonymous() && object->isRenderBlock() && object->style()->display() == INLINE_BLOCK)); 51 52 return object 53 && object->parent()->isRuby() 54 && object->isRenderBlock() 55 && !object->isRubyRun(); 56} 57 58static inline bool isRubyBeforeBlock(const RenderObject* object) 59{ 60 return isAnonymousRubyInlineBlock(object) 61 && !object->previousSibling() 62 && object->firstChild() 63 && object->firstChild()->style()->styleType() == BEFORE; 64} 65 66static inline bool isRubyAfterBlock(const RenderObject* object) 67{ 68 return isAnonymousRubyInlineBlock(object) 69 && !object->nextSibling() 70 && object->firstChild() 71 && object->firstChild()->style()->styleType() == AFTER; 72} 73 74static inline RenderBlock* rubyBeforeBlock(const RenderObject* ruby) 75{ 76 RenderObject* child = ruby->firstChild(); 77 return isRubyBeforeBlock(child) ? toRenderBlock(child) : 0; 78} 79 80static inline RenderBlock* rubyAfterBlock(const RenderObject* ruby) 81{ 82 RenderObject* child = ruby->lastChild(); 83 return isRubyAfterBlock(child) ? toRenderBlock(child) : 0; 84} 85 86static RenderBlock* createAnonymousRubyInlineBlock(RenderObject* ruby) 87{ 88 RefPtr<RenderStyle> newStyle = RenderStyle::createAnonymousStyleWithDisplay(ruby->style(), INLINE_BLOCK); 89 RenderBlock* newBlock = RenderBlock::createAnonymous(ruby->document()); 90 newBlock->setStyle(newStyle.release()); 91 return newBlock; 92} 93 94static RenderRubyRun* lastRubyRun(const RenderObject* ruby) 95{ 96 RenderObject* child = ruby->lastChild(); 97 if (child && !child->isRubyRun()) 98 child = child->previousSibling(); 99 ASSERT(!child || child->isRubyRun() || child->isBeforeContent() || child == rubyBeforeBlock(ruby)); 100 return child && child->isRubyRun() ? toRenderRubyRun(child) : 0; 101} 102 103static inline RenderRubyRun* findRubyRunParent(RenderObject* child) 104{ 105 while (child && !child->isRubyRun()) 106 child = child->parent(); 107 return toRenderRubyRun(child); 108} 109 110//=== ruby as inline object === 111 112RenderRubyAsInline::RenderRubyAsInline(Element* element) 113 : RenderInline(element) 114{ 115} 116 117RenderRubyAsInline::~RenderRubyAsInline() 118{ 119} 120 121void RenderRubyAsInline::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 122{ 123 RenderInline::styleDidChange(diff, oldStyle); 124 propagateStyleToAnonymousChildren(); 125} 126 127void RenderRubyAsInline::addChild(RenderObject* child, RenderObject* beforeChild) 128{ 129 // Insert :before and :after content before/after the RenderRubyRun(s) 130 if (child->isBeforeContent()) { 131 if (child->isInline()) { 132 // Add generated inline content normally 133 RenderInline::addChild(child, firstChild()); 134 } else { 135 // Wrap non-inline content with an anonymous inline-block. 136 RenderBlock* beforeBlock = rubyBeforeBlock(this); 137 if (!beforeBlock) { 138 beforeBlock = createAnonymousRubyInlineBlock(this); 139 RenderInline::addChild(beforeBlock, firstChild()); 140 } 141 beforeBlock->addChild(child); 142 } 143 return; 144 } 145 if (child->isAfterContent()) { 146 if (child->isInline()) { 147 // Add generated inline content normally 148 RenderInline::addChild(child); 149 } else { 150 // Wrap non-inline content with an anonymous inline-block. 151 RenderBlock* afterBlock = rubyAfterBlock(this); 152 if (!afterBlock) { 153 afterBlock = createAnonymousRubyInlineBlock(this); 154 RenderInline::addChild(afterBlock); 155 } 156 afterBlock->addChild(child); 157 } 158 return; 159 } 160 161 // If the child is a ruby run, just add it normally. 162 if (child->isRubyRun()) { 163 RenderInline::addChild(child, beforeChild); 164 return; 165 } 166 167 if (beforeChild && !isAfterContent(beforeChild)) { 168 // insert child into run 169 ASSERT(!beforeChild->isRubyRun()); 170 RenderObject* run = beforeChild; 171 while (run && !run->isRubyRun()) 172 run = run->parent(); 173 if (run) { 174 run->addChild(child, beforeChild); 175 return; 176 } 177 ASSERT_NOT_REACHED(); // beforeChild should always have a run as parent! 178 // Emergency fallback: fall through and just append. 179 } 180 181 // If the new child would be appended, try to add the child to the previous run 182 // if possible, or create a new run otherwise. 183 // (The RenderRubyRun object will handle the details) 184 RenderRubyRun* lastRun = lastRubyRun(this); 185 if (!lastRun || lastRun->hasRubyText()) { 186 lastRun = RenderRubyRun::staticCreateRubyRun(this); 187 RenderInline::addChild(lastRun, beforeChild); 188 } 189 lastRun->addChild(child); 190} 191 192void RenderRubyAsInline::removeChild(RenderObject* child) 193{ 194 // If the child's parent is *this (must be a ruby run or generated content or anonymous block), 195 // just use the normal remove method. 196 if (child->parent() == this) { 197 ASSERT(child->isRubyRun() || child->isBeforeContent() || child->isAfterContent() || isAnonymousRubyInlineBlock(child)); 198 RenderInline::removeChild(child); 199 return; 200 } 201 // If the child's parent is an anoymous block (must be generated :before/:after content) 202 // just use the block's remove method. 203 if (isAnonymousRubyInlineBlock(child->parent())) { 204 ASSERT(child->isBeforeContent() || child->isAfterContent()); 205 child->parent()->removeChild(child); 206 removeChild(child->parent()); 207 return; 208 } 209 210 // Otherwise find the containing run and remove it from there. 211 RenderRubyRun* run = findRubyRunParent(child); 212 ASSERT(run); 213 run->removeChild(child); 214} 215 216//=== ruby as block object === 217 218RenderRubyAsBlock::RenderRubyAsBlock(Element* element) 219 : RenderBlock(element) 220{ 221} 222 223RenderRubyAsBlock::~RenderRubyAsBlock() 224{ 225} 226 227void RenderRubyAsBlock::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 228{ 229 RenderBlock::styleDidChange(diff, oldStyle); 230 propagateStyleToAnonymousChildren(); 231} 232 233void RenderRubyAsBlock::addChild(RenderObject* child, RenderObject* beforeChild) 234{ 235 // Insert :before and :after content before/after the RenderRubyRun(s) 236 if (child->isBeforeContent()) { 237 if (child->isInline()) { 238 // Add generated inline content normally 239 RenderBlock::addChild(child, firstChild()); 240 } else { 241 // Wrap non-inline content with an anonymous inline-block. 242 RenderBlock* beforeBlock = rubyBeforeBlock(this); 243 if (!beforeBlock) { 244 beforeBlock = createAnonymousRubyInlineBlock(this); 245 RenderBlock::addChild(beforeBlock, firstChild()); 246 } 247 beforeBlock->addChild(child); 248 } 249 return; 250 } 251 if (child->isAfterContent()) { 252 if (child->isInline()) { 253 // Add generated inline content normally 254 RenderBlock::addChild(child); 255 } else { 256 // Wrap non-inline content with an anonymous inline-block. 257 RenderBlock* afterBlock = rubyAfterBlock(this); 258 if (!afterBlock) { 259 afterBlock = createAnonymousRubyInlineBlock(this); 260 RenderBlock::addChild(afterBlock); 261 } 262 afterBlock->addChild(child); 263 } 264 return; 265 } 266 267 // If the child is a ruby run, just add it normally. 268 if (child->isRubyRun()) { 269 RenderBlock::addChild(child, beforeChild); 270 return; 271 } 272 273 if (beforeChild && !isAfterContent(beforeChild)) { 274 // insert child into run 275 ASSERT(!beforeChild->isRubyRun()); 276 RenderObject* run = beforeChild; 277 while (run && !run->isRubyRun()) 278 run = run->parent(); 279 if (run) { 280 run->addChild(child, beforeChild); 281 return; 282 } 283 ASSERT_NOT_REACHED(); // beforeChild should always have a run as parent! 284 // Emergency fallback: fall through and just append. 285 } 286 287 // If the new child would be appended, try to add the child to the previous run 288 // if possible, or create a new run otherwise. 289 // (The RenderRubyRun object will handle the details) 290 RenderRubyRun* lastRun = lastRubyRun(this); 291 if (!lastRun || lastRun->hasRubyText()) { 292 lastRun = RenderRubyRun::staticCreateRubyRun(this); 293 RenderBlock::addChild(lastRun, beforeChild); 294 } 295 lastRun->addChild(child); 296} 297 298void RenderRubyAsBlock::removeChild(RenderObject* child) 299{ 300 // If the child's parent is *this (must be a ruby run or generated content or anonymous block), 301 // just use the normal remove method. 302 if (child->parent() == this) { 303 ASSERT(child->isRubyRun() || child->isBeforeContent() || child->isAfterContent() || isAnonymousRubyInlineBlock(child)); 304 RenderBlock::removeChild(child); 305 return; 306 } 307 // If the child's parent is an anoymous block (must be generated :before/:after content) 308 // just use the block's remove method. 309 if (isAnonymousRubyInlineBlock(child->parent())) { 310 ASSERT(child->isBeforeContent() || child->isAfterContent()); 311 child->parent()->removeChild(child); 312 removeChild(child->parent()); 313 return; 314 } 315 316 // Otherwise find the containing run and remove it from there. 317 RenderRubyRun* run = findRubyRunParent(child); 318 ASSERT(run); 319 run->removeChild(child); 320} 321 322} // namespace WebCore 323