diff options
-rw-r--r-- | chrome/chrome_renderer.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/renderer/blocked_plugin.cc | 5 | ||||
-rw-r--r-- | chrome/renderer/chrome_content_renderer_client.cc | 22 | ||||
-rw-r--r-- | chrome/renderer/chrome_content_renderer_client.h | 6 | ||||
-rw-r--r-- | chrome/renderer/plugin_uma.cc | 164 | ||||
-rw-r--r-- | chrome/renderer/plugin_uma.h | 70 | ||||
-rw-r--r-- | chrome/renderer/plugin_uma_unittest.cc | 235 |
8 files changed, 505 insertions, 0 deletions
diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi index 8dd9e35..765aa6a 100644 --- a/chrome/chrome_renderer.gypi +++ b/chrome/chrome_renderer.gypi @@ -122,6 +122,8 @@ 'renderer/page_click_tracker.h', 'renderer/page_load_histograms.cc', 'renderer/page_load_histograms.h', + 'renderer/plugin_uma.cc', + 'renderer/plugin_uma.h', 'renderer/prerender/prerender_helper.cc', 'renderer/prerender/prerender_helper.h', 'renderer/print_web_view_helper.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index e9b7c90..d6b8025 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1953,6 +1953,7 @@ 'renderer/extensions/json_schema_unittest.cc', 'renderer/net/predictor_queue_unittest.cc', 'renderer/net/renderer_predictor_unittest.cc', + 'renderer/plugin_uma_unittest.cc', 'renderer/renderer_about_handler_unittest.cc', 'renderer/safe_browsing/features_unittest.cc', 'renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc', diff --git a/chrome/renderer/blocked_plugin.cc b/chrome/renderer/blocked_plugin.cc index 3064ba9..d4e84e6 100644 --- a/chrome/renderer/blocked_plugin.cc +++ b/chrome/renderer/blocked_plugin.cc @@ -9,6 +9,7 @@ #include "base/values.h" #include "chrome/common/jstemplate_builder.h" #include "chrome/common/render_messages.h" +#include "chrome/renderer/plugin_uma.h" #include "content/common/view_messages.h" #include "content/renderer/render_view.h" #include "grit/generated_resources.h" @@ -200,6 +201,10 @@ void BlockedPlugin::LoadPlugin() { container->reportGeometry(); plugin_->ReplayReceivedData(new_plugin); plugin_->destroy(); + } else { + MissingPluginReporter::GetInstance()->ReportPluginMissing( + plugin_params_.mimeType.utf8(), + plugin_params_.url); } } diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc index ee7db8e..9557c30 100644 --- a/chrome/renderer/chrome_content_renderer_client.cc +++ b/chrome/renderer/chrome_content_renderer_client.cc @@ -47,6 +47,7 @@ #include "chrome/renderer/net/renderer_net_predictor.h" #include "chrome/renderer/page_click_tracker.h" #include "chrome/renderer/page_load_histograms.h" +#include "chrome/renderer/plugin_uma.h" #include "chrome/renderer/prerender/prerender_helper.h" #include "chrome/renderer/print_web_view_helper.h" #include "chrome/renderer/renderer_histogram_snapshots.h" @@ -261,7 +262,25 @@ WebPlugin* ChromeContentRendererClient::CreatePlugin( RenderView* render_view, WebFrame* frame, const WebPluginParams& original_params) { + bool is_default_plugin; + WebPlugin* plugin = CreatePluginImpl(render_view, + frame, + original_params, + &is_default_plugin); + if (!plugin || is_default_plugin) + MissingPluginReporter::GetInstance()->ReportPluginMissing( + original_params.mimeType.utf8(), + original_params.url); + return plugin; +} + +WebPlugin* ChromeContentRendererClient::CreatePluginImpl( + RenderView* render_view, + WebFrame* frame, + const WebPluginParams& original_params, + bool* is_default_plugin) { bool found = false; + *is_default_plugin = false; CommandLine* cmd = CommandLine::ForCurrentProcess(); webkit::npapi::WebPluginInfo info; GURL url(original_params.url); @@ -276,6 +295,9 @@ WebPlugin* ChromeContentRendererClient::CreatePlugin( if (!webkit::npapi::IsPluginEnabled(info)) return NULL; + *is_default_plugin = + info.path.value() == webkit::npapi::kDefaultPluginLibraryName; + if (orig_mime_type == actual_mime_type) { UMA_HISTOGRAM_ENUMERATION(kPluginTypeMismatch, PLUGIN_TYPE_MISMATCH_NONE, diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h index ad629bd..35c6383 100644 --- a/chrome/renderer/chrome_content_renderer_client.h +++ b/chrome/renderer/chrome_content_renderer_client.h @@ -74,6 +74,12 @@ class ChromeContentRendererClient : public content::ContentRendererClient { void SetExtensionDispatcher(ExtensionDispatcher* extension_dispatcher); private: + WebKit::WebPlugin* CreatePluginImpl( + RenderView* render_view, + WebKit::WebFrame* frame, + const WebKit::WebPluginParams& params, + bool* is_default_plugin); + WebKit::WebPlugin* CreatePluginPlaceholder( RenderView* render_view, WebKit::WebFrame* frame, diff --git a/chrome/renderer/plugin_uma.cc b/chrome/renderer/plugin_uma.cc new file mode 100644 index 0000000..ad215b7 --- /dev/null +++ b/chrome/renderer/plugin_uma.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2011 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 "chrome/renderer/plugin_uma.h" + +#include <algorithm> +#include <cstring> + +#include "base/metrics/histogram.h" +#include "base/string_util.h" + +namespace { + +// String we will use to convert mime tyoe to plugin type. +const char kWindowsMediaPlayerType[] = "application/x-mplayer2"; +const char kSilverlightTypePrefix[] = "application/x-silverlight"; +const char kRealPlayerTypePrefix[] = "audio/x-pn-realaudio"; +const char kJavaTypeSubstring[] = "application/x-java-applet"; +const char kQuickTimeType[] = "video/quicktime"; + +// Arrays containing file extensions connected with specific plugins. +// The arrays must be sorted because binary search is used on them. +const char* kWindowsMediaPlayerExtensions[] = { + ".asx" +}; + +const char* kRealPlayerExtensions[] = { + ".ra", + ".ram", + ".rm", + ".rmm", + ".rmp", + ".rpm" +}; + +const char* kQuickTimeExtensions[] = { + ".moov", + ".mov", + ".qif", + ".qt", + ".qti", + ".qtif" +}; + +} // namespace. + +class UMASenderImpl : public MissingPluginReporter::UMASender { + virtual void SendPluginUMA(MissingPluginReporter::PluginType plugin_type) + OVERRIDE; +}; + +void UMASenderImpl::SendPluginUMA( + MissingPluginReporter::PluginType plugin_type) { + UMA_HISTOGRAM_ENUMERATION("Plugin.MissingPlugins", + plugin_type, + MissingPluginReporter::OTHER); +} + +// static. +MissingPluginReporter* MissingPluginReporter::GetInstance() { + return Singleton<MissingPluginReporter>::get(); +} + +void MissingPluginReporter::ReportPluginMissing( + std::string plugin_mime_type, const GURL& plugin_src) { + PluginType plugin_type; + // If we know plugin's mime type, we use it to determine plugin's type. Else, + // we try to determine plugin type using plugin source's extension. + if (!plugin_mime_type.empty()) { + StringToLowerASCII(&plugin_mime_type); + plugin_type = MimeTypeToPluginType(plugin_mime_type); + } else { + plugin_type = SrcToPluginType(plugin_src); + } + report_sender_->SendPluginUMA(plugin_type); +} + +void MissingPluginReporter::SetUMASender(UMASender* sender) { + report_sender_.reset(sender); +} + +MissingPluginReporter::MissingPluginReporter() + : report_sender_(new UMASenderImpl()) { +} + +MissingPluginReporter::~MissingPluginReporter() { +} + +// static. +bool MissingPluginReporter::CompareCStrings(const char* first, + const char* second) { + return strcmp(first, second) < 0; +} + +bool MissingPluginReporter::CStringArrayContainsCString(const char** array, + size_t array_size, + const char* str) { + return std::binary_search(array, array + array_size, str, CompareCStrings); +} + +void MissingPluginReporter::ExtractFileExtension(const GURL& src, + std::string* extension) { + std::string extension_file_path(src.ExtractFileName()); + if (extension_file_path.empty()) + extension_file_path = src.host(); + + size_t last_dot = extension_file_path.find_last_of('.'); + if (last_dot != std::string::npos) { + *extension = extension_file_path.substr(last_dot); + } else { + extension->clear(); + } + + StringToLowerASCII(extension); +} + +MissingPluginReporter::PluginType MissingPluginReporter::SrcToPluginType( + const GURL& src) { + std::string file_extension; + ExtractFileExtension(src, &file_extension); + if (CStringArrayContainsCString(kWindowsMediaPlayerExtensions, + arraysize(kWindowsMediaPlayerExtensions), + file_extension.c_str())) { + return WINDOWS_MEDIA_PLAYER; + } + + if (CStringArrayContainsCString(kQuickTimeExtensions, + arraysize(kQuickTimeExtensions), + file_extension.c_str())) { + return QUICKTIME; + } + + if (CStringArrayContainsCString(kRealPlayerExtensions, + arraysize(kRealPlayerExtensions), + file_extension.c_str())) { + return REALPLAYER; + } + + return OTHER; +} + +MissingPluginReporter::PluginType MissingPluginReporter::MimeTypeToPluginType( + const std::string& mime_type) { + if (strcmp(mime_type.c_str(), kWindowsMediaPlayerType) == 0) + return WINDOWS_MEDIA_PLAYER; + + size_t prefix_length = strlen(kSilverlightTypePrefix); + if (strncmp(mime_type.c_str(), kSilverlightTypePrefix, prefix_length) == 0) + return SILVERLIGHT; + + prefix_length = strlen(kRealPlayerTypePrefix); + if (strncmp(mime_type.c_str(), kRealPlayerTypePrefix, prefix_length) == 0) + return REALPLAYER; + + if (strstr(mime_type.c_str(), kJavaTypeSubstring)) + return JAVA; + + if (strcmp(mime_type.c_str(), kQuickTimeType) == 0) + return QUICKTIME; + + return OTHER; +} + diff --git a/chrome/renderer/plugin_uma.h b/chrome/renderer/plugin_uma.h new file mode 100644 index 0000000..411a8ae --- /dev/null +++ b/chrome/renderer/plugin_uma.h @@ -0,0 +1,70 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_RENDERER_PLUGIN_UMA_H_ +#define CHROME_RENDERER_PLUGIN_UMA_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "googleurl/src/gurl.h" + +// Used to send UMA data about missing plugins to UMA histogram server. Method +// ReportPluginMissing should be called whenever plugin that is not available or +// enabled is called. We try to determine plugin's type by requested mime type, +// or, if mime type is unknown, by plugin's src url. +class MissingPluginReporter { + public: + // This must be sync'd with histogram values. + enum PluginType { + WINDOWS_MEDIA_PLAYER = 0, + SILVERLIGHT = 1, + REALPLAYER = 2, + JAVA = 3, + QUICKTIME = 4, + OTHER = 5 + }; + + // Sends UMA data, i.e. plugin's type. + class UMASender { + public: + virtual ~UMASender() {} + virtual void SendPluginUMA(PluginType plugin_type) = 0; + }; + + // Returns singleton instance. + static MissingPluginReporter* GetInstance(); + + void ReportPluginMissing(std::string plugin_mime_type, + const GURL& plugin_src); + + // Used in testing. + void SetUMASender(UMASender* sender); + + private: + friend struct DefaultSingletonTraits<MissingPluginReporter>; + + MissingPluginReporter(); + ~MissingPluginReporter(); + + static bool CompareCStrings(const char* first, const char* second); + bool CStringArrayContainsCString(const char** array, + size_t array_size, + const char* str); + // Extracts file extension from url. + void ExtractFileExtension(const GURL& src, std::string* extension); + + // Converts plugin's src to plugin type. + PluginType SrcToPluginType(const GURL& src); + // Converts plugin's mime type to plugin type. + PluginType MimeTypeToPluginType(const std::string& mime_type); + + scoped_ptr<UMASender> report_sender_; + + DISALLOW_COPY_AND_ASSIGN(MissingPluginReporter); +}; + +#endif // CHROME_RENDERER_PLUGIN_UMA_H_ + diff --git a/chrome/renderer/plugin_uma_unittest.cc b/chrome/renderer/plugin_uma_unittest.cc new file mode 100644 index 0000000..c59d932 --- /dev/null +++ b/chrome/renderer/plugin_uma_unittest.cc @@ -0,0 +1,235 @@ +// Copyright (c) 2011 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 <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "chrome/renderer/plugin_uma.h" + +using ::testing::_; + +class MockPluginUMASender : public MissingPluginReporter::UMASender { + public: + MOCK_METHOD1(SendPluginUMA, void(MissingPluginReporter::PluginType)); +}; + +TEST(PluginUMATest, WindowsMediaPlayer) { + MockPluginUMASender* sender_mock = new MockPluginUMASender(); + MissingPluginReporter::GetInstance()->SetUMASender(sender_mock); + EXPECT_CALL(*sender_mock, SendPluginUMA(_)) + .Times(0); + + EXPECT_CALL(*sender_mock, + SendPluginUMA(MissingPluginReporter::WINDOWS_MEDIA_PLAYER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "application/x-mplayer2", + GURL("file://some_file.mov")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::OTHER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "application/x-mplayer2-some_sufix", + GURL("file://some_file.mov")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::OTHER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "some-prefix-application/x-mplayer2", + GURL("file://some_file.mov")); +} + +TEST(PluginUMATest, Silverlight) { + MockPluginUMASender* sender_mock = new MockPluginUMASender(); + MissingPluginReporter::GetInstance()->SetUMASender(sender_mock); + EXPECT_CALL(*sender_mock, SendPluginUMA(_)) + .Times(0); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::SILVERLIGHT)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "application/x-silverlight", + GURL("aaaa")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::SILVERLIGHT)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "application/x-silverlight-some-sufix", + GURL("aaaa")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::OTHER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "some-prefix-application/x-silverlight", + GURL("aaaa")); +} + +TEST(PluginUMATest, RealPlayer) { + MockPluginUMASender* sender_mock = new MockPluginUMASender(); + MissingPluginReporter::GetInstance()->SetUMASender(sender_mock); + EXPECT_CALL(*sender_mock, SendPluginUMA(_)) + .Times(0); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::REALPLAYER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "audio/x-pn-realaudio", + GURL("some url")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::REALPLAYER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "audio/x-pn-realaudio-some-sufix", + GURL("some url")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::OTHER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "some-prefix-audio/x-pn-realaudio", + GURL("some url")); +} + +TEST(PluginUMATest, Java) { + MockPluginUMASender* sender_mock = new MockPluginUMASender(); + MissingPluginReporter::GetInstance()->SetUMASender(sender_mock); + EXPECT_CALL(*sender_mock, SendPluginUMA(_)) + .Times(0); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::JAVA)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "application/x-java-applet", + GURL("some url")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::JAVA)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "application/x-java-applet-some-sufix", + GURL("some url")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::JAVA)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "some-prefix-application/x-java-applet-sufix", + GURL("some url")); +} + +TEST(PluginUMATest, QuickTime) { + MockPluginUMASender* sender_mock = new MockPluginUMASender(); + MissingPluginReporter::GetInstance()->SetUMASender(sender_mock); + EXPECT_CALL(*sender_mock, SendPluginUMA(_)) + .Times(0); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::QUICKTIME)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "video/quicktime", + GURL("some url")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::OTHER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "video/quicktime-sufix", + GURL("some url")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::OTHER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "prefix-video/quicktime", + GURL("some url")); +} + +TEST(PluginUMATest, BySrcExtension) { + MockPluginUMASender* sender_mock = new MockPluginUMASender(); + MissingPluginReporter::GetInstance()->SetUMASender(sender_mock); + EXPECT_CALL(*sender_mock, SendPluginUMA(_)) + .Times(0); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::QUICKTIME)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "", + GURL("file://file.mov")); + + // When plugin's mime type is given, we don't check extension. + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::OTHER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "unknown-plugin", + GURL("http://file.mov")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::OTHER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "", + GURL("http://file.unknown_extension")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::QUICKTIME)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "", + GURL("http://aaa/file.mov?x=aaaa&y=b#c")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::QUICKTIME)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "", + GURL("http://file.mov?x=aaaa&y=b#c")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::OTHER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "", + GURL("http://")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::OTHER)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "", + GURL("mov")); +} + +TEST(PluginUMATest, CaseSensitivity) { + MockPluginUMASender* sender_mock = new MockPluginUMASender(); + MissingPluginReporter::GetInstance()->SetUMASender(sender_mock); + EXPECT_CALL(*sender_mock, SendPluginUMA(_)) + .Times(0); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::QUICKTIME)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "video/QUICKTIME", + GURL("http://file.aaa")); + + EXPECT_CALL(*sender_mock, SendPluginUMA(MissingPluginReporter::QUICKTIME)) + .Times(1) + .RetiresOnSaturation(); + MissingPluginReporter::GetInstance()->ReportPluginMissing( + "", + GURL("http://file.MoV")); +} + |