void HTMLElement::mapLanguageAttributeToLocale(const AtomicString& value, MutableStylePropertySet* style) { if (!value.isEmpty()) { // Have to quote so the locale id is treated as a string instead of as a CSS keyword. addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLocale, quoteCSSString(value)); // FIXME: Remove the following UseCounter code when we collect enough // data. UseCounter::count(document(), UseCounter::LangAttribute); if (isHTMLHtmlElement(*this)) UseCounter::count(document(), UseCounter::LangAttributeOnHTML); else if (isHTMLBodyElement(*this)) UseCounter::count(document(), UseCounter::LangAttributeOnBody); String htmlLanguage = value.string(); size_t firstSeparator = htmlLanguage.find('-'); if (firstSeparator != kNotFound) htmlLanguage = htmlLanguage.left(firstSeparator); String uiLanguage = defaultLanguage(); firstSeparator = uiLanguage.find('-'); if (firstSeparator != kNotFound) uiLanguage = uiLanguage.left(firstSeparator); firstSeparator = uiLanguage.find('_'); if (firstSeparator != kNotFound) uiLanguage = uiLanguage.left(firstSeparator); if (!equalIgnoringCase(htmlLanguage, uiLanguage)) UseCounter::count(document(), UseCounter::LangAttributeDoesNotMatchToUILocale); } else { // The empty string means the language is explicitly unknown. addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitLocale, CSSValueAuto); } }
void findGoodTouchTargets(const IntRect& touchBox, LocalFrame* mainFrame, Vector<IntRect>& goodTargets, WillBeHeapVector<RawPtrWillBeMember<Node> >& highlightNodes) { goodTargets.clear(); int touchPointPadding = ceil(std::max(touchBox.width(), touchBox.height()) * 0.5); IntPoint touchPoint = touchBox.center(); IntPoint contentsPoint = mainFrame->view()->windowToContents(touchPoint); HitTestResult result = mainFrame->eventHandler().hitTestResultAtPoint(contentsPoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent, IntSize(touchPointPadding, touchPointPadding)); const WillBeHeapListHashSet<RefPtrWillBeMember<Node> >& hitResults = result.rectBasedTestResult(); // Blacklist nodes that are container of disambiguated nodes. // It is not uncommon to have a clickable <div> that contains other clickable objects. // This heuristic avoids excessive disambiguation in that case. WillBeHeapHashSet<RawPtrWillBeMember<Node> > blackList; for (WillBeHeapListHashSet<RefPtrWillBeMember<Node> >::const_iterator it = hitResults.begin(); it != hitResults.end(); ++it) { // Ignore any Nodes that can't be clicked on. RenderObject* renderer = it->get()->renderer(); if (!renderer || !it->get()->willRespondToMouseClickEvents()) continue; // Blacklist all of the Node's containers. for (RenderBlock* container = renderer->containingBlock(); container; container = container->containingBlock()) { Node* containerNode = container->node(); if (!containerNode) continue; if (!blackList.add(containerNode).isNewEntry) break; } } WillBeHeapHashMap<RawPtrWillBeMember<Node>, TouchTargetData> touchTargets; float bestScore = 0; for (WillBeHeapListHashSet<RefPtrWillBeMember<Node> >::const_iterator it = hitResults.begin(); it != hitResults.end(); ++it) { for (Node* node = it->get(); node; node = node->parentNode()) { if (blackList.contains(node)) continue; if (node->isDocumentNode() || isHTMLHtmlElement(*node) || isHTMLBodyElement(*node)) break; if (node->willRespondToMouseClickEvents()) { TouchTargetData& targetData = touchTargets.add(node, TouchTargetData()).storedValue->value; targetData.windowBoundingBox = boundingBoxForEventNodes(node); targetData.score = scoreTouchTarget(touchPoint, touchPointPadding, targetData.windowBoundingBox); bestScore = std::max(bestScore, targetData.score); break; } } } for (WillBeHeapHashMap<RawPtrWillBeMember<Node>, TouchTargetData>::iterator it = touchTargets.begin(); it != touchTargets.end(); ++it) { // Currently the scoring function uses the overlap area with the fat point as the score. // We ignore the candidates that has less than 1/2 overlap (we consider not really ambiguous enough) than the best candidate to avoid excessive popups. if (it->value.score < bestScore * 0.5) continue; goodTargets.append(it->value.windowBoundingBox); highlightNodes.append(it->key); } }
Node* DOMPatchSupport::patchNode(Node* node, const String& markup, ExceptionState& exceptionState) { // Don't parse <html> as a fragment. if (node->isDocumentNode() || (node->parentNode() && node->parentNode()->isDocumentNode())) { patchDocument(markup); return 0; } Node* previousSibling = node->previousSibling(); RefPtrWillBeRawPtr<DocumentFragment> fragment = DocumentFragment::create(m_document); Node* targetNode = node->parentElementOrShadowRoot() ? node->parentElementOrShadowRoot() : m_document.documentElement(); // Use the document BODY as the context element when editing immediate shadow root children, // as it provides an equivalent parsing context. if (targetNode->isShadowRoot()) targetNode = m_document.body(); Element* targetElement = toElement(targetNode); // FIXME: This code should use one of createFragment* in markup.h if (m_document.isHTMLDocument()) fragment->parseHTML(markup, targetElement); else fragment->parseXML(markup, targetElement); // Compose the old list. ContainerNode* parentNode = node->parentNode(); Vector<OwnPtr<Digest> > oldList; for (Node* child = parentNode->firstChild(); child; child = child->nextSibling()) oldList.append(createDigest(child, 0)); // Compose the new list. String markupCopy = markup.lower(); Vector<OwnPtr<Digest> > newList; for (Node* child = parentNode->firstChild(); child != node; child = child->nextSibling()) newList.append(createDigest(child, 0)); for (Node* child = fragment->firstChild(); child; child = child->nextSibling()) { if (isHTMLHeadElement(*child) && !child->firstChild() && markupCopy.find("</head>") == kNotFound) continue; // HTML5 parser inserts empty <head> tag whenever it parses <body> if (isHTMLBodyElement(*child) && !child->firstChild() && markupCopy.find("</body>") == kNotFound) continue; // HTML5 parser inserts empty <body> tag whenever it parses </head> newList.append(createDigest(child, &m_unusedNodesMap)); } for (Node* child = node->nextSibling(); child; child = child->nextSibling()) newList.append(createDigest(child, 0)); if (!innerPatchChildren(parentNode, oldList, newList, exceptionState)) { // Fall back to total replace. if (!m_domEditor->replaceChild(parentNode, fragment.release(), node, exceptionState)) return 0; } return previousSibling ? previousSibling->nextSibling() : parentNode->firstChild(); }
DocumentFragment* createContextualFragment( const String& markup, Element* element, ParserContentPolicy parserContentPolicy, ExceptionState& exceptionState) { DCHECK(element); if (!isSupportedContainer(element)) { exceptionState.throwDOMException( NotSupportedError, "The range's container is '" + element->localName() + "', which is not supported."); return nullptr; } DocumentFragment* fragment = createFragmentForInnerOuterHTML( markup, element, parserContentPolicy, "createContextualFragment", exceptionState); if (!fragment) return nullptr; // We need to pop <html> and <body> elements and remove <head> to // accommodate folks passing complete HTML documents to make the // child of an element. Node* nextNode = nullptr; for (Node* node = fragment->firstChild(); node; node = nextNode) { nextNode = node->nextSibling(); if (isHTMLHtmlElement(*node) || isHTMLHeadElement(*node) || isHTMLBodyElement(*node)) { HTMLElement* element = toHTMLElement(node); if (Node* firstChild = element->firstChild()) nextNode = firstChild; removeElementPreservingChildren(fragment, element); } } return fragment; }
DocumentFragment* createFragmentFromText(const EphemeralRange& context, const String& text) { if (context.isNull()) return nullptr; Document& document = context.document(); DocumentFragment* fragment = document.createDocumentFragment(); if (text.isEmpty()) return fragment; String string = text; string.replace("\r\n", "\n"); string.replace('\r', '\n'); if (!isRichlyEditablePosition(context.startPosition()) || shouldPreserveNewline(context)) { fragment->appendChild(document.createTextNode(string)); if (string.endsWith('\n')) { HTMLBRElement* element = HTMLBRElement::create(document); element->setAttribute(classAttr, AppleInterchangeNewline); fragment->appendChild(element); } return fragment; } // A string with no newlines gets added inline, rather than being put into a // paragraph. if (string.find('\n') == kNotFound) { fillContainerFromString(fragment, string); return fragment; } // Break string into paragraphs. Extra line breaks turn into empty paragraphs. Element* block = enclosingBlock(context.startPosition().nodeAsRangeFirstNode()); bool useClonesOfEnclosingBlock = block && !isHTMLBodyElement(*block) && !isHTMLHtmlElement(*block) && block != rootEditableElementOf(context.startPosition()); Vector<String> list; string.split('\n', true, list); // true gets us empty strings in the list size_t numLines = list.size(); for (size_t i = 0; i < numLines; ++i) { const String& s = list[i]; Element* element = nullptr; if (s.isEmpty() && i + 1 == numLines) { // For last line, use the "magic BR" rather than a P. element = HTMLBRElement::create(document); element->setAttribute(classAttr, AppleInterchangeNewline); } else { if (useClonesOfEnclosingBlock) element = block->cloneElementWithoutChildren(); else element = createDefaultParagraphElement(document); fillContainerFromString(element, s); } fragment->appendChild(element); } return fragment; }
bool DOMPatchSupport::innerPatchChildren(ContainerNode* parentNode, const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList, ExceptionState& exceptionState) { pair<ResultMap, ResultMap> resultMaps = diff(oldList, newList); ResultMap& oldMap = resultMaps.first; ResultMap& newMap = resultMaps.second; Digest* oldHead = 0; Digest* oldBody = 0; // 1. First strip everything except for the nodes that retain. Collect pending merges. HashMap<Digest*, Digest*> merges; HashSet<size_t, WTF::IntHash<size_t>, WTF::UnsignedWithZeroKeyHashTraits<size_t> > usedNewOrdinals; for (size_t i = 0; i < oldList.size(); ++i) { if (oldMap[i].first) { if (usedNewOrdinals.add(oldMap[i].second).isNewEntry) continue; oldMap[i].first = 0; oldMap[i].second = 0; } // Always match <head> and <body> tags with each other - we can't remove them from the DOM // upon patching. if (isHTMLHeadElement(*oldList[i]->m_node)) { oldHead = oldList[i].get(); continue; } if (isHTMLBodyElement(*oldList[i]->m_node)) { oldBody = oldList[i].get(); continue; } // Check if this change is between stable nodes. If it is, consider it as "modified". if (!m_unusedNodesMap.contains(oldList[i]->m_sha1) && (!i || oldMap[i - 1].first) && (i == oldMap.size() - 1 || oldMap[i + 1].first)) { size_t anchorCandidate = i ? oldMap[i - 1].second + 1 : 0; size_t anchorAfter = (i == oldMap.size() - 1) ? anchorCandidate + 1 : oldMap[i + 1].second; if (anchorAfter - anchorCandidate == 1 && anchorCandidate < newList.size()) merges.set(newList[anchorCandidate].get(), oldList[i].get()); else { if (!removeChildAndMoveToNew(oldList[i].get(), exceptionState)) return false; } } else { if (!removeChildAndMoveToNew(oldList[i].get(), exceptionState)) return false; } } // Mark retained nodes as used, do not reuse node more than once. HashSet<size_t, WTF::IntHash<size_t>, WTF::UnsignedWithZeroKeyHashTraits<size_t> > usedOldOrdinals; for (size_t i = 0; i < newList.size(); ++i) { if (!newMap[i].first) continue; size_t oldOrdinal = newMap[i].second; if (usedOldOrdinals.contains(oldOrdinal)) { // Do not map node more than once newMap[i].first = 0; newMap[i].second = 0; continue; } usedOldOrdinals.add(oldOrdinal); markNodeAsUsed(newMap[i].first); } // Mark <head> and <body> nodes for merge. if (oldHead || oldBody) { for (size_t i = 0; i < newList.size(); ++i) { if (oldHead && isHTMLHeadElement(*newList[i]->m_node)) merges.set(newList[i].get(), oldHead); if (oldBody && isHTMLBodyElement(*newList[i]->m_node)) merges.set(newList[i].get(), oldBody); } } // 2. Patch nodes marked for merge. for (HashMap<Digest*, Digest*>::iterator it = merges.begin(); it != merges.end(); ++it) { if (!innerPatchNode(it->value, it->key, exceptionState)) return false; } // 3. Insert missing nodes. for (size_t i = 0; i < newMap.size(); ++i) { if (newMap[i].first || merges.contains(newList[i].get())) continue; if (!insertBeforeAndMarkAsUsed(parentNode, newList[i].get(), parentNode->traverseToChildAt(i), exceptionState)) return false; } // 4. Then put all nodes that retained into their slots (sort by new index). for (size_t i = 0; i < oldMap.size(); ++i) { if (!oldMap[i].first) continue; RefPtrWillBeRawPtr<Node> node = oldMap[i].first->m_node; Node* anchorNode = parentNode->traverseToChildAt(oldMap[i].second); if (node == anchorNode) continue; if (isHTMLBodyElement(*node) || isHTMLHeadElement(*node)) continue; // Never move head or body, move the rest of the nodes around them. if (!m_domEditor->insertBefore(parentNode, node.release(), anchorNode, exceptionState)) return false; } return true; }