Defer media playback in background tabs.

Videos which autoplay in the background will now have their load
deferred until the tab is visible for the first time -- this avoids
autoplay during session restore and premature playback.

Once a tab / RenderFrame has ever played media before, it's allowed
to continue to autoplay/autoload indefinitely; this is to support
playlist type applications.

BUG=509135
TEST=manual verification that background tabs don't autoplay;
playlist type media continues to work.

Review URL: https://codereview.chromium.org/1292433002

Cr-Commit-Position: refs/heads/master@{#344092}
diff --git a/chrome/browser/media/defer_background_media_browsertest.cc b/chrome/browser/media/defer_background_media_browsertest.cc
new file mode 100644
index 0000000..daa3e6b
--- /dev/null
+++ b/chrome/browser/media/defer_background_media_browsertest.cc
@@ -0,0 +1,60 @@
+// Copyright 2015 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 "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/browser_test_utils.h"
+#include "media/base/test_data_util.h"
+
+// TODO(dalecurtis): Android does not correctly defer media today, fix and
+// enable this test (needs disable of user gesture).  http://crbug.com/522157
+#if !defined(OS_ANDROID)
+typedef InProcessBrowserTest MediaBrowserTest;
+
+IN_PROC_BROWSER_TEST_F(MediaBrowserTest, BackgroundMediaIsDeferred) {
+  // Navigate to a video file, which would autoplay in the foreground, but won't
+  // in the background due to deferred media loading for hidden tabs.
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), content::GetFileUrlWithQuery(
+                     media::GetTestDataFilePath("bear-640x360.webm"), ""),
+      NEW_BACKGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+
+  ASSERT_EQ(2, browser()->tab_strip_model()->count());
+  content::WebContents* background_contents =
+      browser()->tab_strip_model()->GetWebContentsAt(1);
+  EXPECT_TRUE(
+      content::WaitForRenderFrameReady(background_contents->GetMainFrame()));
+  EXPECT_NE(background_contents,
+            browser()->tab_strip_model()->GetActiveWebContents());
+
+  // If autoplay for background tabs isn't deferred the play event listener will
+  // be attached too late to catch the event, and subsequently the test will hit
+  // the ended event before the play event.
+  EXPECT_TRUE(
+      content::ExecuteScript(background_contents,
+                             "var video = document.querySelector('video');"
+                             "video.addEventListener('ended', function(event) {"
+                             "  document.title = 'ended';"
+                             "}, false);"
+                             "video.addEventListener('play', function(event) {"
+                             "  document.title = 'playing';"
+                             "}, false);"));
+
+  // Make the background tab w/ our video the active tab.
+  browser()->tab_strip_model()->SelectNextTab();
+  EXPECT_EQ(background_contents,
+            browser()->tab_strip_model()->GetActiveWebContents());
+
+  // If everything worked, we should see "playing" and not "ended".
+  const base::string16 playing_str = base::UTF8ToUTF16("playing");
+  const base::string16 ended_str = base::UTF8ToUTF16("ended");
+  content::TitleWatcher watcher(background_contents, playing_str);
+  watcher.AlsoWaitForTitle(ended_str);
+  EXPECT_EQ(playing_str, watcher.WaitAndGetTitle());
+}
+#endif  // !defined(OS_ANDROID)
diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi
index 281061d6..e835b61 100644
--- a/chrome/chrome_renderer.gypi
+++ b/chrome/chrome_renderer.gypi
@@ -156,10 +156,6 @@
       'renderer/resources/extensions/tts_custom_bindings.js',
       'renderer/resources/extensions/tts_engine_custom_bindings.js',
     ],
-    'chrome_renderer_non_android_sources': [
-      'renderer/prerender/prerender_media_load_deferrer.cc',
-      'renderer/prerender/prerender_media_load_deferrer.h',
-    ],
     'chrome_renderer_plugin_sources': [
       'renderer/pepper/chrome_renderer_pepper_host_factory.cc',
       'renderer/pepper/chrome_renderer_pepper_host_factory.h',
@@ -387,11 +383,6 @@
             '<@(chrome_renderer_full_printing_sources)',
           ],
         }],
