1 // Copyright (c) 2012 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 "chrome/renderer/chrome_render_view_observer.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/command_line.h" 10 #include "base/debug/trace_event.h" 11 #include "base/message_loop/message_loop.h" 12 #include "base/metrics/histogram.h" 13 #include "base/strings/string_split.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "chrome/common/chrome_constants.h" 17 #include "chrome/common/chrome_switches.h" 18 #include "chrome/common/prerender_messages.h" 19 #include "chrome/common/render_messages.h" 20 #include "chrome/common/url_constants.h" 21 #include "chrome/renderer/chrome_render_process_observer.h" 22 #include "chrome/renderer/external_host_bindings.h" 23 #include "chrome/renderer/prerender/prerender_helper.h" 24 #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h" 25 #include "chrome/renderer/translate/translate_helper.h" 26 #include "chrome/renderer/webview_color_overlay.h" 27 #include "content/public/common/bindings_policy.h" 28 #include "content/public/renderer/content_renderer_client.h" 29 #include "content/public/renderer/render_frame.h" 30 #include "content/public/renderer/render_view.h" 31 #include "extensions/common/constants.h" 32 #include "extensions/common/stack_frame.h" 33 #include "net/base/data_url.h" 34 #include "skia/ext/image_operations.h" 35 #include "skia/ext/platform_canvas.h" 36 #include "third_party/WebKit/public/platform/WebCString.h" 37 #include "third_party/WebKit/public/platform/WebRect.h" 38 #include "third_party/WebKit/public/platform/WebSize.h" 39 #include "third_party/WebKit/public/platform/WebString.h" 40 #include "third_party/WebKit/public/platform/WebURLRequest.h" 41 #include "third_party/WebKit/public/platform/WebVector.h" 42 #include "third_party/WebKit/public/web/WebAXObject.h" 43 #include "third_party/WebKit/public/web/WebDataSource.h" 44 #include "third_party/WebKit/public/web/WebDocument.h" 45 #include "third_party/WebKit/public/web/WebElement.h" 46 #include "third_party/WebKit/public/web/WebFrame.h" 47 #include "third_party/WebKit/public/web/WebInputEvent.h" 48 #include "third_party/WebKit/public/web/WebNode.h" 49 #include "third_party/WebKit/public/web/WebNodeList.h" 50 #include "third_party/WebKit/public/web/WebView.h" 51 #include "ui/base/ui_base_switches_util.h" 52 #include "ui/gfx/favicon_size.h" 53 #include "ui/gfx/size.h" 54 #include "ui/gfx/size_f.h" 55 #include "ui/gfx/skbitmap_operations.h" 56 #include "v8/include/v8-testing.h" 57 58 using blink::WebAXObject; 59 using blink::WebCString; 60 using blink::WebDataSource; 61 using blink::WebDocument; 62 using blink::WebElement; 63 using blink::WebFrame; 64 using blink::WebGestureEvent; 65 using blink::WebIconURL; 66 using blink::WebNode; 67 using blink::WebNodeList; 68 using blink::WebRect; 69 using blink::WebSecurityOrigin; 70 using blink::WebSize; 71 using blink::WebString; 72 using blink::WebTouchEvent; 73 using blink::WebURL; 74 using blink::WebURLRequest; 75 using blink::WebView; 76 using blink::WebVector; 77 using blink::WebWindowFeatures; 78 79 // Delay in milliseconds that we'll wait before capturing the page contents 80 // and thumbnail. 81 static const int kDelayForCaptureMs = 500; 82 83 // Typically, we capture the page data once the page is loaded. 84 // Sometimes, the page never finishes to load, preventing the page capture 85 // To workaround this problem, we always perform a capture after the following 86 // delay. 87 static const int kDelayForForcedCaptureMs = 6000; 88 89 // define to write the time necessary for thumbnail/DOM text retrieval, 90 // respectively, into the system debug log 91 // #define TIME_TEXT_RETRIEVAL 92 93 // maximum number of characters in the document to index, any text beyond this 94 // point will be clipped 95 static const size_t kMaxIndexChars = 65535; 96 97 // Constants for UMA statistic collection. 98 static const char kTranslateCaptureText[] = "Translate.CaptureText"; 99 100 namespace { 101 102 GURL StripRef(const GURL& url) { 103 GURL::Replacements replacements; 104 replacements.ClearRef(); 105 return url.ReplaceComponents(replacements); 106 } 107 108 // If the source image is null or occupies less area than 109 // |thumbnail_min_area_pixels|, we return the image unmodified. Otherwise, we 110 // scale down the image so that the width and height do not exceed 111 // |thumbnail_max_size_pixels|, preserving the original aspect ratio. 112 SkBitmap Downscale(blink::WebImage image, 113 int thumbnail_min_area_pixels, 114 gfx::Size thumbnail_max_size_pixels) { 115 if (image.isNull()) 116 return SkBitmap(); 117 118 gfx::Size image_size = image.size(); 119 120 if (image_size.GetArea() < thumbnail_min_area_pixels) 121 return image.getSkBitmap(); 122 123 if (image_size.width() <= thumbnail_max_size_pixels.width() && 124 image_size.height() <= thumbnail_max_size_pixels.height()) 125 return image.getSkBitmap(); 126 127 gfx::SizeF scaled_size = image_size; 128 129 if (scaled_size.width() > thumbnail_max_size_pixels.width()) { 130 scaled_size.Scale(thumbnail_max_size_pixels.width() / scaled_size.width()); 131 } 132 133 if (scaled_size.height() > thumbnail_max_size_pixels.height()) { 134 scaled_size.Scale( 135 thumbnail_max_size_pixels.height() / scaled_size.height()); 136 } 137 138 return skia::ImageOperations::Resize(image.getSkBitmap(), 139 skia::ImageOperations::RESIZE_GOOD, 140 static_cast<int>(scaled_size.width()), 141 static_cast<int>(scaled_size.height())); 142 } 143 144 // The delimiter for a stack trace provided by WebKit. 145 const char kStackFrameDelimiter[] = "\n at "; 146 147 // Get a stack trace from a WebKit console message. 148 // There are three possible scenarios: 149 // 1. WebKit gives us a stack trace in |stack_trace|. 150 // 2. The stack trace is embedded in the error |message| by an internal 151 // script. This will be more useful than |stack_trace|, since |stack_trace| 152 // will include the internal bindings trace, instead of a developer's code. 153 // 3. No stack trace is included. In this case, we should mock one up from 154 // the given line number and source. 155 // |message| will be populated with the error message only (i.e., will not 156 // include any stack trace). 157 extensions::StackTrace GetStackTraceFromMessage( 158 base::string16* message, 159 const base::string16& source, 160 const base::string16& stack_trace, 161 int32 line_number) { 162 extensions::StackTrace result; 163 std::vector<base::string16> pieces; 164 size_t index = 0; 165 166 if (message->find(base::UTF8ToUTF16(kStackFrameDelimiter)) != 167 base::string16::npos) { 168 base::SplitStringUsingSubstr(*message, 169 base::UTF8ToUTF16(kStackFrameDelimiter), 170 &pieces); 171 *message = pieces[0]; 172 index = 1; 173 } else if (!stack_trace.empty()) { 174 base::SplitStringUsingSubstr(stack_trace, 175 base::UTF8ToUTF16(kStackFrameDelimiter), 176 &pieces); 177 } 178 179 // If we got a stack trace, parse each frame from the text. 180 if (index < pieces.size()) { 181 for (; index < pieces.size(); ++index) { 182 scoped_ptr<extensions::StackFrame> frame = 183 extensions::StackFrame::CreateFromText(pieces[index]); 184 if (frame.get()) 185 result.push_back(*frame); 186 } 187 } 188 189 if (result.empty()) { // If we don't have a stack trace, mock one up. 190 result.push_back( 191 extensions::StackFrame(line_number, 192 1u, // column number 193 source, 194 base::string16() /* no function name */ )); 195 } 196 197 return result; 198 } 199 200 } // namespace 201 202 ChromeRenderViewObserver::ChromeRenderViewObserver( 203 content::RenderView* render_view, 204 ChromeRenderProcessObserver* chrome_render_process_observer) 205 : content::RenderViewObserver(render_view), 206 chrome_render_process_observer_(chrome_render_process_observer), 207 translate_helper_(new TranslateHelper(render_view)), 208 phishing_classifier_(NULL), 209 last_indexed_page_id_(-1), 210 capture_timer_(false, false) { 211 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); 212 if (!command_line.HasSwitch(switches::kDisableClientSidePhishingDetection)) 213 OnSetClientSidePhishingDetection(true); 214 } 215 216 ChromeRenderViewObserver::~ChromeRenderViewObserver() { 217 } 218 219 bool ChromeRenderViewObserver::OnMessageReceived(const IPC::Message& message) { 220 bool handled = true; 221 IPC_BEGIN_MESSAGE_MAP(ChromeRenderViewObserver, message) 222 IPC_MESSAGE_HANDLER(ChromeViewMsg_WebUIJavaScript, OnWebUIJavaScript) 223 IPC_MESSAGE_HANDLER(ChromeViewMsg_HandleMessageFromExternalHost, 224 OnHandleMessageFromExternalHost) 225 IPC_MESSAGE_HANDLER(ChromeViewMsg_JavaScriptStressTestControl, 226 OnJavaScriptStressTestControl) 227 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetClientSidePhishingDetection, 228 OnSetClientSidePhishingDetection) 229 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetVisuallyDeemphasized, 230 OnSetVisuallyDeemphasized) 231 IPC_MESSAGE_HANDLER(ChromeViewMsg_RequestThumbnailForContextNode, 232 OnRequestThumbnailForContextNode) 233 IPC_MESSAGE_HANDLER(ChromeViewMsg_GetFPS, OnGetFPS) 234 #if defined(OS_ANDROID) 235 IPC_MESSAGE_HANDLER(ChromeViewMsg_UpdateTopControlsState, 236 OnUpdateTopControlsState) 237 IPC_MESSAGE_HANDLER(ChromeViewMsg_RetrieveWebappInformation, 238 OnRetrieveWebappInformation) 239 #endif 240 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetWindowFeatures, OnSetWindowFeatures) 241 IPC_MESSAGE_UNHANDLED(handled = false) 242 IPC_END_MESSAGE_MAP() 243 244 return handled; 245 } 246 247 void ChromeRenderViewObserver::OnWebUIJavaScript( 248 const base::string16& frame_xpath, 249 const base::string16& jscript, 250 int id, 251 bool notify_result) { 252 webui_javascript_.reset(new WebUIJavaScript()); 253 webui_javascript_->frame_xpath = frame_xpath; 254 webui_javascript_->jscript = jscript; 255 webui_javascript_->id = id; 256 webui_javascript_->notify_result = notify_result; 257 } 258 259 void ChromeRenderViewObserver::OnHandleMessageFromExternalHost( 260 const std::string& message, 261 const std::string& origin, 262 const std::string& target) { 263 if (message.empty()) 264 return; 265 GetExternalHostBindings()->ForwardMessageFromExternalHost(message, origin, 266 target); 267 } 268 269 void ChromeRenderViewObserver::OnJavaScriptStressTestControl(int cmd, 270 int param) { 271 if (cmd == kJavaScriptStressTestSetStressRunType) { 272 v8::Testing::SetStressRunType(static_cast<v8::Testing::StressType>(param)); 273 } else if (cmd == kJavaScriptStressTestPrepareStressRun) { 274 v8::Testing::PrepareStressRun(param); 275 } 276 } 277 278 #if defined(OS_ANDROID) 279 void ChromeRenderViewObserver::OnUpdateTopControlsState( 280 content::TopControlsState constraints, 281 content::TopControlsState current, 282 bool animate) { 283 render_view()->UpdateTopControlsState(constraints, current, animate); 284 } 285 286 void ChromeRenderViewObserver::OnRetrieveWebappInformation( 287 const GURL& expected_url) { 288 WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); 289 WebDocument document = 290 main_frame ? main_frame->document() : WebDocument(); 291 292 WebElement head = document.isNull() ? WebElement() : document.head(); 293 GURL document_url = document.isNull() ? GURL() : GURL(document.url()); 294 295 // Make sure we're checking the right page. 296 bool success = document_url == expected_url; 297 298 bool is_mobile_webapp_capable = false; 299 bool is_apple_mobile_webapp_capable = false; 300 301 // Search the DOM for the webapp <meta> tags. 302 if (!head.isNull()) { 303 WebNodeList children = head.childNodes(); 304 for (unsigned i = 0; i < children.length(); ++i) { 305 WebNode child = children.item(i); 306 if (!child.isElementNode()) 307 continue; 308 WebElement elem = child.to<WebElement>(); 309 310 if (elem.hasTagName("meta") && elem.hasAttribute("name")) { 311 std::string name = elem.getAttribute("name").utf8(); 312 WebString content = elem.getAttribute("content"); 313 if (LowerCaseEqualsASCII(content, "yes")) { 314 if (name == "mobile-web-app-capable") { 315 is_mobile_webapp_capable = true; 316 } else if (name == "apple-mobile-web-app-capable") { 317 is_apple_mobile_webapp_capable = true; 318 } 319 } 320 } 321 } 322 } else { 323 success = false; 324 } 325 326 bool is_only_apple_mobile_webapp_capable = 327 is_apple_mobile_webapp_capable && !is_mobile_webapp_capable; 328 if (main_frame && is_only_apple_mobile_webapp_capable) { 329 blink::WebConsoleMessage message( 330 blink::WebConsoleMessage::LevelWarning, 331 "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"> is " 332 "deprecated. Please include <meta name=\"mobile-web-app-capable\" " 333 "content=\"yes\"> - " 334 "http://developers.google.com/chrome/mobile/docs/installtohomescreen"); 335 main_frame->addMessageToConsole(message); 336 } 337 338 Send(new ChromeViewHostMsg_DidRetrieveWebappInformation( 339 routing_id(), 340 success, 341 is_mobile_webapp_capable, 342 is_apple_mobile_webapp_capable, 343 expected_url)); 344 } 345 #endif 346 347 void ChromeRenderViewObserver::OnSetWindowFeatures( 348 const WebWindowFeatures& window_features) { 349 render_view()->GetWebView()->setWindowFeatures(window_features); 350 } 351 352 void ChromeRenderViewObserver::Navigate(const GURL& url) { 353 // Execute cache clear operations that were postponed until a navigation 354 // event (including tab reload). 355 if (chrome_render_process_observer_) 356 chrome_render_process_observer_->ExecutePendingClearCache(); 357 } 358 359 void ChromeRenderViewObserver::OnSetClientSidePhishingDetection( 360 bool enable_phishing_detection) { 361 #if defined(FULL_SAFE_BROWSING) && !defined(OS_CHROMEOS) 362 phishing_classifier_ = enable_phishing_detection ? 363 safe_browsing::PhishingClassifierDelegate::Create( 364 render_view(), NULL) : 365 NULL; 366 #endif 367 } 368 369 void ChromeRenderViewObserver::OnSetVisuallyDeemphasized(bool deemphasized) { 370 bool already_deemphasized = !!dimmed_color_overlay_.get(); 371 if (already_deemphasized == deemphasized) 372 return; 373 374 if (deemphasized) { 375 // 70% opaque grey. 376 SkColor greyish = SkColorSetARGB(178, 0, 0, 0); 377 dimmed_color_overlay_.reset( 378 new WebViewColorOverlay(render_view(), greyish)); 379 } else { 380 dimmed_color_overlay_.reset(); 381 } 382 } 383 384 void ChromeRenderViewObserver::OnRequestThumbnailForContextNode( 385 int thumbnail_min_area_pixels, gfx::Size thumbnail_max_size_pixels) { 386 WebNode context_node = render_view()->GetContextMenuNode(); 387 SkBitmap thumbnail; 388 gfx::Size original_size; 389 if (!context_node.isNull() && context_node.isElementNode()) { 390 blink::WebImage image = context_node.to<WebElement>().imageContents(); 391 original_size = image.size(); 392 thumbnail = Downscale(image, 393 thumbnail_min_area_pixels, 394 thumbnail_max_size_pixels); 395 } 396 Send(new ChromeViewHostMsg_RequestThumbnailForContextNode_ACK( 397 routing_id(), thumbnail, original_size)); 398 } 399 400 void ChromeRenderViewObserver::OnGetFPS() { 401 float fps = (render_view()->GetFilteredTimePerFrame() > 0.0f)? 402 1.0f / render_view()->GetFilteredTimePerFrame() : 0.0f; 403 Send(new ChromeViewHostMsg_FPS(routing_id(), fps)); 404 } 405 406 void ChromeRenderViewObserver::DidStartLoading() { 407 if ((render_view()->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI) && 408 webui_javascript_.get()) { 409 render_view()->EvaluateScript(webui_javascript_->frame_xpath, 410 webui_javascript_->jscript, 411 webui_javascript_->id, 412 webui_javascript_->notify_result); 413 webui_javascript_.reset(); 414 } 415 } 416 417 void ChromeRenderViewObserver::DidStopLoading() { 418 WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); 419 GURL osd_url = main_frame->document().openSearchDescriptionURL(); 420 if (!osd_url.is_empty()) { 421 Send(new ChromeViewHostMsg_PageHasOSDD( 422 routing_id(), render_view()->GetPageId(), osd_url, 423 search_provider::AUTODETECTED_PROVIDER)); 424 } 425 426 // Don't capture pages including refresh meta tag. 427 if (HasRefreshMetaTag(main_frame)) 428 return; 429 430 CapturePageInfoLater( 431 render_view()->GetPageId(), 432 false, // preliminary_capture 433 base::TimeDelta::FromMilliseconds( 434 render_view()->GetContentStateImmediately() ? 435 0 : kDelayForCaptureMs)); 436 } 437 438 void ChromeRenderViewObserver::DidCommitProvisionalLoad( 439 WebFrame* frame, bool is_new_navigation) { 440 // Don't capture pages being not new, or including refresh meta tag. 441 if (!is_new_navigation || HasRefreshMetaTag(frame)) 442 return; 443 444 CapturePageInfoLater( 445 render_view()->GetPageId(), 446 true, // preliminary_capture 447 base::TimeDelta::FromMilliseconds(kDelayForForcedCaptureMs)); 448 } 449 450 void ChromeRenderViewObserver::DidClearWindowObject(WebFrame* frame) { 451 if (render_view()->GetEnabledBindings() & 452 content::BINDINGS_POLICY_EXTERNAL_HOST) { 453 GetExternalHostBindings()->BindToJavascript(frame, "externalHost"); 454 } 455 } 456 457 void ChromeRenderViewObserver::DetailedConsoleMessageAdded( 458 const base::string16& message, 459 const base::string16& source, 460 const base::string16& stack_trace_string, 461 int32 line_number, 462 int32 severity_level) { 463 base::string16 trimmed_message = message; 464 extensions::StackTrace stack_trace = GetStackTraceFromMessage( 465 &trimmed_message, 466 source, 467 stack_trace_string, 468 line_number); 469 Send(new ChromeViewHostMsg_DetailedConsoleMessageAdded(routing_id(), 470 trimmed_message, 471 source, 472 stack_trace, 473 severity_level)); 474 } 475 476 void ChromeRenderViewObserver::CapturePageInfoLater(int page_id, 477 bool preliminary_capture, 478 base::TimeDelta delay) { 479 capture_timer_.Start( 480 FROM_HERE, 481 delay, 482 base::Bind(&ChromeRenderViewObserver::CapturePageInfo, 483 base::Unretained(this), 484 page_id, 485 preliminary_capture)); 486 } 487 488 void ChromeRenderViewObserver::CapturePageInfo(int page_id, 489 bool preliminary_capture) { 490 // If |page_id| is obsolete, we should stop indexing and capturing a page. 491 if (render_view()->GetPageId() != page_id) 492 return; 493 494 if (!render_view()->GetWebView()) 495 return; 496 497 WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); 498 if (!main_frame) 499 return; 500 501 // Don't index/capture pages that are in view source mode. 502 if (main_frame->isViewSourceModeEnabled()) 503 return; 504 505 // Don't index/capture pages that failed to load. This only checks the top 506 // level frame so the thumbnail may contain a frame that failed to load. 507 WebDataSource* ds = main_frame->dataSource(); 508 if (ds && ds->hasUnreachableURL()) 509 return; 510 511 // Don't index/capture pages that are being prerendered. 512 if (prerender::PrerenderHelper::IsPrerendering( 513 render_view()->GetMainRenderFrame())) { 514 return; 515 } 516 517 // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the 518 // translate helper for language detection and possible translation. 519 base::string16 contents; 520 base::TimeTicks capture_begin_time = base::TimeTicks::Now(); 521 CaptureText(main_frame, &contents); 522 UMA_HISTOGRAM_TIMES(kTranslateCaptureText, 523 base::TimeTicks::Now() - capture_begin_time); 524 if (translate_helper_) 525 translate_helper_->PageCaptured(page_id, contents); 526 527 // TODO(shess): Is indexing "Full text search" indexing? In that 528 // case more of this can go. 529 // Skip indexing if this is not a new load. Note that the case where 530 // page_id == last_indexed_page_id_ is more complicated, since we need to 531 // reindex if the toplevel URL has changed (such as from a redirect), even 532 // though this may not cause the page id to be incremented. 533 if (page_id < last_indexed_page_id_) 534 return; 535 536 bool same_page_id = last_indexed_page_id_ == page_id; 537 if (!preliminary_capture) 538 last_indexed_page_id_ = page_id; 539 540 // Get the URL for this page. 541 GURL url(main_frame->document().url()); 542 if (url.is_empty()) { 543 if (!preliminary_capture) 544 last_indexed_url_ = GURL(); 545 return; 546 } 547 548 // If the page id is unchanged, check whether the URL (ignoring fragments) 549 // has changed. If so, we need to reindex. Otherwise, assume this is a 550 // reload, in-page navigation, or some other load type where we don't want to 551 // reindex. Note: subframe navigations after onload increment the page id, 552 // so these will trigger a reindex. 553 GURL stripped_url(StripRef(url)); 554 if (same_page_id && stripped_url == last_indexed_url_) 555 return; 556 557 if (!preliminary_capture) 558 last_indexed_url_ = stripped_url; 559 560 TRACE_EVENT0("renderer", "ChromeRenderViewObserver::CapturePageInfo"); 561 562 #if defined(FULL_SAFE_BROWSING) 563 // Will swap out the string. 564 if (phishing_classifier_) 565 phishing_classifier_->PageCaptured(&contents, preliminary_capture); 566 #endif 567 } 568 569 void ChromeRenderViewObserver::CaptureText(WebFrame* frame, 570 base::string16* contents) { 571 contents->clear(); 572 if (!frame) 573 return; 574 575 #ifdef TIME_TEXT_RETRIEVAL 576 double begin = time_util::GetHighResolutionTimeNow(); 577 #endif 578 579 // get the contents of the frame 580 *contents = frame->contentAsText(kMaxIndexChars); 581 582 #ifdef TIME_TEXT_RETRIEVAL 583 double end = time_util::GetHighResolutionTimeNow(); 584 char buf[128]; 585 sprintf_s(buf, "%d chars retrieved for indexing in %gms\n", 586 contents.size(), (end - begin)*1000); 587 OutputDebugStringA(buf); 588 #endif 589 590 // When the contents are clipped to the maximum, we don't want to have a 591 // partial word indexed at the end that might have been clipped. Therefore, 592 // terminate the string at the last space to ensure no words are clipped. 593 if (contents->size() == kMaxIndexChars) { 594 size_t last_space_index = contents->find_last_of(base::kWhitespaceUTF16); 595 if (last_space_index == base::string16::npos) 596 return; // don't index if we got a huge block of text with no spaces 597 contents->resize(last_space_index); 598 } 599 } 600 601 ExternalHostBindings* ChromeRenderViewObserver::GetExternalHostBindings() { 602 if (!external_host_bindings_.get()) { 603 external_host_bindings_.reset(new ExternalHostBindings( 604 render_view(), routing_id())); 605 } 606 return external_host_bindings_.get(); 607 } 608 609 bool ChromeRenderViewObserver::HasRefreshMetaTag(WebFrame* frame) { 610 if (!frame) 611 return false; 612 WebElement head = frame->document().head(); 613 if (head.isNull() || !head.hasChildNodes()) 614 return false; 615 616 const WebString tag_name(ASCIIToUTF16("meta")); 617 const WebString attribute_name(ASCIIToUTF16("http-equiv")); 618 619 WebNodeList children = head.childNodes(); 620 for (size_t i = 0; i < children.length(); ++i) { 621 WebNode node = children.item(i); 622 if (!node.isElementNode()) 623 continue; 624 WebElement element = node.to<WebElement>(); 625 if (!element.hasTagName(tag_name)) 626 continue; 627 WebString value = element.getAttribute(attribute_name); 628 if (value.isNull() || !LowerCaseEqualsASCII(value, "refresh")) 629 continue; 630 return true; 631 } 632 return false; 633 } 634