summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/chrome_renderer.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/renderer/blocked_plugin.cc5
-rw-r--r--chrome/renderer/chrome_content_renderer_client.cc22
-rw-r--r--chrome/renderer/chrome_content_renderer_client.h6
-rw-r--r--chrome/renderer/plugin_uma.cc164
-rw-r--r--chrome/renderer/plugin_uma.h70
-rw-r--r--chrome/renderer/plugin_uma_unittest.cc235
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"));
+}
+