blob: 22e0e7564052cde37de528c5a0154da4bdc2f8d2 [file] [log] [blame]
// 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 "webkit/glue/dom_operations.h"
#include <set>
#include "base/compiler_specific.h"
#include "base/string_number_conversions.h"
#include "base/string_split.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/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.isPasswordField() &&
(!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.isImageButton()) {
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 (!base::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;
base::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 GetMetaElementsWithAttribute(WebDocument* document,
const string16& attribute_name,
const string16& attribute_value,
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 value = element.getAttribute(attribute_name);
if (value.isNull() || value != attribute_value)
continue;
meta_elements->push_back(element);
}
}
} // webkit_glue