hyatt@apple.com | 5388e67 | 2013-09-06 20:54:47 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| 3 | * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| 4 | * (C) 2007 David Smith (catfish.man@gmail.com) |
| 5 | * Copyright (C) 2003-2013 Apple Inc. All rights reserved. |
| 6 | * Copyright (C) Research In Motion Limited 2010. All rights reserved. |
| 7 | * |
| 8 | * This library is free software; you can redistribute it and/or |
| 9 | * modify it under the terms of the GNU Library General Public |
| 10 | * License as published by the Free Software Foundation; either |
| 11 | * version 2 of the License, or (at your option) any later version. |
| 12 | * |
| 13 | * This library is distributed in the hope that it will be useful, |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 16 | * Library General Public License for more details. |
| 17 | * |
| 18 | * You should have received a copy of the GNU Library General Public License |
| 19 | * along with this library; see the file COPYING.LIB. If not, write to |
| 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 21 | * Boston, MA 02110-1301, USA. |
| 22 | */ |
| 23 | |
| 24 | #include "config.h" |
| 25 | #include "RenderBlockFlow.h" |
| 26 | |
hyatt@apple.com | c9b1aef | 2013-09-09 20:23:21 +0000 | [diff] [blame^] | 27 | #include "LayoutRepainter.h" |
| 28 | #include "RenderFlowThread.h" |
| 29 | #include "RenderLayer.h" |
| 30 | #include "RenderView.h" |
| 31 | |
| 32 | using namespace std; |
| 33 | |
hyatt@apple.com | 5388e67 | 2013-09-06 20:54:47 +0000 | [diff] [blame] | 34 | namespace WebCore { |
| 35 | |
antti@apple.com | 8f33508 | 2013-09-09 19:54:33 +0000 | [diff] [blame] | 36 | RenderBlockFlow::RenderBlockFlow(Element* element) |
| 37 | : RenderBlock(element) |
hyatt@apple.com | 5388e67 | 2013-09-06 20:54:47 +0000 | [diff] [blame] | 38 | { |
| 39 | } |
| 40 | |
| 41 | RenderBlockFlow::~RenderBlockFlow() |
| 42 | { |
| 43 | } |
| 44 | |
hyatt@apple.com | c9b1aef | 2013-09-09 20:23:21 +0000 | [diff] [blame^] | 45 | void RenderBlockFlow::clearFloats() |
| 46 | { |
| 47 | if (m_floatingObjects) |
| 48 | m_floatingObjects->setHorizontalWritingMode(isHorizontalWritingMode()); |
| 49 | |
| 50 | HashSet<RenderBox*> oldIntrudingFloatSet; |
| 51 | if (!childrenInline() && m_floatingObjects) { |
| 52 | const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); |
| 53 | FloatingObjectSetIterator end = floatingObjectSet.end(); |
| 54 | for (FloatingObjectSetIterator it = floatingObjectSet.begin(); it != end; ++it) { |
| 55 | FloatingObject* floatingObject = *it; |
| 56 | if (!floatingObject->isDescendant()) |
| 57 | oldIntrudingFloatSet.add(floatingObject->renderer()); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | // Inline blocks are covered by the isReplaced() check in the avoidFloats method. |
| 62 | if (avoidsFloats() || isRoot() || isRenderView() || isFloatingOrOutOfFlowPositioned() || isTableCell()) { |
| 63 | if (m_floatingObjects) { |
| 64 | deleteAllValues(m_floatingObjects->set()); |
| 65 | m_floatingObjects->clear(); |
| 66 | } |
| 67 | if (!oldIntrudingFloatSet.isEmpty()) |
| 68 | markAllDescendantsWithFloatsForLayout(); |
| 69 | return; |
| 70 | } |
| 71 | |
| 72 | typedef HashMap<RenderObject*, FloatingObject*> RendererToFloatInfoMap; |
| 73 | RendererToFloatInfoMap floatMap; |
| 74 | |
| 75 | if (m_floatingObjects) { |
| 76 | const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); |
| 77 | if (childrenInline()) { |
| 78 | FloatingObjectSetIterator end = floatingObjectSet.end(); |
| 79 | for (FloatingObjectSetIterator it = floatingObjectSet.begin(); it != end; ++it) { |
| 80 | FloatingObject* f = *it; |
| 81 | floatMap.add(f->renderer(), f); |
| 82 | } |
| 83 | } else |
| 84 | deleteAllValues(floatingObjectSet); |
| 85 | m_floatingObjects->clear(); |
| 86 | } |
| 87 | |
| 88 | // We should not process floats if the parent node is not a RenderBlock. Otherwise, we will add |
| 89 | // floats in an invalid context. This will cause a crash arising from a bad cast on the parent. |
| 90 | // See <rdar://problem/8049753>, where float property is applied on a text node in a SVG. |
| 91 | if (!parent() || !parent()->isRenderBlock()) |
| 92 | return; |
| 93 | |
| 94 | // Attempt to locate a previous sibling with overhanging floats. We skip any elements that are |
| 95 | // out of flow (like floating/positioned elements), and we also skip over any objects that may have shifted |
| 96 | // to avoid floats. |
| 97 | RenderBlock* parentBlock = toRenderBlock(parent()); |
| 98 | bool parentHasFloats = false; |
| 99 | RenderObject* prev = previousSibling(); |
| 100 | while (prev && (prev->isFloatingOrOutOfFlowPositioned() || !prev->isBox() || !prev->isRenderBlock() || toRenderBlock(prev)->avoidsFloats())) { |
| 101 | if (prev->isFloating()) |
| 102 | parentHasFloats = true; |
| 103 | prev = prev->previousSibling(); |
| 104 | } |
| 105 | |
| 106 | // First add in floats from the parent. |
| 107 | LayoutUnit logicalTopOffset = logicalTop(); |
| 108 | if (parentHasFloats) |
| 109 | addIntrudingFloats(parentBlock, parentBlock->logicalLeftOffsetForContent(), logicalTopOffset); |
| 110 | |
| 111 | LayoutUnit logicalLeftOffset = 0; |
| 112 | if (prev) |
| 113 | logicalTopOffset -= toRenderBox(prev)->logicalTop(); |
| 114 | else { |
| 115 | prev = parentBlock; |
| 116 | logicalLeftOffset += parentBlock->logicalLeftOffsetForContent(); |
| 117 | } |
| 118 | |
| 119 | // Add overhanging floats from the previous RenderBlock, but only if it has a float that intrudes into our space. |
| 120 | RenderBlock* block = toRenderBlock(prev); |
| 121 | if (block->m_floatingObjects && block->lowestFloatLogicalBottom() > logicalTopOffset) |
| 122 | addIntrudingFloats(block, logicalLeftOffset, logicalTopOffset); |
| 123 | |
| 124 | if (childrenInline()) { |
| 125 | LayoutUnit changeLogicalTop = LayoutUnit::max(); |
| 126 | LayoutUnit changeLogicalBottom = LayoutUnit::min(); |
| 127 | if (m_floatingObjects) { |
| 128 | const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); |
| 129 | FloatingObjectSetIterator end = floatingObjectSet.end(); |
| 130 | for (FloatingObjectSetIterator it = floatingObjectSet.begin(); it != end; ++it) { |
| 131 | FloatingObject* f = *it; |
| 132 | FloatingObject* oldFloatingObject = floatMap.get(f->renderer()); |
| 133 | LayoutUnit logicalBottom = f->logicalBottom(isHorizontalWritingMode()); |
| 134 | if (oldFloatingObject) { |
| 135 | LayoutUnit oldLogicalBottom = oldFloatingObject->logicalBottom(isHorizontalWritingMode()); |
| 136 | if (f->logicalWidth(isHorizontalWritingMode()) != oldFloatingObject->logicalWidth(isHorizontalWritingMode()) || f->logicalLeft(isHorizontalWritingMode()) != oldFloatingObject->logicalLeft(isHorizontalWritingMode())) { |
| 137 | changeLogicalTop = 0; |
| 138 | changeLogicalBottom = max(changeLogicalBottom, max(logicalBottom, oldLogicalBottom)); |
| 139 | } else { |
| 140 | if (logicalBottom != oldLogicalBottom) { |
| 141 | changeLogicalTop = min(changeLogicalTop, min(logicalBottom, oldLogicalBottom)); |
| 142 | changeLogicalBottom = max(changeLogicalBottom, max(logicalBottom, oldLogicalBottom)); |
| 143 | } |
| 144 | LayoutUnit logicalTop = f->logicalTop(isHorizontalWritingMode()); |
| 145 | LayoutUnit oldLogicalTop = oldFloatingObject->logicalTop(isHorizontalWritingMode()); |
| 146 | if (logicalTop != oldLogicalTop) { |
| 147 | changeLogicalTop = min(changeLogicalTop, min(logicalTop, oldLogicalTop)); |
| 148 | changeLogicalBottom = max(changeLogicalBottom, max(logicalTop, oldLogicalTop)); |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | floatMap.remove(f->renderer()); |
| 153 | if (oldFloatingObject->originatingLine() && !selfNeedsLayout()) { |
| 154 | ASSERT(&oldFloatingObject->originatingLine()->renderer() == this); |
| 155 | oldFloatingObject->originatingLine()->markDirty(); |
| 156 | } |
| 157 | delete oldFloatingObject; |
| 158 | } else { |
| 159 | changeLogicalTop = 0; |
| 160 | changeLogicalBottom = max(changeLogicalBottom, logicalBottom); |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | RendererToFloatInfoMap::iterator end = floatMap.end(); |
| 166 | for (RendererToFloatInfoMap::iterator it = floatMap.begin(); it != end; ++it) { |
| 167 | FloatingObject* floatingObject = (*it).value; |
| 168 | if (!floatingObject->isDescendant()) { |
| 169 | changeLogicalTop = 0; |
| 170 | changeLogicalBottom = max(changeLogicalBottom, floatingObject->logicalBottom(isHorizontalWritingMode())); |
| 171 | } |
| 172 | } |
| 173 | deleteAllValues(floatMap); |
| 174 | |
| 175 | markLinesDirtyInBlockRange(changeLogicalTop, changeLogicalBottom); |
| 176 | } else if (!oldIntrudingFloatSet.isEmpty()) { |
| 177 | // If there are previously intruding floats that no longer intrude, then children with floats |
| 178 | // should also get layout because they might need their floating object lists cleared. |
| 179 | if (m_floatingObjects->set().size() < oldIntrudingFloatSet.size()) |
| 180 | markAllDescendantsWithFloatsForLayout(); |
| 181 | else { |
| 182 | const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); |
| 183 | FloatingObjectSetIterator end = floatingObjectSet.end(); |
| 184 | for (FloatingObjectSetIterator it = floatingObjectSet.begin(); it != end && !oldIntrudingFloatSet.isEmpty(); ++it) |
| 185 | oldIntrudingFloatSet.remove((*it)->renderer()); |
| 186 | if (!oldIntrudingFloatSet.isEmpty()) |
| 187 | markAllDescendantsWithFloatsForLayout(); |
| 188 | } |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight) |
| 193 | { |
| 194 | ASSERT(needsLayout()); |
| 195 | |
| 196 | if (!relayoutChildren && simplifiedLayout()) |
| 197 | return; |
| 198 | |
| 199 | LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); |
| 200 | |
| 201 | if (updateLogicalWidthAndColumnWidth()) |
| 202 | relayoutChildren = true; |
| 203 | |
| 204 | clearFloats(); |
| 205 | |
| 206 | LayoutUnit previousHeight = logicalHeight(); |
| 207 | // FIXME: should this start out as borderAndPaddingLogicalHeight() + scrollbarLogicalHeight(), |
| 208 | // for consistency with other render classes? |
| 209 | setLogicalHeight(0); |
| 210 | |
| 211 | bool pageLogicalHeightChanged = false; |
| 212 | bool hasSpecifiedPageLogicalHeight = false; |
| 213 | checkForPaginationLogicalHeightChange(pageLogicalHeight, pageLogicalHeightChanged, hasSpecifiedPageLogicalHeight); |
| 214 | |
| 215 | RenderStyle* styleToUse = style(); |
| 216 | LayoutStateMaintainer statePusher(&view(), this, locationOffset(), hasColumns() || hasTransform() || hasReflection() || styleToUse->isFlippedBlocksWritingMode(), pageLogicalHeight, pageLogicalHeightChanged, columnInfo()); |
| 217 | |
| 218 | // Regions changing widths can force us to relayout our children. |
| 219 | RenderFlowThread* flowThread = flowThreadContainingBlock(); |
| 220 | if (logicalWidthChangedInRegions(flowThread)) |
| 221 | relayoutChildren = true; |
| 222 | if (updateShapesBeforeBlockLayout()) |
| 223 | relayoutChildren = true; |
| 224 | |
| 225 | // We use four values, maxTopPos, maxTopNeg, maxBottomPos, and maxBottomNeg, to track |
| 226 | // our current maximal positive and negative margins. These values are used when we |
| 227 | // are collapsed with adjacent blocks, so for example, if you have block A and B |
| 228 | // collapsing together, then you'd take the maximal positive margin from both A and B |
| 229 | // and subtract it from the maximal negative margin from both A and B to get the |
| 230 | // true collapsed margin. This algorithm is recursive, so when we finish layout() |
| 231 | // our block knows its current maximal positive/negative values. |
| 232 | // |
| 233 | // Start out by setting our margin values to our current margins. Table cells have |
| 234 | // no margins, so we don't fill in the values for table cells. |
| 235 | bool isCell = isTableCell(); |
| 236 | if (!isCell) { |
| 237 | initMaxMarginValues(); |
| 238 | |
| 239 | setHasMarginBeforeQuirk(styleToUse->hasMarginBeforeQuirk()); |
| 240 | setHasMarginAfterQuirk(styleToUse->hasMarginAfterQuirk()); |
| 241 | setPaginationStrut(0); |
| 242 | } |
| 243 | |
| 244 | LayoutUnit repaintLogicalTop = 0; |
| 245 | LayoutUnit repaintLogicalBottom = 0; |
| 246 | LayoutUnit maxFloatLogicalBottom = 0; |
| 247 | if (!firstChild() && !isAnonymousBlock()) |
| 248 | setChildrenInline(true); |
| 249 | if (childrenInline()) |
| 250 | layoutInlineChildren(relayoutChildren, repaintLogicalTop, repaintLogicalBottom); |
| 251 | else |
| 252 | layoutBlockChildren(relayoutChildren, maxFloatLogicalBottom); |
| 253 | |
| 254 | // Expand our intrinsic height to encompass floats. |
| 255 | LayoutUnit toAdd = borderAndPaddingAfter() + scrollbarLogicalHeight(); |
| 256 | if (lowestFloatLogicalBottom() > (logicalHeight() - toAdd) && expandsToEncloseOverhangingFloats()) |
| 257 | setLogicalHeight(lowestFloatLogicalBottom() + toAdd); |
| 258 | |
| 259 | if (relayoutForPagination(hasSpecifiedPageLogicalHeight, pageLogicalHeight, statePusher) || relayoutToAvoidWidows(statePusher)) { |
| 260 | ASSERT(!shouldBreakAtLineToAvoidWidow()); |
| 261 | return; |
| 262 | } |
| 263 | |
| 264 | // Calculate our new height. |
| 265 | LayoutUnit oldHeight = logicalHeight(); |
| 266 | LayoutUnit oldClientAfterEdge = clientLogicalBottom(); |
| 267 | |
| 268 | // Before updating the final size of the flow thread make sure a forced break is applied after the content. |
| 269 | // This ensures the size information is correctly computed for the last auto-height region receiving content. |
| 270 | if (isRenderFlowThread()) |
| 271 | toRenderFlowThread(this)->applyBreakAfterContent(oldClientAfterEdge); |
| 272 | |
| 273 | updateLogicalHeight(); |
| 274 | LayoutUnit newHeight = logicalHeight(); |
| 275 | if (oldHeight != newHeight) { |
| 276 | if (oldHeight > newHeight && maxFloatLogicalBottom > newHeight && !childrenInline()) { |
| 277 | // One of our children's floats may have become an overhanging float for us. We need to look for it. |
| 278 | for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { |
| 279 | if (child->isRenderBlockFlow() && !child->isFloatingOrOutOfFlowPositioned()) { |
| 280 | RenderBlock* block = toRenderBlock(child); |
| 281 | if (block->lowestFloatLogicalBottom() + block->logicalTop() > newHeight) |
| 282 | addOverhangingFloats(block, false); |
| 283 | } |
| 284 | } |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | bool heightChanged = (previousHeight != newHeight); |
| 289 | if (heightChanged) |
| 290 | relayoutChildren = true; |
| 291 | |
| 292 | layoutPositionedObjects(relayoutChildren || isRoot()); |
| 293 | |
| 294 | updateShapesAfterBlockLayout(heightChanged); |
| 295 | |
| 296 | // Add overflow from children (unless we're multi-column, since in that case all our child overflow is clipped anyway). |
| 297 | computeOverflow(oldClientAfterEdge); |
| 298 | |
| 299 | statePusher.pop(); |
| 300 | |
| 301 | fitBorderToLinesIfNeeded(); |
| 302 | |
| 303 | if (view().layoutState()->m_pageLogicalHeight) |
| 304 | setPageLogicalOffset(view().layoutState()->pageLogicalOffset(this, logicalTop())); |
| 305 | |
| 306 | updateLayerTransform(); |
| 307 | |
| 308 | // Update our scroll information if we're overflow:auto/scroll/hidden now that we know if |
| 309 | // we overflow or not. |
| 310 | updateScrollInfoAfterLayout(); |
| 311 | |
| 312 | // FIXME: This repaint logic should be moved into a separate helper function! |
| 313 | // Repaint with our new bounds if they are different from our old bounds. |
| 314 | bool didFullRepaint = repainter.repaintAfterLayout(); |
| 315 | if (!didFullRepaint && repaintLogicalTop != repaintLogicalBottom && (styleToUse->visibility() == VISIBLE || enclosingLayer()->hasVisibleContent())) { |
| 316 | // FIXME: We could tighten up the left and right invalidation points if we let layoutInlineChildren fill them in based off the particular lines |
| 317 | // it had to lay out. We wouldn't need the hasOverflowClip() hack in that case either. |
| 318 | LayoutUnit repaintLogicalLeft = logicalLeftVisualOverflow(); |
| 319 | LayoutUnit repaintLogicalRight = logicalRightVisualOverflow(); |
| 320 | if (hasOverflowClip()) { |
| 321 | // If we have clipped overflow, we should use layout overflow as well, since visual overflow from lines didn't propagate to our block's overflow. |
| 322 | // Note the old code did this as well but even for overflow:visible. The addition of hasOverflowClip() at least tightens up the hack a bit. |
| 323 | // layoutInlineChildren should be patched to compute the entire repaint rect. |
| 324 | repaintLogicalLeft = min(repaintLogicalLeft, logicalLeftLayoutOverflow()); |
| 325 | repaintLogicalRight = max(repaintLogicalRight, logicalRightLayoutOverflow()); |
| 326 | } |
| 327 | |
| 328 | LayoutRect repaintRect; |
| 329 | if (isHorizontalWritingMode()) |
| 330 | repaintRect = LayoutRect(repaintLogicalLeft, repaintLogicalTop, repaintLogicalRight - repaintLogicalLeft, repaintLogicalBottom - repaintLogicalTop); |
| 331 | else |
| 332 | repaintRect = LayoutRect(repaintLogicalTop, repaintLogicalLeft, repaintLogicalBottom - repaintLogicalTop, repaintLogicalRight - repaintLogicalLeft); |
| 333 | |
| 334 | // The repaint rect may be split across columns, in which case adjustRectForColumns() will return the union. |
| 335 | adjustRectForColumns(repaintRect); |
| 336 | |
| 337 | repaintRect.inflate(maximalOutlineSize(PaintPhaseOutline)); |
| 338 | |
| 339 | if (hasOverflowClip()) { |
| 340 | // Adjust repaint rect for scroll offset |
| 341 | repaintRect.move(-scrolledContentOffset()); |
| 342 | |
| 343 | // Don't allow this rect to spill out of our overflow box. |
| 344 | repaintRect.intersect(LayoutRect(LayoutPoint(), size())); |
| 345 | } |
| 346 | |
| 347 | // Make sure the rect is still non-empty after intersecting for overflow above |
| 348 | if (!repaintRect.isEmpty()) { |
| 349 | repaintRectangle(repaintRect); // We need to do a partial repaint of our content. |
| 350 | if (hasReflection()) |
| 351 | repaintRectangle(reflectedRect(repaintRect)); |
| 352 | } |
| 353 | } |
| 354 | |
| 355 | setNeedsLayout(false); |
| 356 | } |
| 357 | |
| 358 | void RenderBlockFlow::layoutBlockChildren(bool relayoutChildren, LayoutUnit& maxFloatLogicalBottom) |
| 359 | { |
| 360 | dirtyForLayoutFromPercentageHeightDescendants(); |
| 361 | |
| 362 | LayoutUnit beforeEdge = borderAndPaddingBefore(); |
| 363 | LayoutUnit afterEdge = borderAndPaddingAfter() + scrollbarLogicalHeight(); |
| 364 | |
| 365 | setLogicalHeight(beforeEdge); |
| 366 | |
| 367 | // Lay out our hypothetical grid line as though it occurs at the top of the block. |
| 368 | if (view().layoutState()->lineGrid() == this) |
| 369 | layoutLineGridBox(); |
| 370 | |
| 371 | // The margin struct caches all our current margin collapsing state. |
| 372 | MarginInfo marginInfo(this, beforeEdge, afterEdge); |
| 373 | |
| 374 | // Fieldsets need to find their legend and position it inside the border of the object. |
| 375 | // The legend then gets skipped during normal layout. The same is true for ruby text. |
| 376 | // It doesn't get included in the normal layout process but is instead skipped. |
| 377 | RenderObject* childToExclude = layoutSpecialExcludedChild(relayoutChildren); |
| 378 | |
| 379 | LayoutUnit previousFloatLogicalBottom = 0; |
| 380 | maxFloatLogicalBottom = 0; |
| 381 | |
| 382 | RenderBox* next = firstChildBox(); |
| 383 | |
| 384 | while (next) { |
| 385 | RenderBox* child = next; |
| 386 | next = child->nextSiblingBox(); |
| 387 | |
| 388 | if (childToExclude == child) |
| 389 | continue; // Skip this child, since it will be positioned by the specialized subclass (fieldsets and ruby runs). |
| 390 | |
| 391 | updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, child); |
| 392 | |
| 393 | if (child->isOutOfFlowPositioned()) { |
| 394 | child->containingBlock()->insertPositionedObject(child); |
| 395 | adjustPositionedBlock(child, marginInfo); |
| 396 | continue; |
| 397 | } |
| 398 | if (child->isFloating()) { |
| 399 | insertFloatingObject(child); |
| 400 | adjustFloatingBlock(marginInfo); |
| 401 | continue; |
| 402 | } |
| 403 | |
| 404 | // Lay out the child. |
| 405 | layoutBlockChild(child, marginInfo, previousFloatLogicalBottom, maxFloatLogicalBottom); |
| 406 | } |
| 407 | |
| 408 | // Now do the handling of the bottom of the block, adding in our bottom border/padding and |
| 409 | // determining the correct collapsed bottom margin information. |
| 410 | handleAfterSideOfBlock(beforeEdge, afterEdge, marginInfo); |
| 411 | } |
| 412 | |
hyatt@apple.com | 5388e67 | 2013-09-06 20:54:47 +0000 | [diff] [blame] | 413 | } // namespace WebCore |