diff options
Diffstat (limited to 'webkit/glue/dom_operations.cc')
-rw-r--r-- | webkit/glue/dom_operations.cc | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/webkit/glue/dom_operations.cc b/webkit/glue/dom_operations.cc new file mode 100644 index 0000000..82e5e3a --- /dev/null +++ b/webkit/glue/dom_operations.cc @@ -0,0 +1,619 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <set> + +#include "base/compiler_specific.h" +#include "base/string_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebAnimationController.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFormElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNode.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNodeCollection.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNodeList.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/dom_operations.h" +#include "webkit/glue/form_data.h" +#include "webkit/glue/password_form_dom_manager.h" +#include "webkit/glue/webpasswordautocompletelistener_impl.h" + +using WebKit::WebAnimationController; +using WebKit::WebDocument; +using WebKit::WebElement; +using WebKit::WebFormElement; +using WebKit::WebFrame; +using WebKit::WebInputElement; +using WebKit::WebNode; +using WebKit::WebNodeCollection; +using WebKit::WebNodeList; +using WebKit::WebVector; +using WebKit::WebView; + +namespace { + +// Structure for storage the unique set of all savable resource links for +// making sure that no duplicated resource link in final result. The consumer +// of the SavableResourcesUniqueCheck is responsible for keeping these pointers +// valid for the lifetime of the SavableResourcesUniqueCheck instance. +struct SavableResourcesUniqueCheck { + // Unique set of all sub resource links. + std::set<GURL>* resources_set; + // Unique set of all frame links. + std::set<GURL>* frames_set; + // Collection of all frames we go through when getting all savable resource + // links. + std::vector<WebFrame*>* frames; + + SavableResourcesUniqueCheck() + : resources_set(NULL), + frames_set(NULL), + frames(NULL) {} + + SavableResourcesUniqueCheck(std::set<GURL>* resources_set, + std::set<GURL>* frames_set, std::vector<WebFrame*>* frames) + : resources_set(resources_set), + frames_set(frames_set), + frames(frames) {} +}; + +// Get all savable resource links from current element. One element might +// have more than one resource link. It is possible to have some links +// in one CSS stylesheet. +void GetSavableResourceLinkForElement( + const WebElement& element, + const WebDocument& current_doc, + SavableResourcesUniqueCheck* unique_check, + webkit_glue::SavableResourcesResult* result) { + + // Handle frame and iframe tag. + if (element.hasTagName("iframe") || + element.hasTagName("frame")) { + WebFrame* sub_frame = WebFrame::fromFrameOwnerElement(element); + if (sub_frame) + unique_check->frames->push_back(sub_frame); + return; + } + + // Check whether the node has sub resource URL or not. + WebString value = + webkit_glue::GetSubResourceLinkFromElement(element); + if (value.isNull()) + return; + // Get absolute URL. + GURL u = current_doc.completeURL(value); + // ignore invalid URL + if (!u.is_valid()) + return; + // Ignore those URLs which are not standard protocols. Because FTP + // protocol does no have cache mechanism, we will skip all + // sub-resources if they use FTP protocol. + if (!u.SchemeIs("http") && !u.SchemeIs("https") && !u.SchemeIs("file")) + return; + // Ignore duplicated resource link. + if (!unique_check->resources_set->insert(u).second) + return; + result->resources_list->push_back(u); + // Insert referrer for above new resource link. + result->referrers_list->push_back(GURL()); +} + +// Get all savable resource links from current WebFrameImpl object pointer. +void GetAllSavableResourceLinksForFrame(WebFrame* current_frame, + SavableResourcesUniqueCheck* unique_check, + webkit_glue::SavableResourcesResult* result, + const char** savable_schemes) { + // Get current frame's URL. + GURL current_frame_url = current_frame->url(); + + // If url of current frame is invalid, ignore it. + if (!current_frame_url.is_valid()) + return; + + // If url of current frame is not a savable protocol, ignore it. + bool is_valid_protocol = false; + for (int i = 0; savable_schemes[i] != NULL; ++i) { + if (current_frame_url.SchemeIs(savable_schemes[i])) { + is_valid_protocol = true; + break; + } + } + if (!is_valid_protocol) + return; + + // If find same frame we have recorded, ignore it. + if (!unique_check->frames_set->insert(current_frame_url).second) + return; + + // Get current using document. + WebDocument current_doc = current_frame->document(); + // Go through all descent nodes. + WebNodeCollection all = current_doc.all(); + // Go through all node in this frame. + for (WebNode node = all.firstItem(); !node.isNull(); + node = all.nextItem()) { + // We only save HTML resources. + if (!node.isElementNode()) + continue; + WebElement element = node.to<WebElement>(); + GetSavableResourceLinkForElement(element, + current_doc, + unique_check, + result); + } +} + +} // namespace + +namespace webkit_glue { + +// Map element name to a list of pointers to corresponding elements to simplify +// form filling. +typedef std::map<string16, WebKit::WebInputElement> + FormInputElementMap; + +// Utility struct for form lookup and autofill. When we parse the DOM to lookup +// a form, in addition to action and origin URL's we have to compare all +// necessary form elements. To avoid having to look these up again when we want +// to fill the form, the FindFormElements function stores the pointers +// in a FormElements* result, referenced to ensure they are safe to use. +struct FormElements { + WebFormElement form_element; + FormInputElementMap input_elements; + FormElements() { + } +}; + +typedef std::vector<FormElements*> FormElementsList; + +// Internal implementation of FillForm API. +static bool FillFormImpl(FormElements* fe, const FormData& data) { + if (!fe->form_element.autoComplete()) + return false; + + std::map<string16, string16> data_map; + for (size_t i = 0; i < data.fields.size(); i++) + data_map[data.fields[i].name()] = data.fields[i].value(); + + for (FormInputElementMap::iterator it = fe->input_elements.begin(); + it != fe->input_elements.end(); ++it) { + WebKit::WebInputElement& element = it->second; + if (!element.value().isEmpty()) // Don't overwrite pre-filled values. + continue; + if (element.inputType() == WebInputElement::Password && + (!element.isEnabledFormControl() || element.hasAttribute("readonly"))) { + continue; // Don't fill uneditable password fields. + } + element.setValue(data_map[it->first]); + element.setAutofilled(true); + element.dispatchFormControlChangeEvent(); + } + + return false; +} + +// Helper to search the given form element for the specified input elements +// in |data|, and add results to |result|. +static bool FindFormInputElements(WebFormElement* fe, + const FormData& data, + FormElements* result) { + // Loop through the list of elements we need to find on the form in + // order to autofill it. If we don't find any one of them, abort + // processing this form; it can't be the right one. + for (size_t j = 0; j < data.fields.size(); j++) { + WebVector<WebNode> temp_elements; + fe->getNamedElements(data.fields[j].name(), temp_elements); + if (temp_elements.isEmpty()) { + // We didn't find a required element. This is not the right form. + // Make sure no input elements from a partially matched form + // in this iteration remain in the result set. + // Note: clear will remove a reference from each InputElement. + result->input_elements.clear(); + return false; + } + // This element matched, add it to our temporary result. It's possible + // there are multiple matches, but for purposes of identifying the form + // one suffices and if some function needs to deal with multiple + // matching elements it can get at them through the FormElement*. + // Note: This assignment adds a reference to the InputElement. + result->input_elements[data.fields[j].name()] = + temp_elements[0].to<WebInputElement>(); + } + return true; +} + +// Helper to locate form elements identified by |data|. +static void FindFormElements(WebView* view, + const FormData& data, + FormElementsList* results) { + DCHECK(view); + DCHECK(results); + WebFrame* main_frame = view->mainFrame(); + if (!main_frame) + return; + + GURL::Replacements rep; + rep.ClearQuery(); + rep.ClearRef(); + + // Loop through each frame. + for (WebFrame* f = main_frame; f; f = f->traverseNext(false)) { + WebDocument doc = f->document(); + if (!doc.isHTMLDocument()) + continue; + + GURL full_origin(f->url()); + if (data.origin != full_origin.ReplaceComponents(rep)) + continue; + + WebVector<WebFormElement> forms; + f->forms(forms); + + for (size_t i = 0; i < forms.size(); ++i) { + WebFormElement fe = forms[i]; + // Action URL must match. + GURL full_action(f->document().completeURL(fe.action())); + if (data.action != full_action.ReplaceComponents(rep)) + continue; + + scoped_ptr<FormElements> curr_elements(new FormElements); + if (!FindFormInputElements(&fe, data, curr_elements.get())) + continue; + + // We found the right element. + // Note: this assignment adds a reference to |fe|. + curr_elements->form_element = fe; + results->push_back(curr_elements.release()); + } + } +} + +void FillPasswordForm(WebView* view, + const PasswordFormFillData& data) { + FormElementsList forms; + // We own the FormElements* in forms. + FindFormElements(view, data.basic_data, &forms); + FormElementsList::iterator iter; + for (iter = forms.begin(); iter != forms.end(); ++iter) { + scoped_ptr<FormElements> form_elements(*iter); + + // If wait_for_username is true, we don't want to initially fill the form + // until the user types in a valid username. + if (!data.wait_for_username) + FillFormImpl(form_elements.get(), data.basic_data); + + // Attach autocomplete listener to enable selecting alternate logins. + // First, get pointers to username element. + WebInputElement username_element = + form_elements->input_elements[data.basic_data.fields[0].name()]; + + // Get pointer to password element. (We currently only support single + // password forms). + WebInputElement password_element = + form_elements->input_elements[data.basic_data.fields[1].name()]; + + username_element.document().frame()->registerPasswordListener( + username_element, + new WebPasswordAutocompleteListenerImpl( + new WebInputElementDelegate(username_element), + new WebInputElementDelegate(password_element), + data)); + } +} + +WebString GetSubResourceLinkFromElement(const WebElement& element) { + const char* attribute_name = NULL; + if (element.hasTagName("img") || + element.hasTagName("script")) { + attribute_name = "src"; + } else if (element.hasTagName("input")) { + const WebInputElement input = element.toConst<WebInputElement>(); + if (input.inputType() == WebInputElement::Image) { + attribute_name = "src"; + } + } else if (element.hasTagName("body") || + element.hasTagName("table") || + element.hasTagName("tr") || + element.hasTagName("td")) { + attribute_name = "background"; + } else if (element.hasTagName("blockquote") || + element.hasTagName("q") || + element.hasTagName("del") || + element.hasTagName("ins")) { + attribute_name = "cite"; + } else if (element.hasTagName("link")) { + // If the link element is not linked to css, ignore it. + if (LowerCaseEqualsASCII(element.getAttribute("type"), "text/css")) { + // TODO(jnd): Add support for extracting links of sub-resources which + // are inside style-sheet such as @import, url(), etc. + // See bug: http://b/issue?id=1111667. + attribute_name = "href"; + } + } + if (!attribute_name) + return WebString(); + WebString value = element.getAttribute(WebString::fromUTF8(attribute_name)); + // If value has content and not start with "javascript:" then return it, + // otherwise return NULL. + if (!value.isNull() && !value.isEmpty() && + !StartsWithASCII(value.utf8(), "javascript:", false)) + return value; + + return WebString(); +} + +// Get all savable resource links from current webview, include main +// frame and sub-frame +bool GetAllSavableResourceLinksForCurrentPage(WebView* view, + const GURL& page_url, SavableResourcesResult* result, + const char** savable_schemes) { + WebFrame* main_frame = view->mainFrame(); + if (!main_frame) + return false; + + std::set<GURL> resources_set; + std::set<GURL> frames_set; + std::vector<WebFrame*> frames; + SavableResourcesUniqueCheck unique_check(&resources_set, + &frames_set, + &frames); + + GURL main_page_gurl(main_frame->url()); + + // Make sure we are saving same page between embedder and webkit. + // If page has being navigated, embedder will get three empty vector, + // which will make the saving page job ended. + if (page_url != main_page_gurl) + return true; + + // First, process main frame. + frames.push_back(main_frame); + + // Check all resource in this page, include sub-frame. + for (int i = 0; i < static_cast<int>(frames.size()); ++i) { + // Get current frame's all savable resource links. + GetAllSavableResourceLinksForFrame(frames[i], &unique_check, result, + savable_schemes); + } + + // Since frame's src can also point to sub-resources link, so it is possible + // that some URLs in frames_list are also in resources_list. For those + // URLs, we will remove it from frame_list, only keep them in resources_list. + for (std::set<GURL>::iterator it = frames_set.begin(); + it != frames_set.end(); ++it) { + // Append unique frame source to savable frame list. + if (resources_set.find(*it) == resources_set.end()) + result->frames_list->push_back(*it); + } + + return true; +} + +// Sizes a single size (the width or height) from a 'sizes' attribute. A size +// matches must match the following regex: [1-9][0-9]*. +static int ParseSingleIconSize(const string16& text) { + // Size must not start with 0, and be between 0 and 9. + if (text.empty() || !(text[0] >= L'1' && text[0] <= L'9')) + return 0; + // Make sure all chars are from 0-9. + for (size_t i = 1; i < text.length(); ++i) { + if (!(text[i] >= L'0' && text[i] <= L'9')) + return 0; + } + int output; + if (!StringToInt(text, &output)) + return 0; + return output; +} + +// Parses an icon size. An icon size must match the following regex: +// [1-9][0-9]*x[1-9][0-9]*. +// If the input couldn't be parsed, a size with a width/height < 0 is returned. +static gfx::Size ParseIconSize(const string16& text) { + std::vector<string16> sizes; + SplitStringDontTrim(text, L'x', &sizes); + if (sizes.size() != 2) + return gfx::Size(); + + return gfx::Size(ParseSingleIconSize(sizes[0]), + ParseSingleIconSize(sizes[1])); +} + +bool ParseIconSizes(const string16& text, + std::vector<gfx::Size>* sizes, + bool* is_any) { + *is_any = false; + std::vector<string16> size_strings; + SplitStringAlongWhitespace(text, &size_strings); + for (size_t i = 0; i < size_strings.size(); ++i) { + if (EqualsASCII(size_strings[i], "any")) { + *is_any = true; + } else { + gfx::Size size = ParseIconSize(size_strings[i]); + if (size.width() <= 0 || size.height() <= 0) + return false; // Bogus size. + sizes->push_back(size); + } + } + if (*is_any && !sizes->empty()) { + // If is_any is true, it must occur by itself. + return false; + } + return (*is_any || !sizes->empty()); +} + +static void AddInstallIcon(const WebElement& link, + std::vector<WebApplicationInfo::IconInfo>* icons) { + WebString href = link.getAttribute("href"); + if (href.isNull() || href.isEmpty()) + return; + + // Get complete url. + GURL url = link.document().completeURL(href); + if (!url.is_valid()) + return; + + if (!link.hasAttribute("sizes")) + return; + + bool is_any = false; + std::vector<gfx::Size> icon_sizes; + if (!ParseIconSizes(link.getAttribute("sizes"), &icon_sizes, &is_any) || + is_any || + icon_sizes.size() != 1) { + return; + } + WebApplicationInfo::IconInfo icon_info; + icon_info.width = icon_sizes[0].width(); + icon_info.height = icon_sizes[0].height(); + icon_info.url = url; + icons->push_back(icon_info); +} + +void GetApplicationInfo(WebView* view, WebApplicationInfo* app_info) { + WebFrame* main_frame = view->mainFrame(); + if (!main_frame) + return; + + WebDocument doc = main_frame->document(); + if (doc.isNull()) + return; + + WebElement head = main_frame->document().head(); + if (head.isNull()) + return; + + WebNodeList children = head.childNodes(); + for (unsigned i = 0; i < children.length(); ++i) { + WebNode child = children.item(i); + if (!child.isElementNode()) + continue; + WebElement elem = child.to<WebElement>(); + + if (elem.hasTagName("link")) { + std::string rel = elem.getAttribute("rel").utf8(); + // "rel" attribute may use either "icon" or "shortcut icon". + // see also + // <http://en.wikipedia.org/wiki/Favicon> + // <http://dev.w3.org/html5/spec/Overview.html#rel-icon> + if (LowerCaseEqualsASCII(rel, "icon") || + LowerCaseEqualsASCII(rel, "shortcut icon")) + AddInstallIcon(elem, &app_info->icons); + } else if (elem.hasTagName("meta") && elem.hasAttribute("name")) { + std::string name = elem.getAttribute("name").utf8(); + WebString content = elem.getAttribute("content"); + if (name == "application-name") { + app_info->title = content; + } else if (name == "description") { + app_info->description = content; + } else if (name == "application-url") { + std::string url = content.utf8(); + GURL main_url = main_frame->url(); + app_info->app_url = main_url.is_valid() ? + main_url.Resolve(url) : GURL(url); + if (!app_info->app_url.is_valid()) + app_info->app_url = GURL(); + } + } + } +} + +bool PauseAnimationAtTimeOnElementWithId(WebView* view, + const std::string& animation_name, + double time, + const std::string& element_id) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return false; + + WebAnimationController* controller = web_frame->animationController(); + if (!controller) + return false; + + WebElement element = + web_frame->document().getElementById(WebString::fromUTF8(element_id)); + if (element.isNull()) + return false; + return controller->pauseAnimationAtTime(element, + WebString::fromUTF8(animation_name), + time); +} + +bool PauseTransitionAtTimeOnElementWithId(WebView* view, + const std::string& property_name, + double time, + const std::string& element_id) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return false; + + WebAnimationController* controller = web_frame->animationController(); + if (!controller) + return false; + + WebElement element = + web_frame->document().getElementById(WebString::fromUTF8(element_id)); + if (element.isNull()) + return false; + return controller->pauseTransitionAtTime(element, + WebString::fromUTF8(property_name), + time); +} + +bool ElementDoesAutoCompleteForElementWithId(WebView* view, + const std::string& element_id) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return false; + + WebElement element = web_frame->document().getElementById( + WebString::fromUTF8(element_id)); + if (element.isNull() || !element.hasTagName("input")) + return false; + + WebInputElement input_element = element.to<WebInputElement>(); + return input_element.autoComplete(); +} + +int NumberOfActiveAnimations(WebView* view) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return -1; + + WebAnimationController* controller = web_frame->animationController(); + if (!controller) + return -1; + + return controller->numberOfActiveAnimations(); +} + +void GetMetaElementsWithName(WebDocument* document, + const string16& name, + std::vector<WebElement>* meta_elements) { + DCHECK(document); + DCHECK(meta_elements); + meta_elements->clear(); + WebElement head = document->head(); + if (head.isNull() || !head.hasChildNodes()) + return; + + WebNodeList children = head.childNodes(); + for (size_t i = 0; i < children.length(); ++i) { + WebNode node = children.item(i); + if (!node.isElementNode()) + continue; + WebElement element = node.to<WebElement>(); + if (!element.hasTagName("meta")) + continue; + WebString meta_name = element.getAttribute("name"); + if (meta_name.isNull() || meta_name != name) + continue; + meta_elements->push_back(element); + } +} + +} // webkit_glue |