-        ['OS!="android"', {
-          'sources': [
-            '<@(chrome_renderer_non_android_sources)',
-          ],
-        }],
         ['OS=="win"', {
           'dependencies': [
             '../components/components.gyp:dom_distiller_core',  # Needed by chrome_content_renderer_client.cc.
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index a93d497..4f83083 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -320,6 +320,7 @@
       'browser/media/chrome_webrtc_simulcast_browsertest.cc',
       'browser/media/chrome_webrtc_video_quality_browsertest.cc',
       'browser/media/chrome_webrtc_webcam_browsertest.cc',
+      'browser/media/defer_background_media_browsertest.cc',
       'browser/media/encrypted_media_browsertest.cc',
       'browser/media/encrypted_media_istypesupported_browsertest.cc',
       'browser/media/media_browsertest.cc',
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index ee9e894..d2cce4f 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -177,10 +177,6 @@
       "spellchecker/hunspell_engine.h",
     ]
   }
-  if (!is_android) {
-    sources +=
-        rebase_path(gypi_values.chrome_renderer_non_android_sources, ".", "..")
-  }
 }
 
 # In GYP this is part of test_support_common.
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 34ee044..88a3fe0 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -46,7 +46,6 @@
 #include "chrome/renderer/plugins/shadow_dom_plugin_placeholder.h"
 #include "chrome/renderer/prerender/prerender_dispatcher.h"
 #include "chrome/renderer/prerender/prerender_helper.h"
-#include "chrome/renderer/prerender/prerender_media_load_deferrer.h"
 #include "chrome/renderer/prerender/prerenderer_client.h"
 #include "chrome/renderer/safe_browsing/malware_dom_details.h"
 #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
@@ -341,6 +340,29 @@
       extensions::switches::kExtensionProcess);
 }
 #endif
+
+// Defers media player loading in background pages until they're visible.
+// TODO(dalecurtis): Include an idle listener too.  http://crbug.com/509135
+class MediaLoadDeferrer : public content::RenderFrameObserver {
+ public:
+  MediaLoadDeferrer(content::RenderFrame* render_frame,
+                    const base::Closure& continue_loading_cb)
+      : content::RenderFrameObserver(render_frame),
+        continue_loading_cb_(continue_loading_cb) {}
+  ~MediaLoadDeferrer() override {}
+
+ private:
+  // content::RenderFrameObserver implementation:
+  void WasShown() override {
+    continue_loading_cb_.Run();
+    delete this;
+  }
+
+  const base::Closure continue_loading_cb_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaLoadDeferrer);
+};
+
 }  // namespace
 
 ChromeContentRendererClient::ChromeContentRendererClient() {
@@ -683,20 +705,22 @@
 
 void ChromeContentRendererClient::DeferMediaLoad(
     content::RenderFrame* render_frame,
+    bool has_played_media_before,
     const base::Closure& closure) {
-#if defined(OS_ANDROID)
-  // Chromium for Android doesn't support prerender yet.
-  closure.Run();
-  return;
-#else
-  if (!prerender::PrerenderHelper::IsPrerendering(render_frame)) {
-    closure.Run();
+  // Don't allow autoplay/autoload of media resources in a RenderFrame that is
+  // hidden and has never played any media before.  We want to allow future
+  // loads even when hidden to allow playlist-like functionality.
+  //
+  // NOTE: This is also used to defer media loading for prerender.
+  //
+  // TODO(dalecurtis): Include an idle check too.  http://crbug.com/509135
+  if (render_frame->IsHidden() && !has_played_media_before) {
+    // Lifetime is tied to |render_frame| via content::RenderFrameObserver.
+    new MediaLoadDeferrer(render_frame, closure);
     return;
   }
 
-  // Lifetime is tied to |render_frame| via content::RenderFrameObserver.
-  new prerender::PrerenderMediaLoadDeferrer(render_frame, closure);
-#endif
+  closure.Run();
 }
 
 #if defined(ENABLE_PLUGINS)
diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h
index bafd24f..290217d 100644
--- a/chrome/renderer/chrome_content_renderer_client.h
+++ b/chrome/renderer/chrome_content_renderer_client.h
@@ -106,6 +106,7 @@
                                  std::string* error_html,
                                  base::string16* error_description) override;
   void DeferMediaLoad(content::RenderFrame* render_frame,
+                      bool has_played_media_before,
                       const base::Closure& closure) override;
   bool RunIdleHandlerWhenWidgetsHidden() override;
   bool AllowPopup() override;
diff --git a/chrome/renderer/prerender/prerender_media_load_deferrer.cc b/chrome/renderer/prerender/prerender_media_load_deferrer.cc
deleted file mode 100644
index 93bea76..0000000
--- a/chrome/renderer/prerender/prerender_media_load_deferrer.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2013 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/prerender/prerender_media_load_deferrer.h"
-
-#include "base/callback_helpers.h"
-#include "chrome/common/prerender_messages.h"
-
-namespace prerender {
-
-PrerenderMediaLoadDeferrer::PrerenderMediaLoadDeferrer(
-    content::RenderFrame* render_frame,
-    const base::Closure& closure)
-    : RenderFrameObserver(render_frame),
-      is_prerendering_(true),
-      continue_loading_cb_(closure) {
-  DCHECK(!continue_loading_cb_.is_null());
-}
-
-PrerenderMediaLoadDeferrer::~PrerenderMediaLoadDeferrer() {}
-
-bool PrerenderMediaLoadDeferrer::OnMessageReceived(
-    const IPC::Message& message) {
-  IPC_BEGIN_MESSAGE_MAP(PrerenderMediaLoadDeferrer, message)
-    IPC_MESSAGE_HANDLER(PrerenderMsg_SetIsPrerendering, OnSetIsPrerendering)
-  IPC_END_MESSAGE_MAP()
-
-  return false;
-}
-
-void PrerenderMediaLoadDeferrer::OnSetIsPrerendering(bool is_prerendering) {
-  // Prerendering can only be enabled prior to a RenderFrame's first
-  // navigation, so no PrerenderMediaLoadDeferrer should see the notification
-  // that enables prerendering.
-  DCHECK(!is_prerendering);
-  if (!is_prerendering_ || is_prerendering)
-    return;
-
-  is_prerendering_ = false;
-  base::ResetAndReturn(&continue_loading_cb_).Run();
-}
-
-}  // namespace prerender
diff --git a/chrome/renderer/prerender/prerender_media_load_deferrer.h b/chrome/renderer/prerender/prerender_media_load_deferrer.h
deleted file mode 100644
index 2c78389..0000000
--- a/chrome/renderer/prerender/prerender_media_load_deferrer.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2013 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_PRERENDER_PRERENDER_MEDIA_LOAD_DEFERRER_H_
-#define CHROME_RENDERER_PRERENDER_PRERENDER_MEDIA_LOAD_DEFERRER_H_
-
-#include "base/callback.h"
-#include "content/public/renderer/render_frame_observer.h"
-
-namespace prerender {
-
-// Defers media player loading in prerendered pages until the prerendered page
-// is swapped in.
-class PrerenderMediaLoadDeferrer : public content::RenderFrameObserver {
- public:
-  // Will run |closure| to continue loading the media resource once the page is
-  // swapped in.
-  PrerenderMediaLoadDeferrer(content::RenderFrame* render_frame,
-                             const base::Closure& closure);
-  ~PrerenderMediaLoadDeferrer() override;
-
- private:
-  // RenderFrameObserver method:
-  bool OnMessageReceived(const IPC::Message& message) override;
-
-  void OnSetIsPrerendering(bool is_prerendering);
-
-  bool is_prerendering_;
-  base::Closure continue_loading_cb_;
-
-  DISALLOW_COPY_AND_ASSIGN(PrerenderMediaLoadDeferrer);
-};
-
-}  // namespace prerender
-
-#endif  // CHROME_RENDERER_PRERENDER_PRERENDER_MEDIA_LOAD_DEFERRER_H_
diff --git a/chromecast/renderer/cast_content_renderer_client.cc b/chromecast/renderer/cast_content_renderer_client.cc
index 1f7c4a6..daebd98 100644
--- a/chromecast/renderer/cast_content_renderer_client.cc
+++ b/chromecast/renderer/cast_content_renderer_client.cc
@@ -210,6 +210,7 @@
 
 void CastContentRendererClient::DeferMediaLoad(
     content::RenderFrame* render_frame,
+    bool render_frame_has_played_media_before,
     const base::Closure& closure) {
   if (!render_frame->IsHidden()) {
     closure.Run();
diff --git a/chromecast/renderer/cast_content_renderer_client.h b/chromecast/renderer/cast_content_renderer_client.h
index 0ee87ed..bdabd25 100644
--- a/chromecast/renderer/cast_content_renderer_client.h
+++ b/chromecast/renderer/cast_content_renderer_client.h
@@ -51,6 +51,7 @@
 #endif
   blink::WebPrescientNetworking* GetPrescientNetworking() override;
   void DeferMediaLoad(content::RenderFrame* render_frame,
+                      bool render_frame_has_played_media_before,
                       const base::Closure& closure) override;
 
  protected:
diff --git a/content/public/renderer/content_renderer_client.cc b/content/public/renderer/content_renderer_client.cc
index 95af2f7..95133390 100644
--- a/content/public/renderer/content_renderer_client.cc
+++ b/content/public/renderer/content_renderer_client.cc
@@ -51,8 +51,10 @@
   return false;
 }
 
-void ContentRendererClient::DeferMediaLoad(RenderFrame* render_frame,
-                                           const base::Closure& closure) {
+void ContentRendererClient::DeferMediaLoad(
+    RenderFrame* render_frame,
+    bool has_played_media_before,
+    const base::Closure& closure) {
   closure.Run();
 }
 
diff --git a/content/public/renderer/content_renderer_client.h b/content/public/renderer/content_renderer_client.h
index 837ba55..b1b0527 100644
--- a/content/public/renderer/content_renderer_client.h
+++ b/content/public/renderer/content_renderer_client.h
@@ -151,8 +151,10 @@
 
   // Allows the embedder to control when media resources are loaded. Embedders
   // can run |closure| immediately if they don't wish to defer media resource
-  // loading.
+  // loading.  If |has_played_media_before| is true, the render frame has
+  // previously started media playback (i.e. played audio and video).
   virtual void DeferMediaLoad(RenderFrame* render_frame,
+                              bool has_played_media_before,
                               const base::Closure& closure);
 
   // Allows the embedder to override creating a WebMediaStreamCenter. If it
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 05e6ad6..d29b700 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -676,6 +676,7 @@
 #if defined(VIDEO_HOLE)
       contains_media_player_(false),
 #endif
+      has_played_media_(false),
       devtools_agent_(nullptr),
       geolocation_dispatcher_(NULL),
       push_messaging_dispatcher_(NULL),
@@ -2041,7 +2042,7 @@
   media::WebMediaPlayerParams params(
       base::Bind(&ContentRendererClient::DeferMediaLoad,
                  base::Unretained(GetContentClient()->renderer()),
-                 static_cast<RenderFrame*>(this)),
+                 static_cast<RenderFrame*>(this), has_played_media_),
       render_thread->GetAudioRendererMixerManager()->CreateInput(routing_id_),
       media_log, render_thread->GetMediaThreadTaskRunner(),
       render_thread->compositor_task_runner(),
@@ -3827,6 +3828,7 @@
 #endif
 
 void RenderFrameImpl::DidPlay(WebMediaPlayer* player) {
+  has_played_media_ = true;
   Send(new FrameHostMsg_MediaPlayingNotification(
       routing_id_, reinterpret_cast<int64>(player), player->hasVideo(),
       player->hasAudio(), player->isRemote()));
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 321248f..cdeba40c 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -966,6 +966,9 @@
   bool contains_media_player_;
 #endif
 
+  // True if this RenderFrame has ever played media.
+  bool has_played_media_;
+
   // The devtools agent for this frame; only created for main frame and
   // local roots.
   DevToolsAgent* devtools_agent_;