Home | History | Annotate | Download | only in renderer
      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 "android_webview/renderer/aw_render_view_ext.h"
      6 
      7 #include <string>
      8 
      9 #include "android_webview/common/aw_hit_test_data.h"
     10 #include "android_webview/common/render_view_messages.h"
     11 #include "base/bind.h"
     12 #include "base/strings/string_piece.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/time/time.h"
     15 #include "content/public/renderer/android_content_detection_prefixes.h"
     16 #include "content/public/renderer/document_state.h"
     17 #include "content/public/renderer/render_view.h"
     18 #include "skia/ext/refptr.h"
     19 #include "third_party/WebKit/public/platform/WebSize.h"
     20 #include "third_party/WebKit/public/platform/WebURL.h"
     21 #include "third_party/WebKit/public/platform/WebVector.h"
     22 #include "third_party/WebKit/public/web/WebDataSource.h"
     23 #include "third_party/WebKit/public/web/WebDocument.h"
     24 #include "third_party/WebKit/public/web/WebElement.h"
     25 #include "third_party/WebKit/public/web/WebElementCollection.h"
     26 #include "third_party/WebKit/public/web/WebHitTestResult.h"
     27 #include "third_party/WebKit/public/web/WebImageCache.h"
     28 #include "third_party/WebKit/public/web/WebLocalFrame.h"
     29 #include "third_party/WebKit/public/web/WebNode.h"
     30 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
     31 #include "third_party/WebKit/public/web/WebView.h"
     32 #include "url/url_canon.h"
     33 #include "url/url_constants.h"
     34 #include "url/url_util.h"
     35 
     36 namespace android_webview {
     37 
     38 namespace {
     39 
     40 GURL GetAbsoluteUrl(const blink::WebNode& node,
     41                     const base::string16& url_fragment) {
     42   return GURL(node.document().completeURL(url_fragment));
     43 }
     44 
     45 base::string16 GetHref(const blink::WebElement& element) {
     46   // Get the actual 'href' attribute, which might relative if valid or can
     47   // possibly contain garbage otherwise, so not using absoluteLinkURL here.
     48   return element.getAttribute("href");
     49 }
     50 
     51 GURL GetAbsoluteSrcUrl(const blink::WebElement& element) {
     52   if (element.isNull())
     53     return GURL();
     54   return GetAbsoluteUrl(element, element.getAttribute("src"));
     55 }
     56 
     57 blink::WebElement GetImgChild(const blink::WebElement& element) {
     58   // This implementation is incomplete (for example if is an area tag) but
     59   // matches the original WebViewClassic implementation.
     60 
     61   blink::WebElementCollection collection = element.getElementsByTagName("img");
     62   DCHECK(!collection.isNull());
     63   return collection.firstItem();
     64 }
     65 
     66 bool RemovePrefixAndAssignIfMatches(const base::StringPiece& prefix,
     67                                     const GURL& url,
     68                                     std::string* dest) {
     69   const base::StringPiece spec(url.possibly_invalid_spec());
     70 
     71   if (spec.starts_with(prefix)) {
     72     url::RawCanonOutputW<1024> output;
     73     url::DecodeURLEscapeSequences(spec.data() + prefix.length(),
     74                                   spec.length() - prefix.length(),
     75                                   &output);
     76     std::string decoded_url = base::UTF16ToUTF8(
     77         base::string16(output.data(), output.length()));
     78     dest->assign(decoded_url.begin(), decoded_url.end());
     79     return true;
     80   }
     81   return false;
     82 }
     83 
     84 void DistinguishAndAssignSrcLinkType(const GURL& url, AwHitTestData* data) {
     85   if (RemovePrefixAndAssignIfMatches(
     86       content::kAddressPrefix,
     87       url,
     88       &data->extra_data_for_type)) {
     89     data->type = AwHitTestData::GEO_TYPE;
     90   } else if (RemovePrefixAndAssignIfMatches(
     91       content::kPhoneNumberPrefix,
     92       url,
     93       &data->extra_data_for_type)) {
     94     data->type = AwHitTestData::PHONE_TYPE;
     95   } else if (RemovePrefixAndAssignIfMatches(
     96       content::kEmailPrefix,
     97       url,
     98       &data->extra_data_for_type)) {
     99     data->type = AwHitTestData::EMAIL_TYPE;
    100   } else {
    101     data->type = AwHitTestData::SRC_LINK_TYPE;
    102     data->extra_data_for_type = url.possibly_invalid_spec();
    103     if (!data->extra_data_for_type.empty())
    104       data->href = base::UTF8ToUTF16(data->extra_data_for_type);
    105   }
    106 }
    107 
    108 void PopulateHitTestData(const GURL& absolute_link_url,
    109                          const GURL& absolute_image_url,
    110                          bool is_editable,
    111                          AwHitTestData* data) {
    112   // Note: Using GURL::is_empty instead of GURL:is_valid due to the
    113   // WebViewClassic allowing any kind of protocol which GURL::is_valid
    114   // disallows. Similar reasons for using GURL::possibly_invalid_spec instead of
    115   // GURL::spec.
    116   if (!absolute_image_url.is_empty())
    117     data->img_src = absolute_image_url;
    118 
    119   const bool is_javascript_scheme =
    120       absolute_link_url.SchemeIs(url::kJavaScriptScheme);
    121   const bool has_link_url = !absolute_link_url.is_empty();
    122   const bool has_image_url = !absolute_image_url.is_empty();
    123 
    124   if (has_link_url && !has_image_url && !is_javascript_scheme) {
    125     DistinguishAndAssignSrcLinkType(absolute_link_url, data);
    126   } else if (has_link_url && has_image_url && !is_javascript_scheme) {
    127     data->type = AwHitTestData::SRC_IMAGE_LINK_TYPE;
    128     data->extra_data_for_type = data->img_src.possibly_invalid_spec();
    129     if (absolute_link_url.is_valid())
    130       data->href = base::UTF8ToUTF16(absolute_link_url.possibly_invalid_spec());
    131   } else if (!has_link_url && has_image_url) {
    132     data->type = AwHitTestData::IMAGE_TYPE;
    133     data->extra_data_for_type = data->img_src.possibly_invalid_spec();
    134   } else if (is_editable) {
    135     data->type = AwHitTestData::EDIT_TEXT_TYPE;
    136     DCHECK(data->extra_data_for_type.length() == 0);
    137   }
    138 }
    139 
    140 }  // namespace
    141 
    142 AwRenderViewExt::AwRenderViewExt(content::RenderView* render_view)
    143     : content::RenderViewObserver(render_view), page_scale_factor_(0.0f) {
    144 }
    145 
    146 AwRenderViewExt::~AwRenderViewExt() {
    147 }
    148 
    149 // static
    150 void AwRenderViewExt::RenderViewCreated(content::RenderView* render_view) {
    151   new AwRenderViewExt(render_view);  // |render_view| takes ownership.
    152 }
    153 
    154 bool AwRenderViewExt::OnMessageReceived(const IPC::Message& message) {
    155   bool handled = true;
    156   IPC_BEGIN_MESSAGE_MAP(AwRenderViewExt, message)
    157     IPC_MESSAGE_HANDLER(AwViewMsg_DocumentHasImages, OnDocumentHasImagesRequest)
    158     IPC_MESSAGE_HANDLER(AwViewMsg_DoHitTest, OnDoHitTest)
    159     IPC_MESSAGE_HANDLER(AwViewMsg_SetTextZoomFactor, OnSetTextZoomFactor)
    160     IPC_MESSAGE_HANDLER(AwViewMsg_ResetScrollAndScaleState,
    161                         OnResetScrollAndScaleState)
    162     IPC_MESSAGE_HANDLER(AwViewMsg_SetInitialPageScale, OnSetInitialPageScale)
    163     IPC_MESSAGE_HANDLER(AwViewMsg_SetBackgroundColor, OnSetBackgroundColor)
    164     IPC_MESSAGE_UNHANDLED(handled = false)
    165   IPC_END_MESSAGE_MAP()
    166   return handled;
    167 }
    168 
    169 void AwRenderViewExt::OnDocumentHasImagesRequest(int id) {
    170   bool hasImages = false;
    171   if (render_view()) {
    172     blink::WebView* webview = render_view()->GetWebView();
    173     if (webview) {
    174       blink::WebVector<blink::WebElement> images;
    175       webview->mainFrame()->document().images(images);
    176       hasImages = !images.isEmpty();
    177     }
    178   }
    179   Send(new AwViewHostMsg_DocumentHasImagesResponse(routing_id(), id,
    180                                                    hasImages));
    181 }
    182 
    183 void AwRenderViewExt::DidCommitCompositorFrame() {
    184   UpdatePageScaleFactor();
    185 }
    186 
    187 void AwRenderViewExt::DidUpdateLayout() {
    188   if (check_contents_size_timer_.IsRunning())
    189     return;
    190 
    191   check_contents_size_timer_.Start(FROM_HERE,
    192                                    base::TimeDelta::FromMilliseconds(0), this,
    193                                    &AwRenderViewExt::CheckContentsSize);
    194 }
    195 
    196 void AwRenderViewExt::UpdatePageScaleFactor() {
    197   if (page_scale_factor_ != render_view()->GetWebView()->pageScaleFactor()) {
    198     page_scale_factor_ = render_view()->GetWebView()->pageScaleFactor();
    199     Send(new AwViewHostMsg_PageScaleFactorChanged(routing_id(),
    200                                                   page_scale_factor_));
    201   }
    202 }
    203 
    204 void AwRenderViewExt::CheckContentsSize() {
    205   if (!render_view()->GetWebView())
    206     return;
    207 
    208   gfx::Size contents_size;
    209 
    210   blink::WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
    211   if (main_frame)
    212     contents_size = main_frame->contentsSize();
    213 
    214   // Fall back to contentsPreferredMinimumSize if the mainFrame is reporting a
    215   // 0x0 size (this happens during initial load).
    216   if (contents_size.IsEmpty()) {
    217     contents_size = render_view()->GetWebView()->contentsPreferredMinimumSize();
    218   }
    219 
    220   if (contents_size == last_sent_contents_size_)
    221     return;
    222 
    223   last_sent_contents_size_ = contents_size;
    224   Send(new AwViewHostMsg_OnContentsSizeChanged(routing_id(), contents_size));
    225 }
    226 
    227 void AwRenderViewExt::Navigate(const GURL& url) {
    228   // Navigate is called only on NEW navigations, so WebImageCache won't be freed
    229   // when the user just clicks on links, but only when a navigation is started,
    230   // for instance via loadUrl. A better approach would be clearing the cache on
    231   // cross-site boundaries, however this would require too many changes both on
    232   // the browser side (in RenderViewHostManger), to the IPCmessages and to the
    233   // RenderViewObserver. Thus, clearing decoding image cache on Navigate, seems
    234   // a more acceptable compromise.
    235   blink::WebImageCache::clear();
    236 }
    237 
    238 void AwRenderViewExt::FocusedNodeChanged(const blink::WebNode& node) {
    239   if (node.isNull() || !node.isElementNode() || !render_view())
    240     return;
    241 
    242   // Note: element is not const due to innerText() is not const.
    243   blink::WebElement element = node.toConst<blink::WebElement>();
    244   AwHitTestData data;
    245 
    246   data.href = GetHref(element);
    247   data.anchor_text = element.innerText();
    248 
    249   GURL absolute_link_url;
    250   if (node.isLink())
    251     absolute_link_url = GetAbsoluteUrl(node, data.href);
    252 
    253   GURL absolute_image_url;
    254   const blink::WebElement child_img = GetImgChild(element);
    255   if (!child_img.isNull()) {
    256     absolute_image_url =
    257         GetAbsoluteSrcUrl(child_img);
    258   }
    259 
    260   PopulateHitTestData(absolute_link_url,
    261                       absolute_image_url,
    262                       render_view()->IsEditableNode(node),
    263                       &data);
    264   Send(new AwViewHostMsg_UpdateHitTestData(routing_id(), data));
    265 }
    266 
    267 void AwRenderViewExt::OnDoHitTest(int view_x, int view_y) {
    268   if (!render_view() || !render_view()->GetWebView())
    269     return;
    270 
    271   const blink::WebHitTestResult result =
    272       render_view()->GetWebView()->hitTestResultAt(
    273           blink::WebPoint(view_x, view_y));
    274   AwHitTestData data;
    275 
    276   if (!result.urlElement().isNull()) {
    277     data.anchor_text = result.urlElement().innerText();
    278     data.href = GetHref(result.urlElement());
    279   }
    280 
    281   PopulateHitTestData(result.absoluteLinkURL(),
    282                       result.absoluteImageURL(),
    283                       result.isContentEditable(),
    284                       &data);
    285   Send(new AwViewHostMsg_UpdateHitTestData(routing_id(), data));
    286 }
    287 
    288 void AwRenderViewExt::OnSetTextZoomFactor(float zoom_factor) {
    289   if (!render_view() || !render_view()->GetWebView())
    290     return;
    291   // Hide selection and autofill popups.
    292   render_view()->GetWebView()->hidePopups();
    293   render_view()->GetWebView()->setTextZoomFactor(zoom_factor);
    294 }
    295 
    296 void AwRenderViewExt::OnResetScrollAndScaleState() {
    297   if (!render_view() || !render_view()->GetWebView())
    298     return;
    299   render_view()->GetWebView()->resetScrollAndScaleState();
    300 }
    301 
    302 void AwRenderViewExt::OnSetInitialPageScale(double page_scale_factor) {
    303   if (!render_view() || !render_view()->GetWebView())
    304     return;
    305   render_view()->GetWebView()->setInitialPageScaleOverride(
    306       page_scale_factor);
    307 }
    308 
    309 void AwRenderViewExt::OnSetBackgroundColor(SkColor c) {
    310   if (!render_view() || !render_view()->GetWebView())
    311     return;
    312   render_view()->GetWebView()->setBaseBackgroundColor(c);
    313 }
    314 
    315 }  // namespace android_webview
    316