Commit 4bd58064 authored by dfalcantara's avatar dfalcantara Committed by Commit bot

[App banners] Start adding unit tets

Add basic unit tests for ensuring that the banner appears,
appears after being ignored, and appears after being blocked.

BUG=457414
TEST=AppBannerManagerTest

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

Cr-Commit-Position: refs/heads/master@{#316690}
parent 6863146f
...@@ -2440,6 +2440,14 @@ public class Tab implements ViewGroup.OnHierarchyChangeListener, ...@@ -2440,6 +2440,14 @@ public class Tab implements ViewGroup.OnHierarchyChangeListener,
nativeSetInterceptNavigationDelegate(mNativeTabAndroid, delegate); nativeSetInterceptNavigationDelegate(mNativeTabAndroid, delegate);
} }
/**
* @return the AppBannerManager.
*/
@VisibleForTesting
public AppBannerManager getAppBannerManagerForTesting() {
return mAppBannerManager;
}
/** /**
* Ensures the counter is at least as high as the specified value. The counter should always * Ensures the counter is at least as high as the specified value. The counter should always
* point to an unused ID (which will be handed out next time a request comes in). Exposed so * point to an unused ID (which will be handed out next time a request comes in). Exposed so
......
...@@ -9,6 +9,7 @@ import android.text.TextUtils; ...@@ -9,6 +9,7 @@ import android.text.TextUtils;
import org.chromium.base.ApplicationStatus; import org.chromium.base.ApplicationStatus;
import org.chromium.base.CalledByNative; import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace; import org.chromium.base.JNINamespace;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.EmptyTabObserver; import org.chromium.chrome.browser.EmptyTabObserver;
import org.chromium.chrome.browser.Tab; import org.chromium.chrome.browser.Tab;
...@@ -32,6 +33,9 @@ public class AppBannerManager extends EmptyTabObserver { ...@@ -32,6 +33,9 @@ public class AppBannerManager extends EmptyTabObserver {
/** Retrieves information about a given package. */ /** Retrieves information about a given package. */
private static AppDetailsDelegate sAppDetailsDelegate; private static AppDetailsDelegate sAppDetailsDelegate;
/** Whether the banners are enabled. */
private static Boolean sIsEnabled;
/** Pointer to the native side AppBannerManager. */ /** Pointer to the native side AppBannerManager. */
private final long mNativePointer; private final long mNativePointer;
...@@ -43,7 +47,8 @@ public class AppBannerManager extends EmptyTabObserver { ...@@ -43,7 +47,8 @@ public class AppBannerManager extends EmptyTabObserver {
* @return True if banners are enabled, false otherwise. * @return True if banners are enabled, false otherwise.
*/ */
public static boolean isEnabled() { public static boolean isEnabled() {
return nativeIsEnabled(); if (sIsEnabled == null) sIsEnabled = nativeIsEnabled();
return sIsEnabled;
} }
/** /**
...@@ -129,6 +134,24 @@ public class AppBannerManager extends EmptyTabObserver { ...@@ -129,6 +134,24 @@ public class AppBannerManager extends EmptyTabObserver {
}; };
} }
/** Enables the app banners for testing. */
@VisibleForTesting
static void setIsEnabledForTesting() {
sIsEnabled = true;
}
/** Sets a constant (in days) that gets added to the time when the current time is requested. */
@VisibleForTesting
static void setTimeDeltaForTesting(int days) {
nativeSetTimeDeltaForTesting(days);
}
/** Records how many native BitmapFetchers are actively retrieving app icons. */
@VisibleForTesting
public int getNumActiveFetchersForTesting() {
return nativeGetNumActiveFetchers(mNativePointer);
}
private static native boolean nativeIsEnabled(); private static native boolean nativeIsEnabled();
private native long nativeInit(); private native long nativeInit();
private native void nativeDestroy(long nativeAppBannerManager); private native void nativeDestroy(long nativeAppBannerManager);
...@@ -137,6 +160,10 @@ public class AppBannerManager extends EmptyTabObserver { ...@@ -137,6 +160,10 @@ public class AppBannerManager extends EmptyTabObserver {
private native boolean nativeOnAppDetailsRetrieved(long nativeAppBannerManager, AppData data, private native boolean nativeOnAppDetailsRetrieved(long nativeAppBannerManager, AppData data,
String title, String packageName, String imageUrl); String title, String packageName, String imageUrl);
// Testing methods.
private static native void nativeSetTimeDeltaForTesting(int days);
private native int nativeGetNumActiveFetchers(long nativeAppBannerManager);
// UMA tracking. // UMA tracking.
private static native void nativeRecordDismissEvent(int metric); private static native void nativeRecordDismissEvent(int metric);
private static native void nativeRecordInstallEvent(int metric); private static native void nativeRecordInstallEvent(int metric);
......
// 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.
package org.chromium.chrome.browser.banners;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.Tab;
import org.chromium.chrome.browser.infobar.AnimationHelper;
import org.chromium.chrome.browser.infobar.AppBannerInfoBar;
import org.chromium.chrome.browser.infobar.InfoBar;
import org.chromium.chrome.browser.infobar.InfoBarContainer;
import org.chromium.chrome.shell.ChromeShellTestBase;
import org.chromium.chrome.test.util.TestHttpServerClient;
import org.chromium.chrome.test.util.browser.TabLoadObserver;
import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content.browser.test.util.TouchCommon;
import java.util.ArrayList;
/**
* Tests the app banners.
*/
public class AppBannerManagerTest extends ChromeShellTestBase {
private static final String NATIVE_APP_URL =
TestHttpServerClient.getUrl("chrome/test/data/android/banners/native_app_test.html");
private static final String APP_ICON_URL =
TestHttpServerClient.getUrl("chrome/test/data/android/banners/native_app_test.png");
private static final String APP_TITLE = "Mock app title";
private static class MockAppDetailsDelegate extends AppDetailsDelegate {
private Observer mObserver;
private AppData mAppData;
private int mNumRetrieved;
@Override
protected void getAppDetailsAsynchronously(
Observer observer, String url, String packageName, int iconSize) {
mNumRetrieved += 1;
mObserver = observer;
mAppData = new AppData(url, packageName);
mAppData.setPackageInfo(APP_TITLE, APP_ICON_URL, 4.5f,
"Install this", null, null);
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
mObserver.onAppDetailsRetrieved(mAppData);
}
});
}
@Override
public void destroy() {
}
}
private static class InfobarListener implements InfoBarContainer.InfoBarAnimationListener {
private boolean mDoneAnimating;
@Override
public void notifyAnimationFinished(int animationType) {
if (animationType == AnimationHelper.ANIMATION_TYPE_SHOW) mDoneAnimating = true;
}
}
private MockAppDetailsDelegate mDetailsDelegate;
private InfoBarContainer mInfoBarContainer;
private AppBannerManager mAppBannerManager;
private Tab mActivityTab;
@Override
protected void setUp() throws Exception {
mDetailsDelegate = new MockAppDetailsDelegate();
AppBannerManager.setAppDetailsDelegate(mDetailsDelegate);
AppBannerManager.setIsEnabledForTesting();
clearAppData();
super.setUp();
launchChromeShellWithUrl("about:blank");
assertTrue(waitForActiveShellToBeDoneLoading());
mActivityTab = getActivity().getActiveTab();
mInfoBarContainer = mActivityTab.getInfoBarContainer();
mAppBannerManager = mActivityTab.getAppBannerManagerForTesting();
}
private boolean waitUntilNoInfoBarsExist() throws Exception {
return CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return mInfoBarContainer.getInfoBars().size() == 0;
}
});
}
private boolean waitUntilAppDetailsRetrieved(final int numExpected) throws Exception {
return CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return mDetailsDelegate.mNumRetrieved == numExpected
&& mAppBannerManager.getNumActiveFetchersForTesting() == 0;
}
});
}
private boolean waitUntilAppBannerInfoBarAppears() throws Exception {
return CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
ArrayList<InfoBar> infobars = mInfoBarContainer.getInfoBars();
if (infobars.size() != 1) return false;
if (!(infobars.get(0) instanceof AppBannerInfoBar)) return false;
TextView textView =
(TextView) infobars.get(0).getContentWrapper().findViewById(R.id.app_name);
if (textView == null) return false;
return TextUtils.equals(textView.getText(), APP_TITLE);
}
});
}
@SmallTest
@Feature({"AppBanners"})
public void testBannerAppears() throws Exception {
// Visit a site that requests a banner.
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(1));
assertTrue(waitUntilNoInfoBarsExist());
// Indicate a day has passed, then revisit the page.
AppBannerManager.setTimeDeltaForTesting(1);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(2));
assertTrue(waitUntilAppBannerInfoBarAppears());
}
@MediumTest
@Feature({"AppBanners"})
public void testBannerAppearsThenDoesNotAppearAgainForMonths() throws Exception {
// Visit a site that requests a banner.
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(1));
assertTrue(waitUntilNoInfoBarsExist());
// Indicate a day has passed, then revisit the page.
AppBannerManager.setTimeDeltaForTesting(1);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(2));
assertTrue(waitUntilAppBannerInfoBarAppears());
// Revisit the page to make the banner go away, but don't explicitly dismiss it.
// This hides the banner for a few months.
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(3));
assertTrue(waitUntilNoInfoBarsExist());
// Wait a month until revisiting the page.
AppBannerManager.setTimeDeltaForTesting(31);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(4));
assertTrue(waitUntilNoInfoBarsExist());
AppBannerManager.setTimeDeltaForTesting(32);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(5));
assertTrue(waitUntilNoInfoBarsExist());
// Wait two months until revisiting the page, which should pop up the banner.
AppBannerManager.setTimeDeltaForTesting(61);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(6));
assertTrue(waitUntilNoInfoBarsExist());
AppBannerManager.setTimeDeltaForTesting(62);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(7));
assertTrue(waitUntilAppBannerInfoBarAppears());
}
@MediumTest
@Feature({"AppBanners"})
public void testBlockedBannerDoesNotAppearAgainForMonths() throws Exception {
// Visit a site that requests a banner.
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(1));
assertTrue(waitUntilNoInfoBarsExist());
// Indicate a day has passed, then revisit the page.
final InfobarListener listener = new InfobarListener();
mInfoBarContainer.setAnimationListener(listener);
AppBannerManager.setTimeDeltaForTesting(1);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(2));
assertTrue(waitUntilAppBannerInfoBarAppears());
// Explicitly dismiss the banner.
assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return listener.mDoneAnimating;
}
}));
ArrayList<InfoBar> infobars = mInfoBarContainer.getInfoBars();
View close = infobars.get(0).getContentWrapper().findViewById(R.id.infobar_close_button);
TouchCommon.singleClickView(close);
assertTrue(waitUntilNoInfoBarsExist());
// Waiting two months shouldn't be long enough.
AppBannerManager.setTimeDeltaForTesting(61);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(3));
assertTrue(waitUntilNoInfoBarsExist());
AppBannerManager.setTimeDeltaForTesting(62);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(4));
assertTrue(waitUntilNoInfoBarsExist());
// Waiting three months should allow banners to reappear.
AppBannerManager.setTimeDeltaForTesting(91);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(5));
assertTrue(waitUntilNoInfoBarsExist());
AppBannerManager.setTimeDeltaForTesting(92);
assertTrue(CriteriaHelper.pollForUIThreadCriteria(
new TabLoadObserver(getActivity().getActiveTab(), NATIVE_APP_URL)));
assertTrue(waitUntilAppDetailsRetrieved(6));
assertTrue(waitUntilAppBannerInfoBarAppears());
}
}
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/threading/worker_pool.h" #include "base/threading/worker_pool.h"
#include "chrome/browser/android/banners/app_banner_manager.h"
#include "chrome/browser/android/shortcut_helper.h" #include "chrome/browser/android/shortcut_helper.h"
#include "chrome/browser/android/shortcut_info.h" #include "chrome/browser/android/shortcut_info.h"
#include "chrome/browser/android/tab_android.h" #include "chrome/browser/android/tab_android.h"
...@@ -114,12 +115,14 @@ void AppBannerInfoBarDelegate::InfoBarDismissed() { ...@@ -114,12 +115,14 @@ void AppBannerInfoBarDelegate::InfoBarDismissed() {
AppBannerSettingsHelper::RecordBannerEvent( AppBannerSettingsHelper::RecordBannerEvent(
web_contents, web_contents->GetURL(), web_contents, web_contents->GetURL(),
native_app_package_, native_app_package_,
AppBannerSettingsHelper::APP_BANNER_EVENT_DID_BLOCK, base::Time::Now()); AppBannerSettingsHelper::APP_BANNER_EVENT_DID_BLOCK,
AppBannerManager::GetCurrentTime());
} else if (!web_app_data_.IsEmpty()) { } else if (!web_app_data_.IsEmpty()) {
AppBannerSettingsHelper::RecordBannerEvent( AppBannerSettingsHelper::RecordBannerEvent(
web_contents, web_contents->GetURL(), web_contents, web_contents->GetURL(),
web_app_data_.start_url.spec(), web_app_data_.start_url.spec(),
AppBannerSettingsHelper::APP_BANNER_EVENT_DID_BLOCK, base::Time::Now()); AppBannerSettingsHelper::APP_BANNER_EVENT_DID_BLOCK,
AppBannerManager::GetCurrentTime());
} }
} }
...@@ -146,7 +149,7 @@ bool AppBannerInfoBarDelegate::Accept() { ...@@ -146,7 +149,7 @@ bool AppBannerInfoBarDelegate::Accept() {
web_contents, web_contents->GetURL(), web_contents, web_contents->GetURL(),
web_app_data_.start_url.spec(), web_app_data_.start_url.spec(),
AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN, AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN,
base::Time::Now()); AppBannerManager::GetCurrentTime());
ShortcutInfo info; ShortcutInfo info;
info.UpdateFromManifest(web_app_data_); info.UpdateFromManifest(web_app_data_);
...@@ -201,7 +204,7 @@ void AppBannerInfoBarDelegate::OnInstallIntentReturned( ...@@ -201,7 +204,7 @@ void AppBannerInfoBarDelegate::OnInstallIntentReturned(
web_contents->GetURL(), web_contents->GetURL(),
native_app_package_, native_app_package_,
AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN, AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN,
base::Time::Now()); AppBannerManager::GetCurrentTime());
} }
UpdateInstallState(env, obj); UpdateInstallState(env, obj);
......
...@@ -44,6 +44,7 @@ using base::android::ConvertUTF16ToJavaString; ...@@ -44,6 +44,7 @@ using base::android::ConvertUTF16ToJavaString;
namespace { namespace {
const char kBannerTag[] = "google-play-id"; const char kBannerTag[] = "google-play-id";
base::TimeDelta gTimeDeltaForTesting;
} }
namespace banners { namespace banners {
...@@ -144,20 +145,20 @@ void AppBannerManager::RecordCouldShowBanner( ...@@ -144,20 +145,20 @@ void AppBannerManager::RecordCouldShowBanner(
const std::string& package_or_start_url) { const std::string& package_or_start_url) {
AppBannerSettingsHelper::RecordBannerEvent( AppBannerSettingsHelper::RecordBannerEvent(
web_contents(), validated_url_, package_or_start_url, web_contents(), validated_url_, package_or_start_url,
AppBannerSettingsHelper::APP_BANNER_EVENT_COULD_SHOW, base::Time::Now()); AppBannerSettingsHelper::APP_BANNER_EVENT_COULD_SHOW, GetCurrentTime());
} }
bool AppBannerManager::CheckIfShouldShow( bool AppBannerManager::CheckIfShouldShow(
const std::string& package_or_start_url) { const std::string& package_or_start_url) {
if (!AppBannerSettingsHelper::ShouldShowBanner(web_contents(), validated_url_, if (!AppBannerSettingsHelper::ShouldShowBanner(web_contents(), validated_url_,
package_or_start_url, package_or_start_url,
base::Time::Now())) { GetCurrentTime())) {
return false; return false;
} }
AppBannerSettingsHelper::RecordBannerEvent( AppBannerSettingsHelper::RecordBannerEvent(
web_contents(), validated_url_, package_or_start_url, web_contents(), validated_url_, package_or_start_url,
AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, base::Time::Now()); AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, GetCurrentTime());
return true; return true;
} }
...@@ -258,6 +259,10 @@ bool AppBannerManager::OnAppDetailsRetrieved(JNIEnv* env, ...@@ -258,6 +259,10 @@ bool AppBannerManager::OnAppDetailsRetrieved(JNIEnv* env,
return FetchIcon(GURL(image_url)); return FetchIcon(GURL(image_url));
} }
int AppBannerManager::GetNumActiveFetchers(JNIEnv* env, jobject obj) {
return fetcher_.get() ? 1 : 0;
}
bool AppBannerManager::FetchIcon(const GURL& image_url) { bool AppBannerManager::FetchIcon(const GURL& image_url) {
if (!web_contents()) if (!web_contents())
return false; return false;
...@@ -284,6 +289,11 @@ int AppBannerManager::GetPreferredIconSize() { ...@@ -284,6 +289,11 @@ int AppBannerManager::GetPreferredIconSize() {
return Java_AppBannerManager_getPreferredIconSize(env, jobj.obj()); return Java_AppBannerManager_getPreferredIconSize(env, jobj.obj());
} }
// static
base::Time AppBannerManager::GetCurrentTime() {
return base::Time::Now() + gTimeDeltaForTesting;
}
void RecordDismissEvent(JNIEnv* env, jclass clazz, jint metric) { void RecordDismissEvent(JNIEnv* env, jclass clazz, jint metric) {
banners::TrackDismissEvent(metric); banners::TrackDismissEvent(metric);
} }
...@@ -302,6 +312,10 @@ jboolean IsEnabled(JNIEnv* env, jclass clazz) { ...@@ -302,6 +312,10 @@ jboolean IsEnabled(JNIEnv* env, jclass clazz) {
switches::kEnableAppInstallAlerts); switches::kEnableAppInstallAlerts);
} }
void SetTimeDeltaForTesting(JNIEnv* env, jclass clazz, jint days) {
gTimeDeltaForTesting = base::TimeDelta::FromDays(days);
}
// Register native methods // Register native methods
bool RegisterAppBannerManager(JNIEnv* env) { bool RegisterAppBannerManager(JNIEnv* env) {
return RegisterNativesImpl(env); return RegisterNativesImpl(env);
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/android/banners/app_banner_infobar_delegate.h" #include "chrome/browser/android/banners/app_banner_infobar_delegate.h"
#include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h" #include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
#include "chrome/browser/ui/android/infobars/app_banner_infobar.h" #include "chrome/browser/ui/android/infobars/app_banner_infobar.h"
...@@ -93,6 +94,12 @@ class AppBannerManager : public chrome::BitmapFetcherDelegate, ...@@ -93,6 +94,12 @@ class AppBannerManager : public chrome::BitmapFetcherDelegate,
// Returns |false| if this couldn't be kicked off. // Returns |false| if this couldn't be kicked off.
bool FetchIcon(const GURL& image_url); bool FetchIcon(const GURL& image_url);
// Return how many fetchers are active.
int GetNumActiveFetchers(JNIEnv* env, jobject jobj);
// Returns the current time.
static base::Time GetCurrentTime();
// WebContentsObserver overrides. // WebContentsObserver overrides.
void DidNavigateMainFrame( void DidNavigateMainFrame(
const content::LoadCommittedDetails& details, const content::LoadCommittedDetails& details,
......
<html>
<head>
<title>AppBannerManager test page</title>
<meta name="google-play-id" content="test.package" />
</head>
<body>
Promoting the "test.package" package.
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment