1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "webkit/glue/dom_operations.h"
6
7#include <set>
8
9#include "base/compiler_specific.h"
10#include "base/logging.h"
11#include "base/string_util.h"
12#include "third_party/WebKit/Source/WebKit/chromium/public/WebAnimationController.h"
13#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
14#include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h"
15#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
16#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h"
17#include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h"
18#include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeCollection.h"
19#include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeList.h"
20#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
21#include "third_party/WebKit/Source/WebKit/chromium/public/WebVector.h"
22#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
23
24using WebKit::WebAnimationController;
25using WebKit::WebDocument;
26using WebKit::WebElement;
27using WebKit::WebFrame;
28using WebKit::WebInputElement;
29using WebKit::WebNode;
30using WebKit::WebNodeCollection;
31using WebKit::WebNodeList;
32using WebKit::WebString;
33using WebKit::WebVector;
34using WebKit::WebView;
35
36namespace {
37
38// Structure for storage the unique set of all savable resource links for
39// making sure that no duplicated resource link in final result. The consumer
40// of the SavableResourcesUniqueCheck is responsible for keeping these pointers
41// valid for the lifetime of the SavableResourcesUniqueCheck instance.
42struct SavableResourcesUniqueCheck {
43  // Unique set of all sub resource links.
44  std::set<GURL>* resources_set;
45  // Unique set of all frame links.
46  std::set<GURL>* frames_set;
47  // Collection of all frames we go through when getting all savable resource
48  // links.
49  std::vector<WebFrame*>* frames;
50
51  SavableResourcesUniqueCheck()
52      : resources_set(NULL),
53        frames_set(NULL),
54        frames(NULL) {}
55
56  SavableResourcesUniqueCheck(std::set<GURL>* resources_set,
57      std::set<GURL>* frames_set, std::vector<WebFrame*>* frames)
58      : resources_set(resources_set),
59        frames_set(frames_set),
60        frames(frames) {}
61};
62
63// Get all savable resource links from current element. One element might
64// have more than one resource link. It is possible to have some links
65// in one CSS stylesheet.
66void GetSavableResourceLinkForElement(
67    const WebElement& element,
68    const WebDocument& current_doc,
69    SavableResourcesUniqueCheck* unique_check,
70    webkit_glue::SavableResourcesResult* result) {
71
72  // Handle frame and iframe tag.
73  if (element.hasTagName("iframe") ||
74      element.hasTagName("frame")) {
75    WebFrame* sub_frame = WebFrame::fromFrameOwnerElement(element);
76    if (sub_frame)
77      unique_check->frames->push_back(sub_frame);
78    return;
79  }
80
81  // Check whether the node has sub resource URL or not.
82  WebString value =
83      webkit_glue::GetSubResourceLinkFromElement(element);
84  if (value.isNull())
85    return;
86  // Get absolute URL.
87  GURL u = current_doc.completeURL(value);
88  // ignore invalid URL
89  if (!u.is_valid())
90    return;
91  // Ignore those URLs which are not standard protocols. Because FTP
92  // protocol does no have cache mechanism, we will skip all
93  // sub-resources if they use FTP protocol.
94  if (!u.SchemeIs("http") && !u.SchemeIs("https") && !u.SchemeIs("file"))
95    return;
96  // Ignore duplicated resource link.
97  if (!unique_check->resources_set->insert(u).second)
98    return;
99  result->resources_list->push_back(u);
100  // Insert referrer for above new resource link.
101  result->referrers_list->push_back(GURL());
102}
103
104// Get all savable resource links from current WebFrameImpl object pointer.
105void GetAllSavableResourceLinksForFrame(WebFrame* current_frame,
106    SavableResourcesUniqueCheck* unique_check,
107    webkit_glue::SavableResourcesResult* result,
108    const char** savable_schemes) {
109  // Get current frame's URL.
110  GURL current_frame_url = current_frame->url();
111
112  // If url of current frame is invalid, ignore it.
113  if (!current_frame_url.is_valid())
114    return;
115
116  // If url of current frame is not a savable protocol, ignore it.
117  bool is_valid_protocol = false;
118  for (int i = 0; savable_schemes[i] != NULL; ++i) {
119    if (current_frame_url.SchemeIs(savable_schemes[i])) {
120      is_valid_protocol = true;
121      break;
122    }
123  }
124  if (!is_valid_protocol)
125    return;
126
127  // If find same frame we have recorded, ignore it.
128  if (!unique_check->frames_set->insert(current_frame_url).second)
129    return;
130
131  // Get current using document.
132  WebDocument current_doc = current_frame->document();
133  // Go through all descent nodes.
134  WebNodeCollection all = current_doc.all();
135  // Go through all node in this frame.
136  for (WebNode node = all.firstItem(); !node.isNull();
137       node = all.nextItem()) {
138    // We only save HTML resources.
139    if (!node.isElementNode())
140      continue;
141    WebElement element = node.to<WebElement>();
142    GetSavableResourceLinkForElement(element,
143                                     current_doc,
144                                     unique_check,
145                                     result);
146  }
147}
148
149}  // namespace
150
151namespace webkit_glue {
152
153WebString GetSubResourceLinkFromElement(const WebElement& element) {
154  const char* attribute_name = NULL;
155  if (element.hasTagName("img") ||
156      element.hasTagName("script")) {
157    attribute_name = "src";
158  } else if (element.hasTagName("input")) {
159    const WebInputElement input = element.toConst<WebInputElement>();
160    if (input.isImageButton()) {
161      attribute_name = "src";
162    }
163  } else if (element.hasTagName("body") ||
164             element.hasTagName("table") ||
165             element.hasTagName("tr") ||
166             element.hasTagName("td")) {
167    attribute_name = "background";
168  } else if (element.hasTagName("blockquote") ||
169             element.hasTagName("q") ||
170             element.hasTagName("del") ||
171             element.hasTagName("ins")) {
172    attribute_name = "cite";
173  } else if (element.hasTagName("link")) {
174    // If the link element is not linked to css, ignore it.
175    if (LowerCaseEqualsASCII(element.getAttribute("type"), "text/css")) {
176      // TODO(jnd): Add support for extracting links of sub-resources which
177      // are inside style-sheet such as @import, url(), etc.
178      // See bug: http://b/issue?id=1111667.
179      attribute_name = "href";
180    }
181  }
182  if (!attribute_name)
183    return WebString();
184  WebString value = element.getAttribute(WebString::fromUTF8(attribute_name));
185  // If value has content and not start with "javascript:" then return it,
186  // otherwise return NULL.
187  if (!value.isNull() && !value.isEmpty() &&
188      !StartsWithASCII(value.utf8(), "javascript:", false))
189    return value;
190
191  return WebString();
192}
193
194// Get all savable resource links from current webview, include main
195// frame and sub-frame
196bool GetAllSavableResourceLinksForCurrentPage(WebView* view,
197    const GURL& page_url, SavableResourcesResult* result,
198    const char** savable_schemes) {
199  WebFrame* main_frame = view->mainFrame();
200  if (!main_frame)
201    return false;
202
203  std::set<GURL> resources_set;
204  std::set<GURL> frames_set;
205  std::vector<WebFrame*> frames;
206  SavableResourcesUniqueCheck unique_check(&resources_set,
207                                           &frames_set,
208                                           &frames);
209
210  GURL main_page_gurl(main_frame->url());
211
212  // Make sure we are saving same page between embedder and webkit.
213  // If page has being navigated, embedder will get three empty vector,
214  // which will make the saving page job ended.
215  if (page_url != main_page_gurl)
216    return true;
217
218  // First, process main frame.
219  frames.push_back(main_frame);
220
221  // Check all resource in this page, include sub-frame.
222  for (int i = 0; i < static_cast<int>(frames.size()); ++i) {
223    // Get current frame's all savable resource links.
224    GetAllSavableResourceLinksForFrame(frames[i], &unique_check, result,
225                                       savable_schemes);
226  }
227
228  // Since frame's src can also point to sub-resources link, so it is possible
229  // that some URLs in frames_list are also in resources_list. For those
230  // URLs, we will remove it from frame_list, only keep them in resources_list.
231  for (std::set<GURL>::iterator it = frames_set.begin();
232       it != frames_set.end(); ++it) {
233    // Append unique frame source to savable frame list.
234    if (resources_set.find(*it) == resources_set.end())
235      result->frames_list->push_back(*it);
236  }
237
238  return true;
239}
240
241bool PauseAnimationAtTimeOnElementWithId(WebView* view,
242                                         const std::string& animation_name,
243                                         double time,
244                                         const std::string& element_id) {
245  WebFrame* web_frame = view->mainFrame();
246  if (!web_frame)
247    return false;
248
249  WebAnimationController* controller = web_frame->animationController();
250  if (!controller)
251    return false;
252
253  WebElement element =
254    web_frame->document().getElementById(WebString::fromUTF8(element_id));
255  if (element.isNull())
256    return false;
257  return controller->pauseAnimationAtTime(element,
258                                          WebString::fromUTF8(animation_name),
259                                          time);
260}
261
262bool PauseTransitionAtTimeOnElementWithId(WebView* view,
263                                          const std::string& property_name,
264                                          double time,
265                                          const std::string& element_id) {
266  WebFrame* web_frame = view->mainFrame();
267  if (!web_frame)
268    return false;
269
270  WebAnimationController* controller = web_frame->animationController();
271  if (!controller)
272    return false;
273
274  WebElement element =
275      web_frame->document().getElementById(WebString::fromUTF8(element_id));
276  if (element.isNull())
277    return false;
278  return controller->pauseTransitionAtTime(element,
279                                           WebString::fromUTF8(property_name),
280                                           time);
281}
282
283bool ElementDoesAutoCompleteForElementWithId(WebView* view,
284                                             const std::string& element_id) {
285  WebFrame* web_frame = view->mainFrame();
286  if (!web_frame)
287    return false;
288
289  WebElement element = web_frame->document().getElementById(
290      WebString::fromUTF8(element_id));
291  if (element.isNull() || !element.hasTagName("input"))
292    return false;
293
294  WebInputElement input_element = element.to<WebInputElement>();
295  return input_element.autoComplete();
296}
297
298int NumberOfActiveAnimations(WebView* view) {
299  WebFrame* web_frame = view->mainFrame();
300  if (!web_frame)
301    return -1;
302
303  WebAnimationController* controller = web_frame->animationController();
304  if (!controller)
305    return -1;
306
307  return controller->numberOfActiveAnimations();
308}
309
310void GetMetaElementsWithAttribute(WebDocument* document,
311                                  const string16& attribute_name,
312                                  const string16& attribute_value,
313                                  std::vector<WebElement>* meta_elements) {
314  DCHECK(document);
315  DCHECK(meta_elements);
316  meta_elements->clear();
317  WebElement head = document->head();
318  if (head.isNull() || !head.hasChildNodes())
319    return;
320
321  WebNodeList children = head.childNodes();
322  for (size_t i = 0; i < children.length(); ++i) {
323    WebNode node = children.item(i);
324    if (!node.isElementNode())
325      continue;
326    WebElement element = node.to<WebElement>();
327    if (!element.hasTagName("meta"))
328      continue;
329    WebString value = element.getAttribute(attribute_name);
330    if (value.isNull() || value != attribute_value)
331      continue;
332    meta_elements->push_back(element);
333  }
334}
335
336}  // webkit_glue
337