| /* |
| * Copyright (C) 2006, 2008, 2009, 2010 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/html/html_view_source_document.h" |
| |
| #include "third_party/blink/public/strings/grit/blink_strings.h" |
| #include "third_party/blink/renderer/core/css/css_value_id_mappings.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/dom/events/native_event_listener.h" |
| #include "third_party/blink/renderer/core/dom/text.h" |
| #include "third_party/blink/renderer/core/events/mouse_event.h" |
| #include "third_party/blink/renderer/core/html/forms/html_form_element.h" |
| #include "third_party/blink/renderer/core/html/forms/html_input_element.h" |
| #include "third_party/blink/renderer/core/html/forms/html_label_element.h" |
| #include "third_party/blink/renderer/core/html/html_anchor_element.h" |
| #include "third_party/blink/renderer/core/html/html_base_element.h" |
| #include "third_party/blink/renderer/core/html/html_body_element.h" |
| #include "third_party/blink/renderer/core/html/html_br_element.h" |
| #include "third_party/blink/renderer/core/html/html_div_element.h" |
| #include "third_party/blink/renderer/core/html/html_head_element.h" |
| #include "third_party/blink/renderer/core/html/html_html_element.h" |
| #include "third_party/blink/renderer/core/html/html_span_element.h" |
| #include "third_party/blink/renderer/core/html/html_table_cell_element.h" |
| #include "third_party/blink/renderer/core/html/html_table_element.h" |
| #include "third_party/blink/renderer/core/html/html_table_row_element.h" |
| #include "third_party/blink/renderer/core/html/html_table_section_element.h" |
| #include "third_party/blink/renderer/core/html/parser/html_view_source_parser.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/text/platform_locale.h" |
| |
| namespace blink { |
| |
| class ViewSourceEventListener : public NativeEventListener { |
| public: |
| ViewSourceEventListener(HTMLTableElement* table, HTMLInputElement* checkbox) |
| : table_(table), checkbox_(checkbox) {} |
| |
| void Invoke(ExecutionContext*, Event* event) override { |
| DCHECK_EQ(event->type(), event_type_names::kChange); |
| table_->setAttribute(html_names::kClassAttr, |
| checkbox_->checked() ? "line-wrap" : ""); |
| } |
| |
| void Trace(Visitor* visitor) const override { |
| visitor->Trace(table_); |
| visitor->Trace(checkbox_); |
| NativeEventListener::Trace(visitor); |
| } |
| |
| private: |
| Member<HTMLTableElement> table_; |
| Member<HTMLInputElement> checkbox_; |
| }; |
| |
| HTMLViewSourceDocument::HTMLViewSourceDocument(const DocumentInit& initializer) |
| : HTMLDocument(initializer), type_(initializer.GetMimeType()) { |
| SetIsViewSource(true); |
| SetCompatibilityMode(kNoQuirksMode); |
| LockCompatibilityMode(); |
| } |
| |
| DocumentParser* HTMLViewSourceDocument::CreateParser() { |
| return MakeGarbageCollected<HTMLViewSourceParser>(*this, type_); |
| } |
| |
| void HTMLViewSourceDocument::CreateContainingTable() { |
| auto* html = MakeGarbageCollected<HTMLHtmlElement>(*this); |
| ParserAppendChild(html); |
| auto* head = MakeGarbageCollected<HTMLHeadElement>(*this); |
| html->ParserAppendChild(head); |
| auto* body = MakeGarbageCollected<HTMLBodyElement>(*this); |
| html->ParserAppendChild(body); |
| |
| // Create a line gutter div that can be used to make sure the gutter extends |
| // down the height of the whole document. |
| auto* div = MakeGarbageCollected<HTMLDivElement>(*this); |
| div->setAttribute(html_names::kClassAttr, "line-gutter-backdrop"); |
| body->ParserAppendChild(div); |
| |
| auto* table = MakeGarbageCollected<HTMLTableElement>(*this); |
| body->ParserAppendChild(table); |
| tbody_ = MakeGarbageCollected<HTMLTableSectionElement>(html_names::kTbodyTag, |
| *this); |
| table->ParserAppendChild(tbody_); |
| current_ = tbody_; |
| line_number_ = 0; |
| |
| // Create a checkbox to control line wrapping. |
| auto* checkbox = |
| MakeGarbageCollected<HTMLInputElement>(*this, CreateElementFlags()); |
| checkbox->setAttribute(html_names::kTypeAttr, "checkbox"); |
| checkbox->addEventListener( |
| event_type_names::kChange, |
| MakeGarbageCollected<ViewSourceEventListener>(table, checkbox), |
| /*use_capture=*/false); |
| auto* label = MakeGarbageCollected<HTMLLabelElement>(*this); |
| label->ParserAppendChild( |
| Text::Create(*this, WTF::AtomicString(Locale::DefaultLocale().QueryString( |
| IDS_VIEW_SOURCE_LINE_WRAP)))); |
| label->setAttribute(html_names::kClassAttr, "line-wrap-control"); |
| label->ParserAppendChild(checkbox); |
| // Add the checkbox to a form with autocomplete=off, to avoid form |
| // restoration from changing the value of the checkbox. |
| auto* form = MakeGarbageCollected<HTMLFormElement>(*this); |
| form->setAttribute(html_names::kAutocompleteAttr, "off"); |
| form->ParserAppendChild(label); |
| auto* tr = MakeGarbageCollected<HTMLTableRowElement>(*this); |
| auto* td = |
| MakeGarbageCollected<HTMLTableCellElement>(html_names::kTdTag, *this); |
| td->setAttribute(html_names::kColspanAttr, "2"); |
| td->setAttribute(html_names::kClassAttr, "line-wrap-cell"); |
| td->ParserAppendChild(form); |
| tr->ParserAppendChild(td); |
| tbody_->ParserAppendChild(tr); |
| } |
| |
| void HTMLViewSourceDocument::AddSource(const String& source, HTMLToken& token) { |
| if (!current_) |
| CreateContainingTable(); |
| |
| switch (token.GetType()) { |
| case HTMLToken::kUninitialized: |
| NOTREACHED(); |
| break; |
| case HTMLToken::DOCTYPE: |
| ProcessDoctypeToken(source, token); |
| break; |
| case HTMLToken::kEndOfFile: |
| ProcessEndOfFileToken(source, token); |
| break; |
| case HTMLToken::kStartTag: |
| case HTMLToken::kEndTag: |
| ProcessTagToken(source, token); |
| break; |
| case HTMLToken::kComment: |
| ProcessCommentToken(source, token); |
| break; |
| case HTMLToken::kCharacter: |
| ProcessCharacterToken(source, token); |
| break; |
| } |
| } |
| |
| void HTMLViewSourceDocument::ProcessDoctypeToken(const String& source, |
| HTMLToken&) { |
| current_ = AddSpanWithClassName("html-doctype"); |
| AddText(source, "html-doctype"); |
| current_ = td_; |
| } |
| |
| void HTMLViewSourceDocument::ProcessEndOfFileToken(const String& source, |
| HTMLToken&) { |
| current_ = AddSpanWithClassName("html-end-of-file"); |
| AddText(source, "html-end-of-file"); |
| current_ = td_; |
| } |
| |
| void HTMLViewSourceDocument::ProcessTagToken(const String& source, |
| HTMLToken& token) { |
| current_ = AddSpanWithClassName("html-tag"); |
| |
| AtomicString tag_name(token.GetName()); |
| |
| unsigned index = 0; |
| HTMLToken::AttributeList::const_iterator iter = token.Attributes().begin(); |
| while (index < source.length()) { |
| if (iter == token.Attributes().end()) { |
| // We want to show the remaining characters in the token. |
| index = AddRange(source, index, source.length(), g_empty_atom); |
| DCHECK_EQ(index, source.length()); |
| break; |
| } |
| |
| AtomicString name(iter->GetName()); |
| AtomicString value(iter->Value8BitIfNecessary()); |
| |
| index = |
| AddRange(source, index, iter->NameRange().start - token.StartIndex(), |
| g_empty_atom); |
| index = AddRange(source, index, iter->NameRange().end - token.StartIndex(), |
| "html-attribute-name"); |
| |
| if (tag_name == html_names::kBaseTag && name == html_names::kHrefAttr) |
| AddBase(value); |
| |
| index = |
| AddRange(source, index, iter->ValueRange().start - token.StartIndex(), |
| g_empty_atom); |
| |
| if (name == html_names::kSrcsetAttr) { |
| index = |
| AddSrcset(source, index, iter->ValueRange().end - token.StartIndex()); |
| } else { |
| bool is_link = |
| name == html_names::kSrcAttr || name == html_names::kHrefAttr; |
| index = |
| AddRange(source, index, iter->ValueRange().end - token.StartIndex(), |
| "html-attribute-value", is_link, |
| tag_name == html_names::kATag, value); |
| } |
| |
| ++iter; |
| } |
| current_ = td_; |
| } |
| |
| void HTMLViewSourceDocument::ProcessCommentToken(const String& source, |
| HTMLToken&) { |
| current_ = AddSpanWithClassName("html-comment"); |
| AddText(source, "html-comment"); |
| current_ = td_; |
| } |
| |
| void HTMLViewSourceDocument::ProcessCharacterToken(const String& source, |
| HTMLToken&) { |
| AddText(source, ""); |
| } |
| |
| Element* HTMLViewSourceDocument::AddSpanWithClassName( |
| const AtomicString& class_name) { |
| if (current_ == tbody_) { |
| AddLine(class_name); |
| return current_; |
| } |
| |
| auto* span = MakeGarbageCollected<HTMLSpanElement>(*this); |
| span->setAttribute(html_names::kClassAttr, class_name); |
| current_->ParserAppendChild(span); |
| return span; |
| } |
| |
| void HTMLViewSourceDocument::AddLine(const AtomicString& class_name) { |
| // Create a table row. |
| auto* trow = MakeGarbageCollected<HTMLTableRowElement>(*this); |
| tbody_->ParserAppendChild(trow); |
| |
| // Create a cell that will hold the line number (it is generated in the |
| // stylesheet using counters). |
| auto* td = |
| MakeGarbageCollected<HTMLTableCellElement>(html_names::kTdTag, *this); |
| td->setAttribute(html_names::kClassAttr, "line-number"); |
| td->SetIntegralAttribute(html_names::kValueAttr, ++line_number_); |
| trow->ParserAppendChild(td); |
| |
| // Create a second cell for the line contents |
| td = MakeGarbageCollected<HTMLTableCellElement>(html_names::kTdTag, *this); |
| td->setAttribute(html_names::kClassAttr, "line-content"); |
| trow->ParserAppendChild(td); |
| current_ = td_ = td; |
| |
| // Open up the needed spans. |
| if (!class_name.IsEmpty()) { |
| if (class_name == "html-attribute-name" || |
| class_name == "html-attribute-value") |
| current_ = AddSpanWithClassName("html-tag"); |
| current_ = AddSpanWithClassName(class_name); |
| } |
| } |
| |
| void HTMLViewSourceDocument::FinishLine() { |
| if (!current_->HasChildren()) { |
| auto* br = MakeGarbageCollected<HTMLBRElement>(*this); |
| current_->ParserAppendChild(br); |
| } |
| current_ = tbody_; |
| } |
| |
| void HTMLViewSourceDocument::AddText(const String& text, |
| const AtomicString& class_name) { |
| if (text.IsEmpty()) |
| return; |
| |
| // Add in the content, splitting on newlines. |
| Vector<String> lines; |
| text.Split('\n', true, lines); |
| unsigned size = lines.size(); |
| for (unsigned i = 0; i < size; i++) { |
| String substring = lines[i]; |
| if (current_ == tbody_) |
| AddLine(class_name); |
| if (substring.IsEmpty()) { |
| if (i == size - 1) |
| break; |
| FinishLine(); |
| continue; |
| } |
| Element* old_element = current_; |
| current_->ParserAppendChild(Text::Create(*this, substring)); |
| current_ = old_element; |
| if (i < size - 1) |
| FinishLine(); |
| } |
| } |
| |
| int HTMLViewSourceDocument::AddRange(const String& source, |
| int start, |
| int end, |
| const AtomicString& class_name, |
| bool is_link, |
| bool is_anchor, |
| const AtomicString& link) { |
| DCHECK_LE(start, end); |
| if (start == end) |
| return start; |
| |
| String text = source.Substring(start, end - start); |
| if (!class_name.IsEmpty()) { |
| if (is_link) |
| current_ = AddLink(link, is_anchor); |
| else |
| current_ = AddSpanWithClassName(class_name); |
| } |
| AddText(text, class_name); |
| if (!class_name.IsEmpty() && current_ != tbody_) |
| current_ = To<Element>(current_->parentNode()); |
| return end; |
| } |
| |
| Element* HTMLViewSourceDocument::AddBase(const AtomicString& href) { |
| auto* base = MakeGarbageCollected<HTMLBaseElement>(*this); |
| base->setAttribute(html_names::kHrefAttr, href); |
| current_->ParserAppendChild(base); |
| return base; |
| } |
| |
| Element* HTMLViewSourceDocument::AddLink(const AtomicString& url, |
| bool is_anchor) { |
| if (current_ == tbody_) |
| AddLine("html-tag"); |
| |
| // Now create a link for the attribute value instead of a span. |
| auto* anchor = MakeGarbageCollected<HTMLAnchorElement>(*this); |
| const char* class_value; |
| if (is_anchor) |
| class_value = "html-attribute-value html-external-link"; |
| else |
| class_value = "html-attribute-value html-resource-link"; |
| anchor->setAttribute(html_names::kClassAttr, class_value); |
| anchor->setAttribute(html_names::kTargetAttr, "_blank"); |
| anchor->setAttribute(html_names::kHrefAttr, url); |
| anchor->setAttribute(html_names::kRelAttr, "noreferrer noopener"); |
| // Disallow JavaScript hrefs. https://crbug.com/808407 |
| if (anchor->Url().ProtocolIsJavaScript()) |
| anchor->setAttribute(html_names::kHrefAttr, "about:blank"); |
| current_->ParserAppendChild(anchor); |
| return anchor; |
| } |
| |
| int HTMLViewSourceDocument::AddSrcset(const String& source, |
| int start, |
| int end) { |
| String srcset = source.Substring(start, end - start); |
| Vector<String> srclist; |
| srcset.Split(',', true, srclist); |
| unsigned size = srclist.size(); |
| for (unsigned i = 0; i < size; i++) { |
| Vector<String> tmp; |
| srclist[i].Split(' ', tmp); |
| if (tmp.size() > 0) { |
| AtomicString link(tmp[0]); |
| current_ = AddLink(link, false); |
| AddText(srclist[i], "html-attribute-value"); |
| current_ = To<Element>(current_->parentNode()); |
| } else { |
| AddText(srclist[i], "html-attribute-value"); |
| } |
| if (i + 1 < size) |
| AddText(",", "html-attribute-value"); |
| } |
| return end; |
| } |
| |
| void HTMLViewSourceDocument::Trace(Visitor* visitor) const { |
| visitor->Trace(current_); |
| visitor->Trace(tbody_); |
| visitor->Trace(td_); |
| HTMLDocument::Trace(visitor); |
| } |
| |
| } // namespace blink |