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