diff --git a/DEPS b/DEPS index 556c173..8ace85df8e 100644 --- a/DEPS +++ b/DEPS
@@ -45,7 +45,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': '90f28ec3da530d1720a0a74283a44cfd9c207126', + 'skia_revision': '6e0a6b3c40de254268b48deea6184cf10e945bd4', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. @@ -69,7 +69,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. - 'pdfium_revision': '7d04f1b0ab4848f1d10983b7a7b1444ac93dec70', + 'pdfium_revision': '3070e94f608f36e7312fad2d471e3d7546f82ca2', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling openmax_dl # and whatever else without interference from each other. @@ -101,7 +101,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. - 'catapult_revision': 'cf05c91b67574954c0f62d369077943f04f5de07', + 'catapult_revision': 'a17499a8648f2707aa6831683ab5db2319ac2534', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libFuzzer # and whatever else without interference from each other.
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/PostMessageTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/PostMessageTest.java index 0a4274fc..9e6ba24 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/PostMessageTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/PostMessageTest.java
@@ -639,11 +639,10 @@ // transferred to JS and full communication can happen on it. // Do this by sending a message to JS and let it echo'ing the message with // some text prepended to it. - // Disabled because flaky, see crbug.com/715960. - // @SmallTest - // @Feature({"AndroidWebView", "Android-PostMessage"}) + @RetryOnFailure // crbug.com/715960 + @SmallTest + @Feature({"AndroidWebView", "Android-PostMessage"}) @Test - @DisabledTest public void testMessageChannelUsingPendingPort() throws Throwable { final ChannelContainer channelContainer = new ChannelContainer(); loadPage(ECHO_PAGE); @@ -720,10 +719,10 @@ // 3. Java sends a message using the new channel in 2. // 4. Js responds to this message using the channel in 2. // 5. Java responds to message in 4 using the channel in 2. - // @SmallTest - // @Feature({"AndroidWebView", "Android-PostMessage"}) + @RetryOnFailure // crbug.com/708821 + @SmallTest + @Feature({"AndroidWebView", "Android-PostMessage"}) @Test - @DisabledTest public void testCanUseReceivedAwMessagePortFromJS() throws Throwable { loadPage(RECEIVE_JS_MESSAGE_CHANNEL_PAGE); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
diff --git a/ash/mus/main.cc b/ash/mus/main.cc index d152d51..edc6eb6 100644 --- a/ash/mus/main.cc +++ b/ash/mus/main.cc
@@ -10,6 +10,7 @@ const bool show_primary_host_on_connect = true; ash::mus::WindowManagerApplication* app = new ash::mus::WindowManagerApplication(show_primary_host_on_connect); + app->set_running_standalone(true); service_manager::ServiceRunner runner(app); return runner.Run(service_request_handle); }
diff --git a/ash/mus/window_manager_application.cc b/ash/mus/window_manager_application.cc index 18971f6..1ac4f34 100644 --- a/ash/mus/window_manager_application.cc +++ b/ash/mus/window_manager_application.cc
@@ -129,10 +129,11 @@ mojo_interface_factory::RegisterInterfaces( ®istry_, base::ThreadTaskRunnerHandle::Get()); + const bool register_path_provider = running_standalone_; aura_init_ = views::AuraInit::Create( context()->connector(), context()->identity(), "ash_mus_resources.pak", "ash_mus_resources_200.pak", nullptr, - views::AuraInit::Mode::AURA_MUS_WINDOW_MANAGER); + views::AuraInit::Mode::AURA_MUS_WINDOW_MANAGER, register_path_provider); if (!aura_init_) { context()->QuitNow(); return;
diff --git a/ash/mus/window_manager_application.h b/ash/mus/window_manager_application.h index 647af79f..2a3a579 100644 --- a/ash/mus/window_manager_application.h +++ b/ash/mus/window_manager_application.h
@@ -1,3 +1,4 @@ + // 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. @@ -61,6 +62,8 @@ service_manager::Connector* GetConnector(); + void set_running_standalone(bool value) { running_standalone_ = value; } + private: friend class ash::AshTestHelper; @@ -80,6 +83,7 @@ mojo::ScopedMessagePipeHandle interface_pipe) override; const bool show_primary_host_on_connect_; + bool running_standalone_ = false; std::unique_ptr<views::AuraInit> aura_init_;
diff --git a/ash/shelf/app_list_button.cc b/ash/shelf/app_list_button.cc index 346a016..e01832e7 100644 --- a/ash/shelf/app_list_button.cc +++ b/ash/shelf/app_list_button.cc
@@ -148,7 +148,17 @@ switch (event->type()) { case ui::ET_GESTURE_SCROLL_BEGIN: AnimateInkDrop(views::InkDropState::HIDDEN, event); - ImageButton::OnGestureEvent(event); + shelf_view_->PointerPressedOnButton(this, ShelfView::TOUCH, *event); + event->SetHandled(); + return; + case ui::ET_GESTURE_SCROLL_UPDATE: + shelf_view_->PointerDraggedOnButton(this, ShelfView::TOUCH, *event); + event->SetHandled(); + return; + case ui::ET_GESTURE_SCROLL_END: + case ui::ET_SCROLL_FLING_START: + shelf_view_->PointerReleasedOnButton(this, ShelfView::TOUCH, false); + event->SetHandled(); return; case ui::ET_GESTURE_TAP: case ui::ET_GESTURE_TAP_CANCEL:
diff --git a/ash/shelf/app_list_button_unittest.cc b/ash/shelf/app_list_button_unittest.cc index bff24bd1..7612284 100644 --- a/ash/shelf/app_list_button_unittest.cc +++ b/ash/shelf/app_list_button_unittest.cc
@@ -4,11 +4,9 @@ #include "ash/shelf/app_list_button.h" -#include "ash/public/cpp/config.h" #include "ash/root_window_controller.h" #include "ash/session/session_controller.h" #include "ash/shelf/shelf.h" -#include "ash/shelf/shelf_layout_manager.h" #include "ash/shelf/shelf_view.h" #include "ash/shelf/shelf_view_test_api.h" #include "ash/shell.h" @@ -21,7 +19,6 @@ #include "ui/app_list/presenter/app_list.h" #include "ui/app_list/presenter/test/test_app_list_presenter.h" #include "ui/events/event_constants.h" -#include "ui/events/test/event_generator.h" namespace ash { @@ -94,34 +91,6 @@ EXPECT_EQ(0u, test_app_list_presenter.voice_session_count()); } -TEST_F(AppListButtonTest, SwipingupToOpenFullscreenAppList) { - // TODO: investigate failure in mash, http://crbug.com/695686. - if (Shell::GetAshConfig() == Config::MASH) - return; - - Shelf* shelf = GetPrimaryShelf(); - EXPECT_EQ(SHELF_ALIGNMENT_BOTTOM, shelf->alignment()); - - // Start the drag from the center of the applist button's bottom. - gfx::Point center_point = app_list_button()->GetAppListButtonCenterPoint(); - gfx::Point start(center_point.x(), - center_point.y() + app_list_button()->height() / 2.f); - views::View::ConvertPointToScreen(app_list_button(), &start); - // Swiping up less than peeking threshold should keep the app list at PEEKING - // state. - gfx::Point end = - start - - gfx::Vector2d( - 0, ShelfLayoutManager::kAppListDragSnapToPeekingThreshold - 10); - GetEventGenerator().GestureScrollSequence( - start, end, base::TimeDelta::FromMilliseconds(100), 4 /* steps */); - RunAllPendingInMessageLoop(); - EXPECT_EQ(1u, test_app_list_presenter.show_count()); - EXPECT_GE(test_app_list_presenter.set_y_position_count(), 1u); - EXPECT_EQ(app_list::mojom::AppListState::PEEKING, - test_app_list_presenter.app_list_state()); -} - class VoiceInteractionAppListButtonTest : public AppListButtonTest { public: VoiceInteractionAppListButtonTest() {}
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc index 7326996..9a13c28 100644 --- a/ash/shelf/shelf_layout_manager_unittest.cc +++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -1656,7 +1656,6 @@ test_app_list_presenter.app_list_state()); // Swiping up more than the close threshold but less than peeking threshold - // should keep the app list at PEEKING state. delta.set_y(ShelfLayoutManager::kAppListDragSnapToPeekingThreshold - 10); end = start - delta; generator.GestureScrollSequence(start, end, kTimeDelta, kNumScrollSteps);
diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc index 4d6cf91..0ca42ad 100644 --- a/base/metrics/histogram.cc +++ b/base/metrics/histogram.cc
@@ -606,7 +606,7 @@ debug_string += allocation.context.type_name; debug_string += ")"; } - for (size_t i = 0; i < allocation.context.backtrace.frame_count; ++i) { + for (int i = allocation.context.backtrace.frame_count - 1; i >= 0; --i) { base::trace_event::StackFrame& frame = allocation.context.backtrace.frames[i]; switch (frame.type) {
diff --git a/build/android/play_services/update.py b/build/android/play_services/update.py index 5297b3cf..d672e2f 100755 --- a/build/android/play_services/update.py +++ b/build/android/play_services/update.py
@@ -14,13 +14,13 @@ import re import shutil import sys -import tempfile import zipfile sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) import devil_chromium from devil.utils import cmd_helper from play_services import utils +from py_utils import tempfile_ext from pylib import constants from pylib.constants import host_paths from pylib.utils import logging_utils @@ -47,10 +47,22 @@ LICENSE_FILE_NAME = 'LICENSE' ZIP_FILE_NAME = 'google_play_services_library.zip' -GMS_PACKAGE_ID = 'extras;google;m2repository' # used by sdk manager LICENSE_PATTERN = re.compile(r'^Pkg\.License=(?P<text>.*)$', re.MULTILINE) +# Template for a Maven settings.xml which instructs Maven to download to the +# given directory +MAVEN_SETTINGS_TEMPLATE = '''\ +<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 + https://maven.apache.org/xsd/settings-1.0.0.xsd" > + <localRepository>{}</localRepository> +</settings> +''' + +COM_GOOGLE_ANDROID_GMS = os.path.join('com', 'google', 'android', 'gms') +EXTRAS_GOOGLE_M2REPOSITORY = os.path.join('extras', 'google', 'm2repository') def main(raw_args): parser = argparse.ArgumentParser( @@ -71,7 +83,7 @@ # SDK Update arguments parser_sdk = subparsers.add_parser( 'sdk', - help='get the latest Google Play services SDK using Android SDK Manager', + help='get the latest Google Play services SDK using Maven', description=UpdateSdk.__doc__, formatter_class=utils.DefaultsRawHelpFormatter) parser_sdk.set_defaults(func=UpdateSdk) @@ -103,6 +115,10 @@ instead of `foo.py --debug upload --force` ''' + parser.add_argument('--config', + help='JSON Configuration file', + default=CONFIG_DEFAULT_PATH) + parser.add_argument('--sdk-root', help='base path to the Android SDK tools root', default=constants.ANDROID_SDK_ROOT) @@ -117,10 +133,6 @@ help='name of the bucket where the files are stored', default=GMS_CLOUD_STORAGE) - parser.add_argument('--config', - help='JSON Configuration file', - default=CONFIG_DEFAULT_PATH) - parser.add_argument('--dry-run', action='store_true', help=('run the script in dry run mode. Files will be ' @@ -173,11 +185,9 @@ config.version_number, args.dry_run) - tmp_root = tempfile.mkdtemp() - try: + with tempfile_ext.NamedTemporaryDirectory() as tmp_root: # setup the destination directory - if not os.path.isdir(paths.package): - os.makedirs(paths.package) + _MakeDirIfAbsent(paths.package) # download license file from bucket/{version_number}/license.sha1 new_license = os.path.join(tmp_root, LICENSE_FILE_NAME) @@ -228,17 +238,22 @@ 'An error occurred while installing the new version in ' 'the SDK directory: %s ', e) return -3 - finally: - shutil.rmtree(tmp_root) return 0 +def _MakeDirIfAbsent(path): + try: + os.makedirs(path) + except OSError as e: + if e.errno != os.errno.EEXIST: + raise + + def UpdateSdk(args): ''' - Uses the Android SDK Manager to download the latest Google Play services SDK - locally. Its usual installation path is - //third_party/android_tools/sdk/extras/google/m2repository + Uses Maven to download the latest Google Play Services SDK. Its installation + path is //third_party/android_tools/sdk/extras/google/m2repository. ''' # This should function should not run on bots and could fail for many user @@ -246,19 +261,43 @@ # disable breakpad to avoid spamming the logs. breakpad.IS_ENABLED = False - # `android update sdk` fails if the library is not installed yet, but it does - # not allow to install it from scratch using the command line. We then create - # a fake outdated installation. - paths = PlayServicesPaths(args.sdk_root, 'no_version_number', []) - if not os.path.isfile(paths.source_prop): - if not os.path.exists(os.path.dirname(paths.source_prop)): - os.makedirs(os.path.dirname(paths.source_prop)) - with open(paths.source_prop, 'w') as prop_file: - prop_file.write('Pkg.Revision=0.0.0\n') + config = utils.ConfigParser(args.config) - sdk_manager = os.path.join(args.sdk_root, 'tools', 'bin', 'sdkmanager') - cmd_helper.Call([sdk_manager, GMS_PACKAGE_ID]) - # If no update is needed, it still returns successfully so we just do nothing + # Remove the old SDK. + shutil.rmtree(os.path.join(args.sdk_root, EXTRAS_GOOGLE_M2REPOSITORY)) + + with tempfile_ext.NamedTemporaryDirectory() as temp_dir: + # Configure temp_dir as the Maven repository. + settings_path = os.path.join(temp_dir, 'settings.xml') + with open(settings_path, 'w') as settings: + settings.write(MAVEN_SETTINGS_TEMPLATE.format(temp_dir)) + + for client in config.clients: + # Download the client. + artifact = 'com.google.android.gms:{}:{}:aar'.format( + client, config.version_number) + try: + cmd_helper.Call([ + 'mvn', '--global-settings', settings_path, + 'org.apache.maven.plugins:maven-dependency-plugin:2.1:get', + '-DrepoUrl=https://maven.google.com', '-Dartifact=' + artifact]) + except OSError as e: + if e.errno == os.errno.ENOENT: + logging.error('mvn command not found. Please install Maven.') + return -1 + else: + raise + + # Copy the client .aar file from temp_dir into the tree. + src_path = os.path.join( + temp_dir, COM_GOOGLE_ANDROID_GMS, + client, config.version_number, + '{}-{}.aar'.format(client, config.version_number)) + dst_path = os.path.join( + args.sdk_root, EXTRAS_GOOGLE_M2REPOSITORY, COM_GOOGLE_ANDROID_GMS, + client, config.version_number) + _MakeDirIfAbsent(dst_path) + shutil.copy(src_path, dst_path) return 0 @@ -282,8 +321,7 @@ config.clients) logging.debug('-- Loaded paths --\n%s\n------------------', paths) - tmp_root = tempfile.mkdtemp() - try: + with tempfile_ext.NamedTemporaryDirectory() as tmp_root: new_lib_zip = os.path.join(tmp_root, ZIP_FILE_NAME) new_license = os.path.join(tmp_root, LICENSE_FILE_NAME) @@ -302,8 +340,6 @@ LICENSE_FILE_NAME + '.sha1') shutil.copy(new_lib_zip + '.sha1', new_lib_zip_sha1) shutil.copy(new_license + '.sha1', new_license_sha1) - finally: - shutil.rmtree(tmp_root) logging.info('Update to version %s complete', config.version_number) return 0 @@ -475,11 +511,10 @@ client_names: names of client libraries to be uploaded. See utils.ConfigParser for more info. ''' - relative_package = os.path.join('extras', 'google', 'm2repository') self.sdk_root = sdk_root self.version_number = version_number - self.package = os.path.join(sdk_root, relative_package) + self.package = os.path.join(sdk_root, EXTRAS_GOOGLE_M2REPOSITORY) self.lib_zip_sha1 = os.path.join(self.package, ZIP_FILE_NAME + '.sha1') self.license = os.path.join(self.package, LICENSE_FILE_NAME) self.source_prop = os.path.join(self.package, 'source.properties') @@ -487,8 +522,8 @@ self.client_paths = [] for client in client_names: self.client_paths.append(os.path.join( - self.package, 'com', 'google', 'android', 'gms', client, - version_number, '%s-%s.aar' % (client, version_number))) + self.package, COM_GOOGLE_ANDROID_GMS, client, version_number, + '{}-{}.aar'.format(client, version_number))) def __repr__(self): return ("\nsdk_root: " + self.sdk_root +
diff --git a/build/android/pylib/android/logdog_logcat_monitor.py b/build/android/pylib/android/logdog_logcat_monitor.py index 7b278db6..e1c4371 100644 --- a/build/android/pylib/android/logdog_logcat_monitor.py +++ b/build/android/pylib/android/logdog_logcat_monitor.py
@@ -37,7 +37,6 @@ try: super(LogdogLogcatMonitor, self)._StopRecording() if self._logdog_stream: - self._logcat_url = logdog_helper.get_viewer_url(self._stream_name) self._logdog_stream.close() except Exception as e: # pylint: disable=broad-except logging.exception('Unknown Error: %s.', e) @@ -51,6 +50,8 @@ self._adb.Logcat(clear=True) self._logdog_stream = logdog_helper.open_text(self._stream_name) + self._logcat_url = logdog_helper.get_viewer_url(self._stream_name) + logging.info('Logcat will be saved to %s', self._logcat_url) self._StartRecording() def _StartRecording(self):
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp index 77f0b0c..9b0b023 100644 --- a/chrome/app/settings_strings.grdp +++ b/chrome/app/settings_strings.grdp
@@ -1783,7 +1783,7 @@ Unknown carrier </message> <message name="IDS_ONC_VPN_HOST" desc="ONC Property label for VPN.Host"> - Host + Server hostname </message> <message name="IDS_ONC_VPN_L2TP_USERNAME" desc="ONC Property label for VPN.L2TP.Username"> Username @@ -1797,6 +1797,15 @@ <message name="IDS_ONC_VPN_TYPE" desc="ONC Property label for VPN.Type"> Provider type </message> + <message name="IDS_ONC_VPN_TYPE_L2TP_IPSEC" desc="ONC Property label for VPN.Type.L2TP-IPSec"> + L2TP/IPsec + </message> + <message name="IDS_ONC_VPN_TYPE_OPENVPN" desc="ONC Property label for VPN.Type.OpenVPN"> + Open VPN + </message> + <message name="IDS_ONC_VPN_TYPE_ARCVPN" desc="ONC Property label for VPN.Type.ARCVPN"> + Android VPN + </message> <message name="IDS_ONC_WIFI_FREQUENCY" desc="ONC Property label for WiFi.Frequency"> Frequency </message>
diff --git a/chrome/browser/chromeos/arc/arc_session_manager.cc b/chrome/browser/chromeos/arc/arc_session_manager.cc index 061f722..5c2c584 100644 --- a/chrome/browser/chromeos/arc/arc_session_manager.cc +++ b/chrome/browser/chromeos/arc/arc_session_manager.cc
@@ -26,6 +26,7 @@ #include "chrome/browser/chromeos/arc/policy/arc_android_management_checker.h" #include "chrome/browser/chromeos/arc/policy/arc_policy_util.h" #include "chrome/browser/chromeos/login/ui/login_display_host.h" +#include "chrome/browser/chromeos/login/wizard_controller.h" #include "chrome/browser/lifetime/application_lifetime.h" #include "chrome/browser/policy/profile_policy_connector.h" #include "chrome/browser/policy/profile_policy_connector_factory.h" @@ -163,17 +164,22 @@ // static bool ArcSessionManager::IsOobeOptInActive() { - // ARC OOBE OptIn is optional for now. Test if it exists and login host is - // active. - if (!user_manager::UserManager::Get()->IsCurrentUserNew()) + // Check if ARC ToS screen for OOBE or OPA OptIn flow is currently showing. + // TODO(b/65861628): Rename the method since it is no longer accurate. + // Redesign the OptIn flow since there is no longer reason to have two + // different OptIn flows. + chromeos::LoginDisplayHost* host = chromeos::LoginDisplayHost::default_host(); + if (!host) return false; - if (!base::CommandLine::ForCurrentProcess()->HasSwitch( - chromeos::switches::kEnableArcOOBEOptIn)) { + const chromeos::WizardController* wizard_controller = + host->GetWizardController(); + if (!wizard_controller) return false; - } - if (!chromeos::LoginDisplayHost::default_host()) + const chromeos::BaseScreen* screen = wizard_controller->current_screen(); + if (!screen) return false; - return true; + return screen->screen_id() == + chromeos::OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE; } // static
diff --git a/chrome/browser/chromeos/arc/arc_session_manager_unittest.cc b/chrome/browser/chromeos/arc/arc_session_manager_unittest.cc index b056d1e1..f3a0cec 100644 --- a/chrome/browser/chromeos/arc/arc_session_manager_unittest.cc +++ b/chrome/browser/chromeos/arc/arc_session_manager_unittest.cc
@@ -29,6 +29,7 @@ #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h" #include "chrome/browser/chromeos/login/users/scoped_user_manager_enabler.h" #include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h" +#include "chrome/browser/chromeos/login/wizard_controller.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/policy/profile_policy_connector.h" #include "chrome/browser/policy/profile_policy_connector_factory.h" @@ -48,6 +49,7 @@ #include "components/arc/test/fake_arc_session.h" #include "components/prefs/pref_service.h" #include "components/prefs/testing_pref_service.h" +#include "components/session_manager/core/session_manager.h" #include "components/signin/core/account_id/account_id.h" #include "components/sync/model/fake_sync_change_processor.h" #include "components/sync/model/sync_error_factory_mock.h" @@ -65,6 +67,21 @@ namespace { +class FakeBaseScreen : public chromeos::BaseScreen { + public: + explicit FakeBaseScreen(chromeos::OobeScreen screen_id) + : BaseScreen(nullptr, screen_id) {} + + ~FakeBaseScreen() override = default; + + // chromeos::BaseScreen: + void Show() override {} + void Hide() override {} + + private: + DISALLOW_COPY_AND_ASSIGN(FakeBaseScreen); +}; + class FakeLoginDisplayHost : public chromeos::LoginDisplayHost { public: FakeLoginDisplayHost() { @@ -91,8 +108,19 @@ void Finalize(base::OnceClosure) override {} void OpenProxySettings(const std::string& network_id) override {} void SetStatusAreaVisible(bool visible) override {} - void StartWizard(chromeos::OobeScreen first_screen) override {} - chromeos::WizardController* GetWizardController() override { return nullptr; } + void StartWizard(chromeos::OobeScreen first_screen) override { + // Reset the controller first since there could only be one wizard + // controller at any time. + wizard_controller_.reset(); + wizard_controller_ = + std::make_unique<chromeos::WizardController>(nullptr, nullptr); + + fake_screen_ = std::make_unique<FakeBaseScreen>(first_screen); + wizard_controller_->SetCurrentScreenForTesting(fake_screen_.get()); + } + chromeos::WizardController* GetWizardController() override { + return wizard_controller_.get(); + } chromeos::AppLaunchController* GetAppLaunchController() override { return nullptr; } @@ -111,6 +139,12 @@ bool IsVoiceInteractionOobe() override { return false; } private: + // SessionManager is required by the constructor of WizardController. + std::unique_ptr<session_manager::SessionManager> session_manager_ = + std::make_unique<session_manager::SessionManager>(); + std::unique_ptr<FakeBaseScreen> fake_screen_; + std::unique_ptr<chromeos::WizardController> wizard_controller_; + DISALLOW_COPY_AND_ASSIGN(FakeLoginDisplayHost); }; @@ -798,6 +832,10 @@ fake_login_display_host_ = base::MakeUnique<FakeLoginDisplayHost>(); } + FakeLoginDisplayHost* login_display_host() { + return fake_login_display_host_.get(); + } + void CloseLoginDisplayHost() { fake_login_display_host_.reset(); } void AppendEnableArcOOBEOptInSwitch() { @@ -812,21 +850,16 @@ }; TEST_F(ArcSessionOobeOptInTest, OobeOptInActive) { - // OOBE OptIn is active in case of OOBE is started for new user and ARC OOBE - // is enabled by switch. - EXPECT_FALSE(ArcSessionManager::IsOobeOptInActive()); - GetFakeUserManager()->set_current_user_new(true); + // OOBE OptIn is active in case of OOBE controller is alive and the ARC ToS + // screen is currently showing. EXPECT_FALSE(ArcSessionManager::IsOobeOptInActive()); CreateLoginDisplayHost(); EXPECT_FALSE(ArcSessionManager::IsOobeOptInActive()); - - AppendEnableArcOOBEOptInSwitch(); - GetFakeUserManager()->set_current_user_new(false); - CloseLoginDisplayHost(); + login_display_host()->StartWizard( + chromeos::OobeScreen::SCREEN_VOICE_INTERACTION_VALUE_PROP); EXPECT_FALSE(ArcSessionManager::IsOobeOptInActive()); - GetFakeUserManager()->set_current_user_new(true); - EXPECT_FALSE(ArcSessionManager::IsOobeOptInActive()); - CreateLoginDisplayHost(); + login_display_host()->StartWizard( + chromeos::OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE); EXPECT_TRUE(ArcSessionManager::IsOobeOptInActive()); } @@ -861,6 +894,10 @@ arc_session_manager()->SetProfile(profile()); arc_session_manager()->Initialize(); + + login_display_host()->StartWizard( + chromeos::OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE); + if (IsArcPlayStoreEnabledForProfile(profile())) arc_session_manager()->RequestEnable(); }
diff --git a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc index f016a8f..c030cf6a 100644 --- a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc +++ b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc
@@ -403,7 +403,7 @@ WallpaperManager::~WallpaperManager() { show_user_name_on_signin_subscription_.reset(); device_wallpaper_image_subscription_.reset(); - user_manager::UserManager::Get()->RemoveSessionStateObserver(this); + user_manager::UserManager::Get()->RemoveObserver(this); weak_factory_.InvalidateWeakPtrs(); } @@ -962,7 +962,7 @@ {base::MayBlock(), base::TaskPriority::USER_BLOCKING, base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}); - user_manager::UserManager::Get()->AddSessionStateObserver(this); + user_manager::UserManager::Get()->AddObserver(this); content::ServiceManagerConnection* connection = content::ServiceManagerConnection::GetForProcess(); @@ -1381,8 +1381,8 @@ return false; } -void WallpaperManager::UserChangedChildStatus(user_manager::User* user) { - SetUserWallpaperNow(user->GetAccountId()); +void WallpaperManager::OnChildStatusChanged(const user_manager::User& user) { + SetUserWallpaperNow(user.GetAccountId()); } void WallpaperManager::SetDefaultWallpaperPathsFromCommandLine(
diff --git a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h index 3516205..2239d4d 100644 --- a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h +++ b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h
@@ -36,16 +36,20 @@ namespace chromeos { -class WallpaperManager - : public wallpaper::WallpaperManagerBase, - public ash::mojom::WallpaperPicker, - public content::NotificationObserver, - public user_manager::UserManager::UserSessionStateObserver, - public wm::ActivationChangeObserver, - public aura::WindowObserver { +class WallpaperManager : public wallpaper::WallpaperManagerBase, + public ash::mojom::WallpaperPicker, + public content::NotificationObserver, + public user_manager::UserManager::Observer, + public wm::ActivationChangeObserver, + public aura::WindowObserver { public: class PendingWallpaper; + // Needed to avoid ambiguity of WallpaperManager::Observer. This can be + // removed when either WallpaperManagerBase::Observer or UserManager::Observer + // is moved its own header file. + using Observer = wallpaper::WallpaperManagerBase::Observer; + ~WallpaperManager() override; // Expects there is no instance of WallpaperManager and create one. @@ -107,8 +111,8 @@ const content::NotificationSource& source, const content::NotificationDetails& details) override; - // user_manager::UserManager::UserSessionStateObserver: - void UserChangedChildStatus(user_manager::User* user) override; + // user_manager::UserManager::Observer: + void OnChildStatusChanged(const user_manager::User& user) override; // wm::ActivationChangeObserver: void OnWindowActivated(ActivationReason reason,
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc index 17f1f95..4d4f6fc 100644 --- a/chrome/browser/chromeos/login/wizard_controller.cc +++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -266,10 +266,12 @@ session_manager::SessionManager::Get()->IsSessionStarted(); if (!ash_util::IsRunningInMash()) { AccessibilityManager* accessibility_manager = AccessibilityManager::Get(); - CHECK(accessibility_manager); - accessibility_subscription_ = accessibility_manager->RegisterCallback( - base::Bind(&WizardController::OnAccessibilityStatusChanged, - base::Unretained(this))); + if (accessibility_manager) { + // accessibility_manager could be null in Tests. + accessibility_subscription_ = accessibility_manager->RegisterCallback( + base::Bind(&WizardController::OnAccessibilityStatusChanged, + base::Unretained(this))); + } } else { NOTIMPLEMENTED(); } @@ -448,6 +450,10 @@ return nullptr; } +void WizardController::SetCurrentScreenForTesting(BaseScreen* screen) { + current_screen_ = screen; +} + void WizardController::ShowNetworkScreen() { VLOG(1) << "Showing network screen."; UpdateStatusAreaVisibilityForScreen(OobeScreen::SCREEN_OOBE_NETWORK);
diff --git a/chrome/browser/chromeos/login/wizard_controller.h b/chrome/browser/chromeos/login/wizard_controller.h index c6616f6..7876b01 100644 --- a/chrome/browser/chromeos/login/wizard_controller.h +++ b/chrome/browser/chromeos/login/wizard_controller.h
@@ -129,6 +129,9 @@ // |screen_manager_|. BaseScreen* CreateScreen(OobeScreen screen); + // Set the current screen. For Test use only. + void SetCurrentScreenForTesting(BaseScreen* screen); + private: // Show specific screen. void ShowNetworkScreen();
diff --git a/chrome/browser/devtools/BUILD.gn b/chrome/browser/devtools/BUILD.gn index e5f6ebd..bc41927 100644 --- a/chrome/browser/devtools/BUILD.gn +++ b/chrome/browser/devtools/BUILD.gn
@@ -34,6 +34,8 @@ import("$_inspector_protocol/inspector_protocol.gni") _protocol_generated = [ + "protocol/browser.cc", + "protocol/browser.h", "protocol/forward.h", "protocol/page.cc", "protocol/page.h", @@ -170,6 +172,8 @@ if (!is_android) { deps += [ ":protocol_generated_sources" ] sources += [ + "protocol/browser_handler.cc", + "protocol/browser_handler.h", "protocol/page_handler.cc", "protocol/page_handler.h", "protocol_string.cc",
diff --git a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc index cf962f9..23c12309 100644 --- a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc +++ b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc
@@ -21,10 +21,7 @@ #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/ui/browser_navigator.h" #include "chrome/browser/ui/browser_navigator_params.h" -#include "chrome/browser/ui/browser_window.h" -#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h" #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" -#include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/grit/browser_resources.h" #include "components/guest_view/browser/guest_view_base.h" #include "content/public/browser/devtools_agent_host.h" @@ -46,43 +43,6 @@ char kHostParam[] = "host"; char kPortParam[] = "port"; -BrowserWindow* GetBrowserWindow(int window_id) { - for (auto* b : *BrowserList::GetInstance()) { - if (b->session_id().id() == window_id) - return b->window(); - } - return nullptr; -} - -// Get the bounds and state of the browser window. The bounds is for the -// restored window when the window is minimized. Otherwise, it is for the actual -// window. -std::unique_ptr<base::DictionaryValue> GetBounds(BrowserWindow* window) { - gfx::Rect bounds; - if (window->IsMinimized()) - bounds = window->GetRestoredBounds(); - else - bounds = window->GetBounds(); - - auto bounds_object = base::MakeUnique<base::DictionaryValue>(); - - bounds_object->SetInteger("left", bounds.x()); - bounds_object->SetInteger("top", bounds.y()); - bounds_object->SetInteger("width", bounds.width()); - bounds_object->SetInteger("height", bounds.height()); - - std::string window_state = "normal"; - if (window->IsMinimized()) - window_state = "minimized"; - if (window->IsMaximized()) - window_state = "maximized"; - if (window->IsFullscreen()) - window_state = "fullscreen"; - bounds_object->SetString("windowState", window_state); - - return bounds_object; -} - bool GetExtensionInfo(content::WebContents* wc, std::string* name, std::string* type) { @@ -118,184 +78,6 @@ } // namespace -// static -std::unique_ptr<base::DictionaryValue> -ChromeDevToolsManagerDelegate::GetWindowForTarget( - int id, - base::DictionaryValue* params) { - std::string target_id; - if (!params->GetString("targetId", &target_id)) - return DevToolsProtocol::CreateInvalidParamsResponse(id, "targetId"); - - Browser* browser = nullptr; - scoped_refptr<DevToolsAgentHost> host = - DevToolsAgentHost::GetForId(target_id); - if (!host) - return DevToolsProtocol::CreateErrorResponse(id, "No target with given id"); - content::WebContents* web_contents = host->GetWebContents(); - if (!web_contents) { - return DevToolsProtocol::CreateErrorResponse( - id, "No web contents in the target"); - } - for (auto* b : *BrowserList::GetInstance()) { - int tab_index = b->tab_strip_model()->GetIndexOfWebContents(web_contents); - if (tab_index != TabStripModel::kNoTab) - browser = b; - } - if (!browser) { - return DevToolsProtocol::CreateErrorResponse(id, - "Browser window not found"); - } - - auto result = base::MakeUnique<base::DictionaryValue>(); - result->SetInteger("windowId", browser->session_id().id()); - result->Set("bounds", GetBounds(browser->window())); - return DevToolsProtocol::CreateSuccessResponse(id, std::move(result)); -} - -// static -std::unique_ptr<base::DictionaryValue> -ChromeDevToolsManagerDelegate::GetWindowBounds(int id, - base::DictionaryValue* params) { - int window_id; - if (!params->GetInteger("windowId", &window_id)) - return DevToolsProtocol::CreateInvalidParamsResponse(id, "windowId"); - BrowserWindow* window = GetBrowserWindow(window_id); - if (!window) { - return DevToolsProtocol::CreateErrorResponse(id, - "Browser window not found"); - } - - auto result = base::MakeUnique<base::DictionaryValue>(); - result->Set("bounds", GetBounds(window)); - return DevToolsProtocol::CreateSuccessResponse(id, std::move(result)); -} - -// static -std::unique_ptr<base::DictionaryValue> -ChromeDevToolsManagerDelegate::SetWindowBounds(int id, - base::DictionaryValue* params) { - int window_id; - if (!params->GetInteger("windowId", &window_id)) - return DevToolsProtocol::CreateInvalidParamsResponse(id, "windowId"); - BrowserWindow* window = GetBrowserWindow(window_id); - if (!window) { - return DevToolsProtocol::CreateErrorResponse(id, - "Browser window not found"); - } - - const base::Value* value = nullptr; - const base::DictionaryValue* bounds_dict = nullptr; - if (!params->Get("bounds", &value) || !value->GetAsDictionary(&bounds_dict)) - return DevToolsProtocol::CreateInvalidParamsResponse(id, "bounds"); - - std::string window_state; - if (!bounds_dict->GetString("windowState", &window_state)) - window_state = "normal"; - else if (window_state != "normal" && window_state != "minimized" && - window_state != "maximized" && window_state != "fullscreen") - return DevToolsProtocol::CreateInvalidParamsResponse(id, "windowState"); - - // Compute updated bounds when window state is normal. - bool set_bounds = false; - gfx::Rect bounds = window->GetBounds(); - int left, top, width, height; - if (bounds_dict->GetInteger("left", &left)) { - bounds.set_x(left); - set_bounds = true; - } - if (bounds_dict->GetInteger("top", &top)) { - bounds.set_y(top); - set_bounds = true; - } - if (bounds_dict->GetInteger("width", &width)) { - if (width < 0) - return DevToolsProtocol::CreateInvalidParamsResponse(id, "width"); - bounds.set_width(width); - set_bounds = true; - } - if (bounds_dict->GetInteger("height", &height)) { - if (height < 0) - return DevToolsProtocol::CreateInvalidParamsResponse(id, "height"); - bounds.set_height(height); - set_bounds = true; - } - - if (set_bounds && window_state != "normal") { - return DevToolsProtocol::CreateErrorResponse( - id, - "The 'minimized', 'maximized' and 'fullscreen' states cannot be " - "combined with 'left', 'top', 'width' or 'height'"); - } - - if (set_bounds && (window->IsMinimized() || window->IsMaximized() || - window->IsFullscreen())) { - return DevToolsProtocol::CreateErrorResponse( - id, - "To resize minimized/maximized/fullscreen window, restore it to normal " - "state first."); - } - - if (window_state == "fullscreen") { - if (window->IsMinimized()) { - return DevToolsProtocol::CreateErrorResponse(id, - "To make minimized window " - "fullscreen, restore it to " - "normal state first."); - } - window->GetExclusiveAccessContext()->EnterFullscreen( - GURL(), EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE); - } - - if (window_state == "maximized") { - if (window->IsMinimized() || window->IsFullscreen()) { - return DevToolsProtocol::CreateErrorResponse( - id, - "To maximize a minimized or fullscreen window, restore it to normal " - "state first."); - } - window->Maximize(); - } - - if (window_state == "minimized") { - if (window->IsFullscreen()) { - return DevToolsProtocol::CreateErrorResponse( - id, - "To minimize a fullscreen window, restore it to normal " - "state first."); - } - window->Minimize(); - } - - if (window_state == "normal") { - if (window->IsFullscreen()) { - window->GetExclusiveAccessContext()->ExitFullscreen(); - } else if (window->IsMinimized()) { - window->Show(); - } else if (window->IsMaximized()) { - window->Restore(); - } else if (set_bounds) { - window->SetBounds(bounds); - } - } - - return DevToolsProtocol::CreateSuccessResponse(id, nullptr); -} - -std::unique_ptr<base::DictionaryValue> -ChromeDevToolsManagerDelegate::HandleBrowserCommand( - int id, - std::string method, - base::DictionaryValue* params) { - if (method == chrome::devtools::Browser::getWindowForTarget::kName) - return GetWindowForTarget(id, params); - if (method == chrome::devtools::Browser::getWindowBounds::kName) - return GetWindowBounds(id, params); - if (method == chrome::devtools::Browser::setWindowBounds::kName) - return SetWindowBounds(id, params); - return nullptr; -} - class ChromeDevToolsManagerDelegate::HostData { public: HostData() {} @@ -334,15 +116,8 @@ if (!DevToolsProtocol::ParseCommand(command_dict, &id, &method, ¶ms)) return false; - auto result = HandleBrowserCommand(id, method, params); - if (result) { - agent_host->SendProtocolMessageToClient(session_id, - ToString(std::move(result))); - return true; - } - if (method == chrome::devtools::Target::setRemoteLocations::kName) { - result = SetRemoteLocations(agent_host, id, params); + auto result = SetRemoteLocations(agent_host, id, params); DCHECK(result); agent_host->SendProtocolMessageToClient(session_id, ToString(std::move(result)));
diff --git a/chrome/browser/devtools/chrome_devtools_manager_delegate.h b/chrome/browser/devtools/chrome_devtools_manager_delegate.h index c0e2026..f426f7b0 100644 --- a/chrome/browser/devtools/chrome_devtools_manager_delegate.h +++ b/chrome/browser/devtools/chrome_devtools_manager_delegate.h
@@ -67,20 +67,6 @@ int command_id, base::DictionaryValue* params); - std::unique_ptr<base::DictionaryValue> HandleBrowserCommand( - int id, - std::string method, - base::DictionaryValue* params); - static std::unique_ptr<base::DictionaryValue> GetWindowForTarget( - int id, - base::DictionaryValue* params); - static std::unique_ptr<base::DictionaryValue> GetWindowBounds( - int id, - base::DictionaryValue* params); - static std::unique_ptr<base::DictionaryValue> SetWindowBounds( - int id, - base::DictionaryValue* params); - std::map<content::DevToolsAgentHost*, std::unique_ptr<HostData>> host_data_; std::map<int, std::unique_ptr<ChromeDevToolsSession>> sessions_;
diff --git a/chrome/browser/devtools/chrome_devtools_session.cc b/chrome/browser/devtools/chrome_devtools_session.cc index 45058687..ab4d843a 100644 --- a/chrome/browser/devtools/chrome_devtools_session.cc +++ b/chrome/browser/devtools/chrome_devtools_session.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/devtools/chrome_devtools_session.h" +#include "chrome/browser/devtools/protocol/browser_handler.h" #include "chrome/browser/devtools/protocol/page_handler.h" #include "content/public/browser/devtools_agent_host.h" @@ -18,6 +19,7 @@ page_handler_ = std::make_unique<PageHandler>(agent_host->GetWebContents(), dispatcher_.get()); } + browser_handler_ = std::make_unique<BrowserHandler>(dispatcher_.get()); } ChromeDevToolsSession::~ChromeDevToolsSession() = default;
diff --git a/chrome/browser/devtools/chrome_devtools_session.h b/chrome/browser/devtools/chrome_devtools_session.h index 63f2aeb..c4acda94 100644 --- a/chrome/browser/devtools/chrome_devtools_session.h +++ b/chrome/browser/devtools/chrome_devtools_session.h
@@ -14,6 +14,7 @@ class DevToolsAgentHost; } +class BrowserHandler; class PageHandler; class ChromeDevToolsSession : public protocol::FrontendChannel { @@ -36,6 +37,7 @@ const int session_id_; std::unique_ptr<protocol::UberDispatcher> dispatcher_; + std::unique_ptr<BrowserHandler> browser_handler_; std::unique_ptr<PageHandler> page_handler_; DISALLOW_COPY_AND_ASSIGN(ChromeDevToolsSession);
diff --git a/chrome/browser/devtools/devtools_sanity_interactive_browsertest.cc b/chrome/browser/devtools/devtools_sanity_interactive_browsertest.cc index f8745e28..6bfc76a9 100644 --- a/chrome/browser/devtools/devtools_sanity_interactive_browsertest.cc +++ b/chrome/browser/devtools/devtools_sanity_interactive_browsertest.cc
@@ -8,6 +8,7 @@ #include "base/run_loop.h" #include "build/build_config.h" #include "chrome/browser/devtools/chrome_devtools_manager_delegate.h" +#include "chrome/browser/devtools/protocol/browser_handler.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/exclusive_access/exclusive_access_context.h" #include "chrome/test/base/in_process_browser_test.h" @@ -65,24 +66,23 @@ class DevToolsManagerDelegateTest : public InProcessBrowserTest { public: - std::unique_ptr<base::DictionaryValue> SendCommand(std::string state) { - auto params = base::MakeUnique<base::DictionaryValue>(); - auto bounds_object = base::MakeUnique<base::DictionaryValue>(); - bounds_object->SetString("windowState", state); - params->Set("bounds", std::move(bounds_object)); - params->SetInteger("windowId", browser()->session_id().id()); - return ChromeDevToolsManagerDelegate::SetWindowBounds(0, params.get()); + void SendCommand(std::string state) { + auto window_bounds = + protocol::Browser::Bounds::Create().SetWindowState(state).Build(); + BrowserHandler handler(nullptr); + handler.SetWindowBounds(browser()->session_id().id(), + std::move(window_bounds)); } - std::unique_ptr<base::DictionaryValue> UpdateBounds() { - auto params = base::MakeUnique<base::DictionaryValue>(); - auto bounds_object = base::MakeUnique<base::DictionaryValue>(); - bounds_object->SetString("windowState", "normal"); - bounds_object->SetInteger("left", 200); - bounds_object->SetInteger("height", 400); - params->Set("bounds", std::move(bounds_object)); - params->SetInteger("windowId", browser()->session_id().id()); - return ChromeDevToolsManagerDelegate::SetWindowBounds(0, params.get()); + void UpdateBounds() { + auto window_bounds = protocol::Browser::Bounds::Create() + .SetWindowState("normal") + .SetLeft(200) + .SetHeight(400) + .Build(); + BrowserHandler handler(nullptr); + handler.SetWindowBounds(browser()->session_id().id(), + std::move(window_bounds)); } void CheckIsMaximized(bool maximized) {
diff --git a/chrome/browser/devtools/inspector_protocol_config.json b/chrome/browser/devtools/inspector_protocol_config.json index 58272d8..9105868 100644 --- a/chrome/browser/devtools/inspector_protocol_config.json +++ b/chrome/browser/devtools/inspector_protocol_config.json
@@ -11,6 +11,12 @@ "include": [ "enable", "disable", "setAdBlockingEnabled" ], "include_types": [], "include_events": [] + }, + { + "domain": "Browser", + "include": [ "getWindowForTarget", "getWindowBounds", "setWindowBounds" ], + "include_types": [ "Bounds" ], + "include_events": [] } ] },
diff --git a/chrome/browser/devtools/protocol/browser_handler.cc b/chrome/browser/devtools/protocol/browser_handler.cc new file mode 100644 index 0000000..498135e5 --- /dev/null +++ b/chrome/browser/devtools/protocol/browser_handler.cc
@@ -0,0 +1,154 @@ +// Copyright 2017 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/browser/devtools/protocol/browser_handler.h" + +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "content/public/browser/devtools_agent_host.h" + +namespace { + +BrowserWindow* GetBrowserWindow(int window_id) { + for (auto* b : *BrowserList::GetInstance()) { + if (b->session_id().id() == window_id) + return b->window(); + } + return nullptr; +} + +std::unique_ptr<protocol::Browser::Bounds> GetBrowserWindowBounds( + BrowserWindow* window) { + std::string window_state = "normal"; + if (window->IsMinimized()) + window_state = "minimized"; + if (window->IsMaximized()) + window_state = "maximized"; + if (window->IsFullscreen()) + window_state = "fullscreen"; + + gfx::Rect bounds; + if (window->IsMinimized()) + bounds = window->GetRestoredBounds(); + else + bounds = window->GetBounds(); + return protocol::Browser::Bounds::Create() + .SetLeft(bounds.x()) + .SetTop(bounds.y()) + .SetWidth(bounds.width()) + .SetHeight(bounds.height()) + .SetWindowState(window_state) + .Build(); +} + +} // namespace + +BrowserHandler::BrowserHandler(protocol::UberDispatcher* dispatcher) { + // Dispatcher can be null in tests. + if (dispatcher) + protocol::Browser::Dispatcher::wire(dispatcher, this); +} + +BrowserHandler::~BrowserHandler() = default; + +protocol::Response BrowserHandler::GetWindowForTarget( + const std::string& target_id, + int* out_window_id, + std::unique_ptr<protocol::Browser::Bounds>* out_bounds) { + auto host = content::DevToolsAgentHost::GetForId(target_id); + if (!host) + return protocol::Response::Error("No target with given id"); + content::WebContents* web_contents = host->GetWebContents(); + if (!web_contents) + return protocol::Response::Error("No web contents in the target"); + + Browser* browser = nullptr; + for (auto* b : *BrowserList::GetInstance()) { + int tab_index = b->tab_strip_model()->GetIndexOfWebContents(web_contents); + if (tab_index != TabStripModel::kNoTab) + browser = b; + } + if (!browser) + return protocol::Response::Error("Browser window not found"); + + BrowserWindow* window = browser->window(); + *out_window_id = browser->session_id().id(); + *out_bounds = GetBrowserWindowBounds(window); + return protocol::Response::OK(); +} + +protocol::Response BrowserHandler::GetWindowBounds( + int window_id, + std::unique_ptr<protocol::Browser::Bounds>* out_bounds) { + BrowserWindow* window = GetBrowserWindow(window_id); + if (!window) + return protocol::Response::Error("Browser window not found"); + + *out_bounds = GetBrowserWindowBounds(window); + return protocol::Response::OK(); +} + +protocol::Response BrowserHandler::SetWindowBounds( + int window_id, + std::unique_ptr<protocol::Browser::Bounds> window_bounds) { + BrowserWindow* window = GetBrowserWindow(window_id); + if (!window) + return protocol::Response::Error("Browser window not found"); + gfx::Rect bounds = window->GetBounds(); + const bool set_bounds = window_bounds->HasLeft() || window_bounds->HasTop() || + window_bounds->HasWidth() || + window_bounds->HasHeight(); + if (set_bounds) { + bounds.set_x(window_bounds->GetLeft(bounds.x())); + bounds.set_y(window_bounds->GetTop(bounds.y())); + bounds.set_width(window_bounds->GetWidth(bounds.width())); + bounds.set_height(window_bounds->GetHeight(bounds.height())); + } + + const std::string& window_state = window_bounds->GetWindowState("normal"); + if (set_bounds && window_state != "normal") { + return protocol::Response::Error( + "The 'minimized', 'maximized' and 'fullscreen' states cannot be " + "combined with 'left', 'top', 'width' or 'height'"); + } + + if (window_state == "fullscreen") { + if (window->IsMinimized()) { + return protocol::Response::Error( + "To make minimized window fullscreen, " + "restore it to normal state first."); + } + window->GetExclusiveAccessContext()->EnterFullscreen( + GURL(), EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE); + } else if (window_state == "maximized") { + if (window->IsMinimized() || window->IsFullscreen()) { + return protocol::Response::Error( + "To maximize a minimized or fullscreen " + "window, restore it to normal state first."); + } + window->Maximize(); + } else if (window_state == "minimized") { + if (window->IsFullscreen()) { + return protocol::Response::Error( + "To minimize a fullscreen window, restore it to normal " + "state first."); + } + window->Minimize(); + } else if (window_state == "normal") { + if (window->IsFullscreen()) + window->GetExclusiveAccessContext()->ExitFullscreen(); + else if (window->IsMinimized()) + window->Show(); + else if (window->IsMaximized()) + window->Restore(); + else if (set_bounds) + window->SetBounds(bounds); + } else { + NOTREACHED(); + } + + return protocol::Response::OK(); +}
diff --git a/chrome/browser/devtools/protocol/browser_handler.h b/chrome/browser/devtools/protocol/browser_handler.h new file mode 100644 index 0000000..342af51 --- /dev/null +++ b/chrome/browser/devtools/protocol/browser_handler.h
@@ -0,0 +1,31 @@ +// Copyright 2017 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_BROWSER_DEVTOOLS_PROTOCOL_BROWSER_HANDLER_H_ +#define CHROME_BROWSER_DEVTOOLS_PROTOCOL_BROWSER_HANDLER_H_ + +#include "chrome/browser/devtools/protocol/browser.h" + +class BrowserHandler : public protocol::Browser::Backend { + public: + explicit BrowserHandler(protocol::UberDispatcher* dispatcher); + ~BrowserHandler() override; + + // Browser::Backend: + protocol::Response GetWindowForTarget( + const std::string& target_id, + int* out_window_id, + std::unique_ptr<protocol::Browser::Bounds>* out_bounds) override; + protocol::Response GetWindowBounds( + int window_id, + std::unique_ptr<protocol::Browser::Bounds>* out_bounds) override; + protocol::Response SetWindowBounds( + int window_id, + std::unique_ptr<protocol::Browser::Bounds> out_bounds) override; + + private: + DISALLOW_COPY_AND_ASSIGN(BrowserHandler); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_PROTOCOL_BROWSER_HANDLER_H_
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc index 873abeb..2c1e20b13 100644 --- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc +++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -1102,6 +1102,51 @@ } } +// Test that initiator is only included as part of event details when the +// extension has a permission matching the initiator. +IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, MinimumAccessInitiator) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + ExtensionTestMessageListener listener("ready", false); + const Extension* extension = LoadExtension( + test_data_dir_.AppendASCII("webrequest_permissions/initiator")); + ASSERT_TRUE(extension) << message_; + EXPECT_TRUE(listener.WaitUntilSatisfied()); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(web_contents); + + struct TestCase { + std::string navigate_before_start; + std::string xhr_domain; + std::string expected_initiator; + } testcases[] = {{"example.com", "example.com", "example.com"}, + {"example2.com", "example3.com", "example2.com"}, + {"no-permission.com", "example4.com", ""}}; + + int port = embedded_test_server()->port(); + for (const auto& testcase : testcases) { + SCOPED_TRACE(testcase.navigate_before_start + ":" + testcase.xhr_domain + + ":" + testcase.expected_initiator); + ExtensionTestMessageListener initiator_listener(false); + initiator_listener.set_extension_id(extension->id()); + ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL( + testcase.navigate_before_start, + "/extensions/body1.html")); + PerformXhrInFrame(web_contents->GetMainFrame(), testcase.xhr_domain, port, + "extensions/api_test/webrequest/xhr/data.json"); + EXPECT_TRUE(initiator_listener.WaitUntilSatisfied()); + if (testcase.expected_initiator.empty()) { + ASSERT_EQ("NO_INITIATOR", initiator_listener.message()); + } else { + ASSERT_EQ( + "http://" + testcase.expected_initiator + ":" + std::to_string(port), + initiator_listener.message()); + } + } +} + // Tests that the webRequest events aren't dispatched when the request initiator // is protected by policy. IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy,
diff --git a/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc b/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc index b68ccc59..266fbe8f 100644 --- a/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc +++ b/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc
@@ -97,7 +97,7 @@ WebRequestEventDetails details(request.get(), kFilter); details.SetResponseHeaders(request.get(), headers.get()); std::unique_ptr<base::DictionaryValue> dict = - details.GetFilteredDict(kFilter); + details.GetFilteredDict(kFilter, nullptr, std::string(), false); base::Value* filtered_headers = dict->FindPath({"responseHeaders"}); ASSERT_TRUE(filtered_headers); EXPECT_EQ(2u, filtered_headers->GetList().size()); @@ -119,7 +119,7 @@ WebRequestEventDetails gaia_details(gaia_request.get(), kFilter); gaia_details.SetResponseHeaders(gaia_request.get(), headers.get()); std::unique_ptr<base::DictionaryValue> dict = - gaia_details.GetFilteredDict(kFilter); + gaia_details.GetFilteredDict(kFilter, nullptr, std::string(), false); base::Value* filtered_headers = dict->FindPath({"responseHeaders"}); ASSERT_TRUE(filtered_headers); EXPECT_EQ(1u, filtered_headers->GetList().size());
diff --git a/chrome/browser/extensions/bookmark_app_url_redirector_browsertest.cc b/chrome/browser/extensions/bookmark_app_url_redirector_browsertest.cc index 4b95f40e..4a35967 100644 --- a/chrome/browser/extensions/bookmark_app_url_redirector_browsertest.cc +++ b/chrome/browser/extensions/bookmark_app_url_redirector_browsertest.cc
@@ -5,8 +5,10 @@ #include "base/memory/ptr_util.h" #include "base/strings/utf_string_conversions.h" #include "base/test/scoped_feature_list.h" +#include "chrome/app/chrome_command_ids.h" #include "chrome/browser/extensions/bookmark_app_helper.h" #include "chrome/browser/extensions/extension_browsertest.h" +#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/extensions/app_launch_params.h" @@ -19,6 +21,7 @@ #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/render_frame_host.h" +#include "content/public/common/context_menu_params.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/test_frame_navigation_observer.h" #include "content/public/test/test_utils.h" @@ -26,6 +29,7 @@ #include "extensions/browser/notification_types.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_response.h" +#include "net/url_request/url_fetcher.h" using namespace net::test_server; @@ -69,15 +73,51 @@ ui_test_utils::UrlLoadObserver url_observer( target_url, content::NotificationService::AllSources()); std::string script = base::StringPrintf( + "(() => {" "const link = document.createElement('a');" "link.href = '%s';" "link.target = '%s';" "document.body.appendChild(link);" "const event = new MouseEvent('click', {'view': window});" - "link.dispatchEvent(event);", + "link.dispatchEvent(event);" + "})();", target_url.spec().c_str(), target == LinkTarget::SELF ? "_self" : "_blank"); - EXPECT_TRUE(content::ExecuteScript(web_contents, script)); + ASSERT_TRUE(content::ExecuteScript(web_contents, script)); + url_observer.Wait(); +} + +// Creates a <form> element with a |target_url| action and |method| method. Adds +// the form to the DOM with a button and clicks the button. Returns once +// |target_url| has been loaded. +// +// If |method| is net::URLFetcher::RequestType::GET, |target_url| should contain +// an empty query string, since that URL will be loaded when submitting the form +// e.g. "https://www.example.com/?". +void SubmitFormAndWait(content::WebContents* web_contents, + const GURL& target_url, + net::URLFetcher::RequestType method) { + if (method == net::URLFetcher::RequestType::GET) { + ASSERT_TRUE(target_url.has_query()); + ASSERT_TRUE(target_url.query().empty()); + } + + ui_test_utils::UrlLoadObserver url_observer( + target_url, content::NotificationService::AllSources()); + std::string script = base::StringPrintf( + "(() => {" + "const form = document.createElement('form');" + "form.action = '%s';" + "form.method = '%s';" + "const button = document.createElement('input');" + "button.type = 'submit';" + "form.appendChild(button);" + "document.body.appendChild(form);" + "button.dispatchEvent(new MouseEvent('click', {'view': window}));" + "})();", + target_url.spec().c_str(), + method == net::URLFetcher::RequestType::POST ? "post" : "get"); + ASSERT_TRUE(content::ExecuteScript(web_contents, script)); url_observer.Wait(); } @@ -118,6 +158,7 @@ base::BindRepeating([](const HttpRequest& request) { auto response = base::MakeUnique<BasicHttpResponse>(); response->set_content_type("text/html"); + response->AddCustomHeader("Access-Control-Allow-Origin", "*"); return static_cast<std::unique_ptr<HttpResponse>>( std::move(response)); })); @@ -163,9 +204,33 @@ return chrome::FindLastActive(); } + // Navigates the active tab in |browser| to the launching page. + void NavigateToLaunchingPage(Browser* browser) { + ui_test_utils::NavigateToURL(browser, GetLaunchingPageURL()); + } + // Navigates the active tab to the launching page. - void NavigateToLaunchingPage() { - ui_test_utils::NavigateToURL(browser(), GetLaunchingPageURL()); + void NavigateToLaunchingPage() { NavigateToLaunchingPage(browser()); } + + // Checks that, after running |action|, the initial tab's window doesn't have + // any new tabs, the initial tab did not navigate, and that no new windows + // have been opened. + void TestTabActionDoesNotNavigateOrOpenAppWindow( + const base::Closure& action) { + size_t num_browsers = chrome::GetBrowserCount(profile()); + int num_tabs = browser()->tab_strip_model()->count(); + content::WebContents* initial_tab = + browser()->tab_strip_model()->GetActiveWebContents(); + GURL initial_url = initial_tab->GetLastCommittedURL(); + + action.Run(); + + EXPECT_EQ(num_browsers, chrome::GetBrowserCount(profile())); + EXPECT_EQ(browser(), chrome::FindLastActive()); + EXPECT_EQ(num_tabs, browser()->tab_strip_model()->count()); + EXPECT_EQ(initial_tab, + browser()->tab_strip_model()->GetActiveWebContents()); + EXPECT_EQ(initial_url, initial_tab->GetLastCommittedURL()); } // Checks that, after running |action|, the initial tab's window doesn't have @@ -193,28 +258,36 @@ } // Checks that no new windows are opened after running |action| and that the - // existing window is still the active one and navigated to |target_url|. - // Returns true if there were no errors. - bool TestTabActionDoesNotOpenAppWindow(const GURL& target_url, + // existing |browser| window is still the active one and navigated to + // |target_url|. Returns true if there were no errors. + bool TestTabActionDoesNotOpenAppWindow(Browser* browser, + const GURL& target_url, const base::Closure& action) { content::WebContents* initial_tab = - browser()->tab_strip_model()->GetActiveWebContents(); - int num_tabs = browser()->tab_strip_model()->count(); - size_t num_browsers = chrome::GetBrowserCount(profile()); + browser->tab_strip_model()->GetActiveWebContents(); + int num_tabs = browser->tab_strip_model()->count(); + size_t num_browsers = chrome::GetBrowserCount(browser->profile()); action.Run(); - EXPECT_EQ(num_tabs, browser()->tab_strip_model()->count()); - EXPECT_EQ(num_browsers, chrome::GetBrowserCount(profile())); - EXPECT_EQ(browser(), chrome::FindLastActive()); - EXPECT_EQ(initial_tab, - browser()->tab_strip_model()->GetActiveWebContents()); + EXPECT_EQ(num_tabs, browser->tab_strip_model()->count()); + EXPECT_EQ(num_browsers, chrome::GetBrowserCount(browser->profile())); + EXPECT_EQ(browser, chrome::FindLastActive()); + EXPECT_EQ(initial_tab, browser->tab_strip_model()->GetActiveWebContents()); EXPECT_EQ(target_url, initial_tab->GetLastCommittedURL()); return !HasFailure(); } // Checks that no new windows are opened after running |action| and that the + // main browser window is still the active one and navigated to |target_url|. + // Returns true if there were no errors. + bool TestTabActionDoesNotOpenAppWindow(const GURL& target_url, + const base::Closure& action) { + return TestTabActionDoesNotOpenAppWindow(browser(), target_url, action); + } + + // Checks that no new windows are opened after running |action| and that the // iframe in the initial tab navigated to |target_url|. Returns true if there // were no errors. bool TestIFrameActionDoesNotOpenAppWindow(const GURL& target_url, @@ -426,6 +499,139 @@ out_of_scope_url, LinkTarget::SELF)); } +// Tests that submitting a form using POST does not open a new app window. +IN_PROC_BROWSER_TEST_F(BookmarkAppUrlRedirectorBrowserTest, + PostFormSubmission) { + InstallTestBookmarkApp(); + NavigateToLaunchingPage(); + + const GURL in_scope_url = embedded_test_server()->GetURL(kInScopeUrlPath); + TestTabActionDoesNotOpenAppWindow( + in_scope_url, + base::Bind(&SubmitFormAndWait, + browser()->tab_strip_model()->GetActiveWebContents(), + in_scope_url, net::URLFetcher::RequestType::POST)); +} + +// Tests that submitting a form using GET does not open a new app window. +IN_PROC_BROWSER_TEST_F(BookmarkAppUrlRedirectorBrowserTest, GetFormSubmission) { + InstallTestBookmarkApp(); + NavigateToLaunchingPage(); + + GURL::Replacements replacements; + replacements.SetQuery("", url::Component(0, 0)); + const GURL in_scope_form_url = embedded_test_server() + ->GetURL(kInScopeUrlPath) + .ReplaceComponents(replacements); + TestTabActionDoesNotOpenAppWindow( + in_scope_form_url, + base::Bind(&SubmitFormAndWait, + browser()->tab_strip_model()->GetActiveWebContents(), + in_scope_form_url, net::URLFetcher::RequestType::GET)); +} + +// Tests that prerender links don't open the app. +IN_PROC_BROWSER_TEST_F(BookmarkAppUrlRedirectorBrowserTest, PrerenderLinks) { + InstallTestBookmarkApp(); + NavigateToLaunchingPage(); + + TestTabActionDoesNotNavigateOrOpenAppWindow(base::Bind( + [](content::WebContents* web_contents, const GURL& target_url) { + std::string script = base::StringPrintf( + "(() => {" + "const prerender_link = document.createElement('link');" + "prerender_link.rel = 'prerender';" + "prerender_link.href = '%s';" + "prerender_link.addEventListener('webkitprerenderstop'," + "() => window.domAutomationController.send(true));" + "document.body.appendChild(prerender_link);" + "})();", + target_url.spec().c_str()); + bool result; + ASSERT_TRUE(content::ExecuteScriptAndExtractBool(web_contents, script, + &result)); + ASSERT_TRUE(result); + }, + browser()->tab_strip_model()->GetActiveWebContents(), + embedded_test_server()->GetURL(kInScopeUrlPath))); +} + +// Tests fetch calls don't open a new App window. +IN_PROC_BROWSER_TEST_F(BookmarkAppUrlRedirectorBrowserTest, Fetch) { + InstallTestBookmarkApp(); + NavigateToLaunchingPage(); + + TestTabActionDoesNotNavigateOrOpenAppWindow(base::Bind( + [](content::WebContents* web_contents, const GURL& target_url) { + std::string script = base::StringPrintf( + "(() => {" + "fetch('%s').then(response => {" + " window.domAutomationController.send(response.ok);" + "});" + "})();", + target_url.spec().c_str()); + bool result; + ASSERT_TRUE(content::ExecuteScriptAndExtractBool(web_contents, script, + &result)); + ASSERT_TRUE(result); + }, + browser()->tab_strip_model()->GetActiveWebContents(), + embedded_test_server()->GetURL(kInScopeUrlPath))); +} + +// Tests that clicking "Open link in incognito window" to an in-scope URL opens +// an incognito window and not an app window. +IN_PROC_BROWSER_TEST_F(BookmarkAppUrlRedirectorBrowserTest, OpenInIncognito) { + InstallTestBookmarkApp(); + NavigateToLaunchingPage(); + + size_t num_browsers = chrome::GetBrowserCount(profile()); + int num_tabs = browser()->tab_strip_model()->count(); + content::WebContents* initial_tab = + browser()->tab_strip_model()->GetActiveWebContents(); + GURL initial_url = initial_tab->GetLastCommittedURL(); + + const GURL in_scope_url = embedded_test_server()->GetURL(kInScopeUrlPath); + ui_test_utils::UrlLoadObserver url_observer( + in_scope_url, content::NotificationService::AllSources()); + content::ContextMenuParams params; + params.page_url = initial_url; + params.link_url = in_scope_url; + TestRenderViewContextMenu menu(initial_tab->GetMainFrame(), params); + menu.Init(); + menu.ExecuteCommand(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD, + 0 /* event_flags */); + url_observer.Wait(); + + Browser* incognito_browser = chrome::FindLastActive(); + EXPECT_EQ(incognito_browser->profile(), profile()->GetOffTheRecordProfile()); + EXPECT_NE(browser(), incognito_browser); + EXPECT_EQ(in_scope_url, incognito_browser->tab_strip_model() + ->GetActiveWebContents() + ->GetLastCommittedURL()); + + EXPECT_EQ(num_browsers, chrome::GetBrowserCount(profile())); + EXPECT_EQ(num_tabs, browser()->tab_strip_model()->count()); + EXPECT_EQ(initial_tab, browser()->tab_strip_model()->GetActiveWebContents()); + EXPECT_EQ(initial_url, initial_tab->GetLastCommittedURL()); +} + +// Tests that clicking a link to an in-scope URL when in incognito does not open +// an App window. +IN_PROC_BROWSER_TEST_F(BookmarkAppUrlRedirectorBrowserTest, + InScopeUrlIncognito) { + InstallTestBookmarkApp(); + Browser* incognito_browser = CreateIncognitoBrowser(); + NavigateToLaunchingPage(incognito_browser); + + const GURL in_scope_url = embedded_test_server()->GetURL(kInScopeUrlPath); + TestTabActionDoesNotOpenAppWindow( + incognito_browser, in_scope_url, + base::Bind(&ClickLinkAndWait, + incognito_browser->tab_strip_model()->GetActiveWebContents(), + in_scope_url, LinkTarget::SELF)); +} + // Tests that clicking links inside a website for an installed app doesn't open // a new browser window. IN_PROC_BROWSER_TEST_F(BookmarkAppUrlRedirectorBrowserTest,
diff --git a/chrome/browser/extensions/extension_apitest.cc b/chrome/browser/extensions/extension_apitest.cc index f6a6fd44..f38bccb 100644 --- a/chrome/browser/extensions/extension_apitest.cc +++ b/chrome/browser/extensions/extension_apitest.cc
@@ -22,6 +22,7 @@ #include "chrome/browser/ui/extensions/application_launch.h" #include "chrome/common/extensions/extension_process_policy.h" #include "chrome/test/base/ui_test_utils.h" +#include "content/public/common/browser_side_navigation_policy.h" #include "content/public/common/content_switches.h" #include "extensions/browser/api/test/test_api.h" #include "extensions/browser/extension_registry.h" @@ -46,6 +47,7 @@ const char kTestWebSocketPort[] = "testWebSocketPort"; const char kFtpServerPort[] = "ftpServer.port"; const char kEmbeddedTestServerPort[] = "testServer.port"; +const char kBrowserSideNavigationEnabled[] = "browserSideNavigationEnabled"; std::unique_ptr<net::test_server::HttpResponse> HandleServerRedirectRequest( const net::test_server::HttpRequest& request) { @@ -159,6 +161,8 @@ test_config_.reset(new base::DictionaryValue()); test_config_->SetString(kTestDataDirectory, net::FilePathToFileURL(test_data_dir_).spec()); + test_config_->SetBoolean(kBrowserSideNavigationEnabled, + content::IsBrowserSideNavigationEnabled()); extensions::TestGetConfigFunction::set_test_config_state( test_config_.get()); }
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 0c27d53..66404cb 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc
@@ -2062,6 +2062,9 @@ registry_->terminated_extensions().GetByID(lowercase_id); registry_->RemoveTerminated(lowercase_id); if (extension) { + // TODO: This notification was already sent when the extension was + // unloaded as part of being terminated. But we send it again as observers + // may be tracking the terminated extension. See crbug.com/708230. content::NotificationService::current()->Notify( extensions::NOTIFICATION_EXTENSION_REMOVED, content::Source<Profile>(profile_),
diff --git a/chrome/browser/policy/profile_policy_connector.cc b/chrome/browser/policy/profile_policy_connector.cc index 53c85a9..ada5cadc0 100644 --- a/chrome/browser/policy/profile_policy_connector.cc +++ b/chrome/browser/policy/profile_policy_connector.cc
@@ -37,21 +37,6 @@ namespace policy { -namespace { - -std::string GetStoreManagementDomain(const CloudPolicyStore* policy_store) { - if (policy_store) { - CHECK(policy_store->is_initialized()) - << "Cloud policy management domain must be " - "requested only after the policy system is fully initialized"; - if (policy_store->is_managed() && policy_store->policy()->has_username()) - return gaia::ExtractDomainName(policy_store->policy()->username()); - } - return std::string(); -} - -} // namespace - ProfilePolicyConnector::ProfilePolicyConnector() {} ProfilePolicyConnector::~ProfilePolicyConnector() {} @@ -128,6 +113,13 @@ connector->SetUserPolicyDelegate(special_user_policy_provider_.get()); } #endif + +#if defined(OS_CHROMEOS) + if (user && user->IsActiveDirectoryUser()) { + management_realm_ = + gaia::ExtractDomainName(user->GetAccountId().GetUserEmail()); + } +#endif } void ProfilePolicyConnector::InitForTesting( @@ -164,8 +156,22 @@ std::string ProfilePolicyConnector::GetManagementDomain() const { const CloudPolicyStore* actual_policy_store = GetActualPolicyStore(); - if (actual_policy_store) - return GetStoreManagementDomain(actual_policy_store); + if (!actual_policy_store) + return std::string(); + CHECK(actual_policy_store->is_initialized()) + << "Cloud policy management domain must be " + "requested only after the policy system is fully initialized"; + if (!actual_policy_store->is_managed()) + return std::string(); + +#if defined(OS_CHROMEOS) + if (!management_realm_.empty()) + return management_realm_; +#endif // defined(OS_CHROMEOS) + + if (actual_policy_store->policy()->has_username()) + return gaia::ExtractDomainName(actual_policy_store->policy()->username()); + return std::string(); }
diff --git a/chrome/browser/policy/profile_policy_connector.h b/chrome/browser/policy/profile_policy_connector.h index f0efc439..975a0b4 100644 --- a/chrome/browser/policy/profile_policy_connector.h +++ b/chrome/browser/policy/profile_policy_connector.h
@@ -83,6 +83,9 @@ bool is_primary_user_ = false; std::unique_ptr<ConfigurationPolicyProvider> special_user_policy_provider_; + + // Management realm for Active Directory users. Empty for other users. + std::string management_realm_; #endif // defined(OS_CHROMEOS) std::unique_ptr<ConfigurationPolicyProvider>
diff --git a/chrome/browser/policy/profile_policy_connector_unittest.cc b/chrome/browser/policy/profile_policy_connector_unittest.cc index 8a7d9ddb..278dae07 100644 --- a/chrome/browser/policy/profile_policy_connector_unittest.cc +++ b/chrome/browser/policy/profile_policy_connector_unittest.cc
@@ -23,9 +23,16 @@ #include "components/policy/core/common/schema_registry.h" #include "components/policy/policy_constants.h" #include "components/policy/proto/device_management_backend.pb.h" +#include "components/signin/core/account_id/account_id.h" +#include "components/user_manager/user.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h" +#include "chrome/browser/chromeos/login/users/scoped_user_manager_enabler.h" +#endif // defined(OS_CHROMEOS) + using testing::Return; using testing::_; @@ -56,6 +63,12 @@ cloud_policy_manager_->Shutdown(); } + std::unique_ptr<user_manager::User> CreateRegularUser( + const AccountId& account_id) const { + return base::WrapUnique<user_manager::User>( + user_manager::User::CreateRegularUser(account_id)); + } + // Needs to be the first member. base::test::ScopedTaskEnvironment scoped_task_environment_; SchemaRegistry schema_registry_; @@ -82,6 +95,33 @@ connector.Shutdown(); } +#if defined(OS_CHROMEOS) +TEST_F(ProfilePolicyConnectorTest, ManagedRealmForActiveDirectoryUsers) { + chromeos::ScopedUserManagerEnabler scoped_user_manager_enabler( + new chromeos::FakeChromeUserManager); + ProfilePolicyConnector connector; + const AccountId account_id = + AccountId::AdFromUserEmailObjGuid("user@realm.example", "obj-guid"); + std::unique_ptr<user_manager::User> user = CreateRegularUser(account_id); + connector.Init(user.get(), &schema_registry_, cloud_policy_manager_.get(), + &cloud_policy_store_, false); + cloud_policy_store_.policy_.reset(new enterprise_management::PolicyData()); + cloud_policy_store_.policy_->set_state( + enterprise_management::PolicyData::ACTIVE); + EXPECT_TRUE(connector.IsManaged()); + EXPECT_EQ(connector.GetManagementDomain(), "realm.example"); + + // Policy username does not override management realm for Active Directory + // user. + cloud_policy_store_.policy_->set_username("test@testdomain.com"); + EXPECT_TRUE(connector.IsManaged()); + EXPECT_EQ(connector.GetManagementDomain(), "realm.example"); + + // Cleanup. + connector.Shutdown(); +} +#endif // defined(OS_CHROMEOS) + TEST_F(ProfilePolicyConnectorTest, IsProfilePolicy) { ProfilePolicyConnector connector; connector.Init(nullptr /* user */, &schema_registry_,
diff --git a/chrome/browser/printing/print_preview_message_handler.cc b/chrome/browser/printing/print_preview_message_handler.cc index ae2ff22..a8f84dd 100644 --- a/chrome/browser/printing/print_preview_message_handler.cc +++ b/chrome/browser/printing/print_preview_message_handler.cc
@@ -227,6 +227,7 @@ if (handled) return true; + handled = true; IPC_BEGIN_MESSAGE_MAP(PrintPreviewMessageHandler, message) IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetPreviewPageCount, OnDidGetPreviewPageCount)
diff --git a/chrome/browser/ui/ash/session_controller_client.cc b/chrome/browser/ui/ash/session_controller_client.cc index 168cc61..ee2af65 100644 --- a/chrome/browser/ui/ash/session_controller_client.cc +++ b/chrome/browser/ui/ash/session_controller_client.cc
@@ -325,12 +325,11 @@ SendUserSession(*added_user); } -void SessionControllerClient::UserChangedChildStatus(User* user) { - SendUserSession(*user); +void SessionControllerClient::OnUserImageChanged(const User& user) { + SendUserSession(user); } -void SessionControllerClient::OnUserImageChanged( - const user_manager::User& user) { +void SessionControllerClient::OnChildStatusChanged(const User& user) { SendUserSession(user); }
diff --git a/chrome/browser/ui/ash/session_controller_client.h b/chrome/browser/ui/ash/session_controller_client.h index fd31b48..34f7e6c 100644 --- a/chrome/browser/ui/ash/session_controller_client.h +++ b/chrome/browser/ui/ash/session_controller_client.h
@@ -80,10 +80,10 @@ // user_manager::UserManager::UserSessionStateObserver: void ActiveUserChanged(const user_manager::User* active_user) override; void UserAddedToSession(const user_manager::User* added_user) override; - void UserChangedChildStatus(user_manager::User* user) override; // user_manager::UserManager::Observer void OnUserImageChanged(const user_manager::User& user) override; + void OnChildStatusChanged(const user_manager::User& user) override; // session_manager::SessionManagerObserver: void OnSessionStateChanged() override;
diff --git a/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc b/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc index cf7e2b8..2411229 100644 --- a/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc +++ b/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc
@@ -150,6 +150,9 @@ {"OncVPN-ThirdPartyVPN-ProviderName", IDS_ONC_VPN_THIRD_PARTY_VPN_PROVIDER_NAME}, {"OncVPN-Type", IDS_ONC_VPN_TYPE}, + {"OncVPN-Type_L2TP-IPsec", IDS_ONC_VPN_TYPE_L2TP_IPSEC}, + {"OncVPN-Type_OpenVPN", IDS_ONC_VPN_TYPE_OPENVPN}, + {"OncVPN-Type_ARCVPN", IDS_ONC_VPN_TYPE_ARCVPN}, {"OncWiFi-Frequency", IDS_ONC_WIFI_FREQUENCY}, {"OncWiFi-Passphrase", IDS_ONC_WIFI_PASSWORD}, {"OncWiFi-SSID", IDS_ONC_WIFI_SSID},
diff --git a/chrome/renderer/extensions/automation_internal_custom_bindings.cc b/chrome/renderer/extensions/automation_internal_custom_bindings.cc index 42b314d..271c113 100644 --- a/chrome/renderer/extensions/automation_internal_custom_bindings.cc +++ b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
@@ -67,14 +67,14 @@ // Adjust the bounding box of a node from local to global coordinates, // walking up the parent hierarchy to offset by frame offsets and // scroll offsets. -static gfx::Rect ComputeGlobalNodeBounds( - TreeCache* cache, - ui::AXNode* node, - gfx::RectF local_bounds = gfx::RectF()) { +static gfx::Rect ComputeGlobalNodeBounds(TreeCache* cache, + ui::AXNode* node, + gfx::RectF local_bounds = gfx::RectF(), + bool* offscreen = nullptr) { gfx::RectF bounds = local_bounds; while (node) { - bounds = cache->tree.RelativeToTreeBounds(node, bounds); + bounds = cache->tree.RelativeToTreeBounds(node, bounds, offscreen); TreeCache* previous_cache = cache; ui::AXNode* parent = cache->owner->GetParent(cache->tree.root(), &cache); @@ -1004,8 +1004,10 @@ gin::DataObjectBuilder state(isolate); uint32_t state_pos = 0, state_shifter = node->data().state; while (state_shifter) { - if (state_shifter & 1) - state.Set(ToString(static_cast<ui::AXState>(state_pos)), true); + if (state_pos != ui::AX_STATE_OFFSCREEN) { + if (state_shifter & 1) + state.Set(ToString(static_cast<ui::AXState>(state_pos)), true); + } state_shifter = state_shifter >> 1; state_pos++; } @@ -1022,6 +1024,11 @@ if (focused) state.Set("focused", true); + bool offscreen = false; + ComputeGlobalNodeBounds(cache, node, gfx::RectF(), &offscreen); + if (offscreen) + state.Set(ToString(ui::AX_STATE_OFFSCREEN), true); + args.GetReturnValue().Set(state.Build()); }
diff --git a/chrome/test/chromedriver/server/chromedriver_server.cc b/chrome/test/chromedriver/server/chromedriver_server.cc index 8f940fa..0903de1 100644 --- a/chrome/test/chromedriver/server/chromedriver_server.cc +++ b/chrome/test/chromedriver/server/chromedriver_server.cc
@@ -29,6 +29,7 @@ #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/synchronization/waitable_event.h" +#include "base/task_scheduler/task_scheduler.h" #include "base/threading/thread.h" #include "base/threading/thread_local.h" #include "base/threading/thread_task_runner_handle.h" @@ -335,7 +336,13 @@ printf("Unable to initialize logging. Exiting...\n"); return 1; } + + base::TaskScheduler::CreateAndStartWithDefaultParams("ChromeDriver"); + RunServer(port, allow_remote, whitelisted_ips, url_base, adb_port, std::move(port_server)); + + // clean up + base::TaskScheduler::GetInstance()->Shutdown(); return 0; }
diff --git a/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js b/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js index c2fc323..771f1ad8 100644 --- a/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js +++ b/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
@@ -776,8 +776,10 @@ assertEq(network_guid, result.GUID); var new_properties = { Priority: 1, + Type: 'VPN', VPN: { - Host: 'vpn.host1' + Host: 'vpn.host1', + Type: 'OpenVPN', } }; chrome.networkingPrivate.setProperties(
diff --git a/chrome/test/data/extensions/api_test/webrequest/framework.js b/chrome/test/data/extensions/api_test/webrequest/framework.js index 1519bbc..4aec309 100644 --- a/chrome/test/data/extensions/api_test/webrequest/framework.js +++ b/chrome/test/data/extensions/api_test/webrequest/framework.js
@@ -13,6 +13,7 @@ var frameIdMap; var testWebSocketPort; var testServerPort; +var usingBrowserSideNavigation = false; var testServer = "www.a.com"; var defaultScheme = "http"; var eventsCaptured; @@ -27,6 +28,13 @@ 'onCompleted': [], 'onErrorOccurred': [] }; +// Requests initiated by an extension or user interaction with the browser is a +// BROWSER_INITIATED action. If the request was instead initiated by a website +// or code run in the context of a website then it's WEB_INITIATED. +const initiators = { + BROWSER_INITIATED: 2, + WEB_INITIATED: 3 +}; // If true, don't bark on events that were not registered via expect(). // These events are recorded in capturedUnexpectedData instead of @@ -45,6 +53,7 @@ chrome.test.getConfig(function(config) { testServerPort = config.testServer.port; testWebSocketPort = config.testWebSocketPort; + usingBrowserSideNavigation = config.browserSideNavigationEnabled; chrome.test.runTests(tests); }); } @@ -63,14 +72,49 @@ // Returns an URL from the test server, fixing up the port. Must be called // from within a test case passed to runTests. -function getServerURL(path, opt_host, opt_scheme) { +function getServerURL(opt_path, opt_host, opt_scheme) { if (!testServerPort) throw new Error("Called getServerURL outside of runTests."); var host = opt_host || testServer; var scheme = opt_scheme || defaultScheme; + var path = opt_path || ''; return scheme + "://" + host + ":" + testServerPort + "/" + path; } +// Throws error if an invalid navigation type was presented. +function validateNavigationType(navigationType) { + if (navigationType == undefined) + throw new Error("A navigation type must be defined."); + if(Object.values(initiators).indexOf(navigationType) === -1) + throw new Error("Unknown navigation type."); +} + +// Similar to getURL without the path. If tests are run with +// --enable-browser-side-navigation (PlzNavigate) browser initiated navigation +// will have no initiator. The |navigationType| specifies if the navigation was +// performed by the browser or the renderer. +function getDomain(navigationType) { + validateNavigationType(navigationType); + if (navigationType == initiators.BROWSER_INITIATED && + usingBrowserSideNavigation) + return undefined; + else + return getURL('').slice(0,-1); +} + +// Similar to getServerURL without the path. If tests are run with +// --enable-browser-side-navigation (PlzNavigate) browser initiated navigation +// will have no initiator. The |navigationType| specifies if the navigation was +// performed by the browser or the renderer. +function getServerDomain(navigationType, opt_host, opt_scheme) { + validateNavigationType(navigationType); + if (navigationType == initiators.BROWSER_INITIATED && + usingBrowserSideNavigation) + return undefined; + else + return getServerURL(undefined, opt_host, opt_scheme).slice(0, -1); +} + // Helper to advance to the next test only when the tab has finished loading. // This is because tabs.update can sometimes fail if the tab is in the middle // of a navigation (from the previous test), resulting in flakiness. @@ -127,6 +171,10 @@ if (!('type' in expectedEventData[i].details)) { expectedEventData[i].details.type = "main_frame"; } + if ('initiator' in expectedEventData[i].details && + expectedEventData[i].details.initiator == undefined) { + delete expectedEventData[i].details.initiator; + } } }
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_blocking.js b/chrome/test/data/extensions/api_test/webrequest/test_blocking.js index 3cd5d9b..c084ad6a 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_blocking.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_blocking.js
@@ -44,7 +44,8 @@ details: { type: "main_frame", url: getURL("complexLoad/b.html"), - frameUrl: getURL("complexLoad/b.html") + frameUrl: getURL("complexLoad/b.html"), + initiator: getDomain(initiators.BROWSER_INITIATED) }, retval: {cancel: true} }, @@ -54,7 +55,8 @@ details: { url: getURL("complexLoad/b.html"), fromCache: false, - error: "net::ERR_BLOCKED_BY_CLIENT" + error: "net::ERR_BLOCKED_BY_CLIENT", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -79,7 +81,8 @@ method: "GET", type: "main_frame", url: getURLHttpSimpleLoad(), - frameUrl: getURLHttpSimpleLoad() + frameUrl: getURLHttpSimpleLoad(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, retval: {cancel: false} }, @@ -87,13 +90,15 @@ event: "onBeforeSendHeaders", details: { url: getURLHttpSimpleLoad(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) // Note: no requestHeaders because we don't ask for them. }, }, { label: "onSendHeaders", event: "onSendHeaders", details: { - url: getURLHttpSimpleLoad() + url: getURLHttpSimpleLoad(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onHeadersReceived", @@ -102,6 +107,7 @@ url: getURLHttpSimpleLoad(), statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, retval: {cancel: true} }, @@ -111,7 +117,8 @@ details: { url: getURLHttpSimpleLoad(), fromCache: false, - error: "net::ERR_BLOCKED_BY_CLIENT" + error: "net::ERR_BLOCKED_BY_CLIENT", + initiator: getServerDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -136,14 +143,16 @@ method: "GET", type: "main_frame", url: getURLHttpSimpleLoad(), - frameUrl: getURLHttpSimpleLoad() + frameUrl: getURLHttpSimpleLoad(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }, { label: "onBeforeSendHeaders", event: "onBeforeSendHeaders", details: { url: getURLHttpSimpleLoad(), - requestHeadersValid: true + requestHeadersValid: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, retval: {requestHeaders: [{name: "User-Agent"}]} }, @@ -153,7 +162,8 @@ event: "onSendHeaders", details: { url: getURLHttpSimpleLoad(), - requestHeadersValid: true + requestHeadersValid: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onHeadersReceived", @@ -162,6 +172,7 @@ url: getURLHttpSimpleLoad(), statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onResponseStarted", @@ -172,6 +183,7 @@ statusCode: 200, ip: "127.0.0.1", statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onCompleted", @@ -182,6 +194,7 @@ statusCode: 200, ip: "127.0.0.1", statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -208,14 +221,16 @@ method: "GET", type: "main_frame", url: getURLHttpSimpleLoad(), - frameUrl: getURLHttpSimpleLoad() + frameUrl: getURLHttpSimpleLoad(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }, { label: "onBeforeSendHeaders", event: "onBeforeSendHeaders", details: { url: getURLHttpSimpleLoad(), - requestHeadersValid: true + requestHeadersValid: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, retval: {foo: "bar"} }, @@ -224,7 +239,8 @@ event: "onSendHeaders", details: { url: getURLHttpSimpleLoad(), - requestHeadersValid: true + requestHeadersValid: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onHeadersReceived", @@ -233,6 +249,7 @@ url: getURLHttpSimpleLoad(), statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onResponseStarted", @@ -243,6 +260,7 @@ statusCode: 200, ip: "127.0.0.1", statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onCompleted", @@ -253,6 +271,7 @@ statusCode: 200, ip: "127.0.0.1", statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -274,7 +293,8 @@ event: "onBeforeRequest", details: { url: getURL("complexLoad/a.html"), - frameUrl: getURL("complexLoad/a.html") + frameUrl: getURL("complexLoad/a.html"), + initiator: getDomain(initiators.BROWSER_INITIATED) }, retval: {redirectUrl: getURL("simpleLoad/a.html")} }, @@ -286,6 +306,7 @@ fromCache: false, statusLine: "HTTP/1.1 307 Internal Redirect", statusCode: 307, + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeRequest-2", @@ -293,6 +314,7 @@ details: { url: getURL("simpleLoad/a.html"), frameUrl: getURL("simpleLoad/a.html"), + initiator: getDomain(initiators.BROWSER_INITIATED) }, }, { label: "onResponseStarted", @@ -302,6 +324,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -312,6 +335,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -345,13 +369,15 @@ event: "onBeforeRequest", details: { url: getURLEchoUserAgent(), - frameUrl: getURLEchoUserAgent() + frameUrl: getURLEchoUserAgent(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeSendHeaders", event: "onBeforeSendHeaders", details: { url: getURLEchoUserAgent(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) // Note: no requestHeaders because we don't ask for them. }, retval: {requestHeaders: [{name: "User-Agent", value: "FoobarUA"}]} @@ -359,7 +385,8 @@ { label: "onSendHeaders", event: "onSendHeaders", details: { - url: getURLEchoUserAgent() + url: getURLEchoUserAgent(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onHeadersReceived", @@ -368,6 +395,7 @@ url: getURLEchoUserAgent(), statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onResponseStarted", @@ -378,6 +406,7 @@ statusCode: 200, ip: "127.0.0.1", statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onCompleted", @@ -388,6 +417,7 @@ statusCode: 200, ip: "127.0.0.1", statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -420,13 +450,15 @@ event: "onBeforeRequest", details: { url: getURLEchoUserAgent(), - frameUrl: getURLEchoUserAgent() + frameUrl: getURLEchoUserAgent(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeSendHeaders", event: "onBeforeSendHeaders", details: { url: getURLEchoUserAgent(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) // Note: no requestHeaders because we don't ask for them. }, retval: {requestHeaders: [{name: "User-Agent", @@ -435,7 +467,8 @@ { label: "onSendHeaders", event: "onSendHeaders", details: { - url: getURLEchoUserAgent() + url: getURLEchoUserAgent(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onHeadersReceived", @@ -444,6 +477,7 @@ url: getURLEchoUserAgent(), statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onResponseStarted", @@ -454,6 +488,7 @@ statusCode: 200, ip: "127.0.0.1", statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onCompleted", @@ -464,6 +499,7 @@ statusCode: 200, ip: "127.0.0.1", statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -496,13 +532,15 @@ method: "GET", type: "main_frame", url: getURLSetCookie(), - frameUrl: getURLSetCookie() + frameUrl: getURLSetCookie(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeSendHeaders", event: "onBeforeSendHeaders", details: { url: getURLSetCookie(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) // Note: no requestHeaders because we don't ask for them. }, }, @@ -510,6 +548,7 @@ event: "onSendHeaders", details: { url: getURLSetCookie(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onHeadersReceived", @@ -519,6 +558,7 @@ statusLine: "HTTP/1.1 200 OK", statusCode: 200, responseHeadersExist: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, retval_function: function(name, details) { responseHeaders = details.responseHeaders; @@ -544,6 +584,7 @@ statusLine: "HTTP/1.1 200 OK", ip: "127.0.0.1", responseHeadersExist: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onCompleted", @@ -555,6 +596,7 @@ statusLine: "HTTP/1.1 200 OK", ip: "127.0.0.1", responseHeadersExist: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -587,13 +629,15 @@ method: "GET", type: "main_frame", url: getURLNonUTF8SetCookie(), - frameUrl: getURLNonUTF8SetCookie() + frameUrl: getURLNonUTF8SetCookie(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeSendHeaders", event: "onBeforeSendHeaders", details: { url: getURLNonUTF8SetCookie(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) // Note: no requestHeaders because we don't ask for them. }, }, @@ -601,6 +645,7 @@ event: "onSendHeaders", details: { url: getURLNonUTF8SetCookie(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onHeadersReceived", @@ -610,6 +655,7 @@ statusLine: "HTTP/1.1 200 OK", statusCode: 200, responseHeadersExist: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, retval_function: function(name, details) { responseHeaders = details.responseHeaders; @@ -642,6 +688,7 @@ statusLine: "HTTP/1.1 200 OK", ip: "127.0.0.1", responseHeadersExist: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onCompleted", @@ -653,6 +700,7 @@ statusLine: "HTTP/1.1 200 OK", ip: "127.0.0.1", responseHeadersExist: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -685,20 +733,23 @@ method: "GET", type: "main_frame", url: getURLHttpSimpleLoad(), - frameUrl: getURLHttpSimpleLoad() + frameUrl: getURLHttpSimpleLoad(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }, { label: "onBeforeSendHeaders", event: "onBeforeSendHeaders", details: { url: getURLHttpSimpleLoad(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) // Note: no requestHeaders because we don't ask for them. }, }, { label: "onSendHeaders", event: "onSendHeaders", details: { - url: getURLHttpSimpleLoad() + url: getURLHttpSimpleLoad(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onHeadersReceived", @@ -707,6 +758,7 @@ url: getURLHttpSimpleLoad(), statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, retval: {redirectUrl: getURL("simpleLoad/a.html")} }, @@ -719,6 +771,7 @@ statusCode: 302, fromCache: false, ip: "127.0.0.1", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeRequest-2", @@ -726,6 +779,7 @@ details: { url: getURL("simpleLoad/a.html"), frameUrl: getURL("simpleLoad/a.html"), + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }, { label: "onResponseStarted", @@ -735,6 +789,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -745,6 +800,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -759,8 +815,8 @@ navigateAndWait(getURLHttpSimpleLoad()); }, - // Checks that synchronous XHR requests from ourself are invisible to blocking - // handlers. + // Checks that synchronous XHR requests from ourself are invisible to + // blocking handlers. function syncXhrsFromOurselfAreInvisible() { expect( [ // events @@ -768,7 +824,8 @@ event: "onBeforeRequest", details: { url: getURL("simpleLoad/a.html"), - frameUrl: getURL("simpleLoad/a.html") + frameUrl: getURL("simpleLoad/a.html"), + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "a-onResponseStarted", @@ -778,6 +835,7 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -788,17 +846,19 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, - // We do not see onBeforeRequest for the XHR request here because it is - // handled by a blocking handler. + // We do not see onBeforeRequest for the XHR request here because it + // is handled by a blocking handler. { label: "x-onSendHeaders", event: "onSendHeaders", details: { url: getURLHttpXHRData(), tabId: 1, type: "xmlhttprequest", + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "x-onResponseStarted", @@ -811,6 +871,7 @@ tabId: 1, type: "xmlhttprequest", ip: "127.0.0.1", + initiator: getDomain(initiators.WEB_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -824,6 +885,7 @@ tabId: 1, type: "xmlhttprequest", ip: "127.0.0.1", + initiator: getDomain(initiators.WEB_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -831,7 +893,8 @@ event: "onBeforeRequest", details: { url: getURL("complexLoad/b.jpg"), - frameUrl: getURL("complexLoad/b.jpg") + frameUrl: getURL("complexLoad/b.jpg"), + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "b-onResponseStarted", @@ -841,6 +904,7 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -851,6 +915,7 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -871,8 +936,8 @@ }); }, - // Checks that asynchronous XHR requests from ourself are visible to blocking - // handlers. + // Checks that asynchronous XHR requests from ourself are visible to + // blocking handlers. function asyncXhrsFromOurselfAreVisible() { expect( [ // events @@ -880,7 +945,8 @@ event: "onBeforeRequest", details: { url: getURL("simpleLoad/a.html"), - frameUrl: getURL("simpleLoad/a.html") + frameUrl: getURL("simpleLoad/a.html"), + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "a-onResponseStarted", @@ -890,6 +956,7 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -900,6 +967,7 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -911,6 +979,7 @@ tabId: 1, type: "xmlhttprequest", frameUrl: "unknown frame URL", + initiator: getDomain(initiators.WEB_INITIATED) } }, { @@ -920,6 +989,7 @@ url: getURLHttpXHRData(), tabId: 1, type: "xmlhttprequest", + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "x-onSendHeaders", @@ -928,6 +998,7 @@ url: getURLHttpXHRData(), tabId: 1, type: "xmlhttprequest", + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "x-onResponseStarted", @@ -941,6 +1012,7 @@ type: "xmlhttprequest", ip: "127.0.0.1", // Request to chrome-extension:// url has no IP. + initiator: getDomain(initiators.WEB_INITIATED) } }, { @@ -952,6 +1024,7 @@ type: "xmlhttprequest", statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "x-onCompleted", @@ -965,13 +1038,15 @@ type: "xmlhttprequest", ip: "127.0.0.1", // Request to chrome-extension:// url has no IP. + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "b-onBeforeRequest", event: "onBeforeRequest", details: { url: getURL("complexLoad/b.jpg"), - frameUrl: getURL("complexLoad/b.jpg") + frameUrl: getURL("complexLoad/b.jpg"), + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "b-onResponseStarted", @@ -981,6 +1056,7 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -991,6 +1067,7 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -1028,6 +1105,7 @@ "&src=" + encodeURIComponent(requestedUrl)); var redirectTarget = getServerURL( "extensions/api_test/webrequest/cors/redirect_target.gif", "domain.tld"); + var initiator = getServerDomain(initiators.WEB_INITIATED); expect( [ // events { label: "onBeforeRequest-1", @@ -1037,6 +1115,7 @@ url: requestedUrl, // Frame URL unavailable because requests are filtered by type=image. frameUrl: "unknown frame URL", + initiator: initiator }, retval: {redirectUrl: redirectTarget} }, @@ -1049,6 +1128,7 @@ statusLine: "HTTP/1.1 307 Internal Redirect", statusCode: 307, fromCache: false, + initiator: initiator } }, { label: "onBeforeRequest-2", @@ -1058,6 +1138,7 @@ url: redirectTarget, // Frame URL unavailable because requests are filtered by type=image. frameUrl: "unknown frame URL", + initiator: initiator }, }, { @@ -1066,6 +1147,7 @@ details: { type: "image", url: redirectTarget, + initiator: initiator } }, { @@ -1074,6 +1156,7 @@ details: { type: "image", url: redirectTarget, + initiator: initiator } }, { @@ -1084,6 +1167,7 @@ url: redirectTarget, statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: initiator } }, { label: "onResponseStarted", @@ -1095,6 +1179,7 @@ statusCode: 200, ip: "127.0.0.1", statusLine: "HTTP/1.1 200 OK", + initiator: initiator } }, { label: "onCompleted", @@ -1106,6 +1191,7 @@ statusCode: 200, ip: "127.0.0.1", statusLine: "HTTP/1.1 200 OK", + initiator: initiator } }, // After the image loads, the test will load the following URL @@ -1118,6 +1204,7 @@ url: getServerURL("signal_that_image_loaded_successfully"), // Frame URL unavailable because requests are filtered by type=image. frameUrl: "unknown frame URL", + initiator: initiator }, retval: {cancel: true} }, @@ -1128,6 +1215,7 @@ url: getServerURL("signal_that_image_loaded_successfully"), fromCache: false, error: "net::ERR_BLOCKED_BY_CLIENT", + initiator: initiator } }, ],
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_complex.js b/chrome/test/data/extensions/api_test/webrequest/test_complex.js index a66ac3b2..383ab9f 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_complex.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_complex.js
@@ -22,7 +22,8 @@ details: { type: "main_frame", url: getURL("complexLoad/a.html"), - frameUrl: getURL("complexLoad/a.html") + frameUrl: getURL("complexLoad/a.html"), + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "b.html-onBeforeRequest", @@ -33,6 +34,7 @@ frameUrl: getURL("complexLoad/b.html"), frameId: 1, parentFrameId: 0, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "b.jpg-onBeforeRequest", @@ -43,6 +45,7 @@ frameUrl: getURL("complexLoad/b.html"), frameId: 1, parentFrameId: 0, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "a.html-onResponseStarted", @@ -53,6 +56,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -66,6 +70,7 @@ statusLine: "HTTP/1.1 200 OK", frameId: 1, parentFrameId: 0, + initiator: getDomain(initiators.WEB_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -79,6 +84,7 @@ statusLine: "HTTP/1.1 200 OK", frameId: 1, parentFrameId: 0, + initiator: getDomain(initiators.WEB_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -90,6 +96,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -103,6 +110,7 @@ statusLine: "HTTP/1.1 200 OK", frameId: 1, parentFrameId: 0, + initiator: getDomain(initiators.WEB_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -116,6 +124,7 @@ statusLine: "HTTP/1.1 200 OK", frameId: 1, parentFrameId: 0, + initiator: getDomain(initiators.WEB_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -141,7 +150,8 @@ details: { type: "main_frame", url: getURL("complexLoad/a.html"), - frameUrl: getURL("complexLoad/a.html") + frameUrl: getURL("complexLoad/a.html"), + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "b-onBeforeRequest", @@ -153,6 +163,7 @@ frameUrl: "unknown frame URL", frameId: 1, parentFrameId: 0, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "a-onResponseStarted", @@ -163,6 +174,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -176,6 +188,7 @@ statusLine: "HTTP/1.1 200 OK", frameId: 1, parentFrameId: 0, + initiator: getDomain(initiators.WEB_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -187,6 +200,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -200,6 +214,7 @@ statusLine: "HTTP/1.1 200 OK", frameId: 1, parentFrameId: 0, + initiator: getDomain(initiators.WEB_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -230,7 +245,8 @@ details: { type: "main_frame", url: getURLHttpXHR(), - frameUrl: getURLHttpXHR() + frameUrl: getURLHttpXHR(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeSendHeaders-1", @@ -238,6 +254,7 @@ details: { type: "main_frame", url: getURLHttpXHR(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onSendHeaders-1", @@ -245,6 +262,7 @@ details: { type: "main_frame", url: getURLHttpXHR(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onHeadersReceived-1", @@ -254,6 +272,7 @@ url: getURLHttpXHR(), statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onResponseStarted-1", @@ -265,6 +284,7 @@ ip: "127.0.0.1", fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onCompleted-1", @@ -276,6 +296,7 @@ ip: "127.0.0.1", fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "a.js-onBeforeRequest", @@ -283,7 +304,8 @@ details: { type: "script", url: getURLHttpXHRJavaScript(), - frameUrl: getURLHttpXHR() + frameUrl: getURLHttpXHR(), + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "a.js-onBeforeSendHeaders", @@ -291,6 +313,7 @@ details: { type: "script", url: getURLHttpXHRJavaScript(), + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "a.js-onSendHeaders", @@ -298,6 +321,7 @@ details: { type: "script", url: getURLHttpXHRJavaScript(), + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "a.js-onHeadersReceived", @@ -307,6 +331,7 @@ url: getURLHttpXHRJavaScript(), statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "a.js-onResponseStarted", @@ -318,6 +343,7 @@ ip: "127.0.0.1", fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "a.js-onCompleted", @@ -329,6 +355,7 @@ ip: "127.0.0.1", fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "onBeforeRequest-2", @@ -336,7 +363,8 @@ details: { type: "xmlhttprequest", url: getURLHttpXHRData(), - frameUrl: getURLHttpXHR() + frameUrl: getURLHttpXHR(), + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "onBeforeSendHeaders-2", @@ -344,6 +372,7 @@ details: { type: "xmlhttprequest", url: getURLHttpXHRData(), + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "onSendHeaders-2", @@ -351,6 +380,7 @@ details: { type: "xmlhttprequest", url: getURLHttpXHRData(), + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "onHeadersReceived-2", @@ -360,6 +390,7 @@ url: getURLHttpXHRData(), statusLine: "HTTP/1.1 200 OK", statusCode: 200, + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "onResponseStarted-2", @@ -371,6 +402,7 @@ ip: "127.0.0.1", fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "onCompleted-2", @@ -382,6 +414,7 @@ ip: "127.0.0.1", fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.WEB_INITIATED) } } ],
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_declarative1.js b/chrome/test/data/extensions/api_test/webrequest/test_declarative1.js index 8a5b286..51ea16a 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_declarative1.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_declarative1.js
@@ -65,23 +65,31 @@ event: "onBeforeRequest", details: { url: getURLOfHTMLWithThirdParty(), - frameUrl: getURLOfHTMLWithThirdParty() + frameUrl: getURLOfHTMLWithThirdParty(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeSendHeaders", event: "onBeforeSendHeaders", - details: {url: getURLOfHTMLWithThirdParty()} + details: { + url: getURLOfHTMLWithThirdParty(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) + } }, { label: "onSendHeaders", event: "onSendHeaders", - details: {url: getURLOfHTMLWithThirdParty()} + details: { + url: getURLOfHTMLWithThirdParty(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) + } }, { label: "onHeadersReceived", event: "onHeadersReceived", details: { url: getURLOfHTMLWithThirdParty(), statusLine: "HTTP/1.1 200 OK", - statusCode: 200 + statusCode: 200, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onResponseStarted", @@ -91,7 +99,8 @@ fromCache: false, ip: "127.0.0.1", statusCode: 200, - statusLine: "HTTP/1.1 200 OK" + statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onCompleted", @@ -101,7 +110,8 @@ ip: "127.0.0.1", url: getURLOfHTMLWithThirdParty(), statusCode: 200, - statusLine: "HTTP/1.1 200 OK" + statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "img-onBeforeRequest", @@ -109,7 +119,8 @@ details: { type: "image", url: "http://non_existing_third_party.com/image.png", - frameUrl: getURLOfHTMLWithThirdParty() + frameUrl: getURLOfHTMLWithThirdParty(), + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "img-onErrorOccurred", @@ -118,7 +129,8 @@ error: "net::ERR_BLOCKED_BY_CLIENT", fromCache: false, type: "image", - url: "http://non_existing_third_party.com/image.png" + url: "http://non_existing_third_party.com/image.png", + initiator: getServerDomain(initiators.WEB_INITIATED) } }, ]; @@ -143,7 +155,8 @@ details: { url: getURLHttpWithHeaders(), fromCache: false, - error: "net::ERR_BLOCKED_BY_CLIENT" + error: "net::ERR_BLOCKED_BY_CLIENT", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -179,19 +192,22 @@ event: "onBeforeRequest", details: { url: getURLHttpWithHeaders(), - frameUrl: getURLHttpWithHeaders() + frameUrl: getURLHttpWithHeaders(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeSendHeaders", event: "onBeforeSendHeaders", details: { url: getURLHttpWithHeaders(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onSendHeaders", event: "onSendHeaders", details: { url: getURLHttpWithHeaders(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onHeadersReceived", @@ -199,7 +215,8 @@ details: { statusLine: "HTTP/1.1 200 OK", url: getURLHttpWithHeaders(), - statusCode: 200 + statusCode: 200, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onErrorOccurred", @@ -207,7 +224,8 @@ details: { url: getURLHttpWithHeaders(), fromCache: false, - error: "net::ERR_BLOCKED_BY_CLIENT" + error: "net::ERR_BLOCKED_BY_CLIENT", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -267,7 +285,8 @@ event: "onBeforeRequest", details: { url: getURLOfHTMLWithThirdParty(), - frameUrl: getURLOfHTMLWithThirdParty() + frameUrl: getURLOfHTMLWithThirdParty(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onErrorOccurred", @@ -275,7 +294,8 @@ details: { url: getURLOfHTMLWithThirdParty(), fromCache: false, - error: "net::ERR_BLOCKED_BY_CLIENT" + error: "net::ERR_BLOCKED_BY_CLIENT", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -304,7 +324,8 @@ details: { type: "main_frame", url: getURLHttpComplex(), - frameUrl: getURLHttpComplex() + frameUrl: getURLHttpComplex(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }, { label: "onBeforeRedirect", @@ -315,6 +336,7 @@ fromCache: false, statusLine: "HTTP/1.1 307 Internal Redirect", statusCode: 307, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeRequest-b", @@ -323,6 +345,7 @@ type: "main_frame", url: getURLHttpSimple(), frameUrl: getURLHttpSimple(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }, { label: "onCompleted", @@ -333,6 +356,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -360,6 +384,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, // We cannot wait for onCompleted signals because these are not sent @@ -376,6 +401,7 @@ statusLine: "HTTP/1.1 307 Internal Redirect", statusCode: 307, type: "image", + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: "onBeforeRedirect-2", @@ -390,6 +416,7 @@ statusLine: "HTTP/1.1 307 Internal Redirect", statusCode: 307, type: "sub_frame", + initiator: getServerDomain(initiators.WEB_INITIATED) } }, ], @@ -418,7 +445,8 @@ details: { type: "main_frame", url: getURLHttpWithHeaders(), - frameUrl: getURLHttpWithHeaders() + frameUrl: getURLHttpWithHeaders(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }, { label: "onBeforeRedirect", @@ -430,6 +458,7 @@ statusCode: 302, fromCache: false, ip: "127.0.0.1", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: "onBeforeRequest-b", @@ -438,6 +467,7 @@ type: "main_frame", url: getURLHttpSimple(), frameUrl: getURLHttpSimple(), + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }, { label: "onCompleted", @@ -448,6 +478,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -475,6 +506,7 @@ fromCache: false, statusCode: 200, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ], @@ -498,7 +530,8 @@ details: { url: getURLHttpSimple(), fromCache: false, - error: "net::ERR_BLOCKED_BY_CLIENT" + error: "net::ERR_BLOCKED_BY_CLIENT", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ],
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_declarative2.js b/chrome/test/data/extensions/api_test/webrequest/test_declarative2.js index fd23a658..3d957b2 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_declarative2.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_declarative2.js
@@ -162,6 +162,7 @@ fromCache: false, statusLine: "HTTP/1.1 200 OK", ip: "127.0.0.1", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } } ], @@ -263,7 +264,8 @@ details: { url: getURLHttpSimple(), fromCache: false, - error: "net::ERR_BLOCKED_BY_CLIENT" + error: "net::ERR_BLOCKED_BY_CLIENT", + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, ],
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_newTab.js b/chrome/test/data/extensions/api_test/webrequest/test_newTab.js index ab46ce1..e54cdfd5 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_newTab.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_newTab.js
@@ -12,7 +12,8 @@ event: "onBeforeRequest", details: { url: getURL("newTab/a.html"), - frameUrl: getURL("newTab/a.html") + frameUrl: getURL("newTab/a.html"), + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "a-onResponseStarted", @@ -22,6 +23,7 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED) // Request to chrome-extension:// url has no IP. } }, @@ -32,7 +34,8 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", - // Request to chrome-extension:// url has no IP. + initiator: getDomain(initiators.BROWSER_INITIATED) + // Request to chrome-extension:// url has no IP. } }, { label: "b-onBeforeRequest", @@ -41,6 +44,7 @@ url: getURL("newTab/b.html"), frameUrl: getURL("newTab/b.html"), tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "b-onResponseStarted", @@ -52,6 +56,7 @@ statusLine: "HTTP/1.1 200 OK", // Request to chrome-extension:// url has no IP. tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "b-onCompleted", @@ -63,6 +68,7 @@ statusLine: "HTTP/1.1 200 OK", // Request to chrome-extension:// url has no IP. tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) } }, ],
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_osdd.js b/chrome/test/data/extensions/api_test/webrequest/test_osdd.js index 1477772..ba33198 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_osdd.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_osdd.js
@@ -28,6 +28,7 @@ // of type "other". frameUrl: 'unknown frame URL', tabId: 0, + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -36,6 +37,7 @@ type: 'other', url: getOSDDURL(), tabId: 0, + initiator: getServerDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -44,6 +46,7 @@ type: 'other', url: getOSDDURL(), tabId: 0, + initiator: getServerDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -54,6 +57,7 @@ tabId: 0, statusLine: 'HTTP/1.1 404 Not Found', statusCode: 404, + initiator: getServerDomain(initiators.WEB_INITIATED) }, }, { label: 'onResponseStarted', @@ -66,6 +70,7 @@ fromCache: false, statusLine: 'HTTP/1.1 404 Not Found', statusCode: 404, + initiator: getServerDomain(initiators.WEB_INITIATED) }, }, { label: 'onCompleted', @@ -78,6 +83,7 @@ fromCache: false, statusLine: 'HTTP/1.1 404 Not Found', statusCode: 404, + initiator: getServerDomain(initiators.WEB_INITIATED) }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders',
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_post.js b/chrome/test/data/extensions/api_test/webrequest/test_post.js index dc8707b..f0a1731 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_post.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_post.js
@@ -27,7 +27,8 @@ method: "GET", type: "main_frame", url: getURL(dirName + formFile), - frameUrl: getURL(dirName + formFile) + frameUrl: getURL(dirName + formFile), + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "a-onResponseStarted", @@ -38,7 +39,8 @@ statusCode: 200, statusLine: "HTTP/1.1 200 OK", type: "main_frame", - url: getURL(dirName + formFile) + url: getURL(dirName + formFile), + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "a-onCompleted", @@ -49,7 +51,8 @@ statusCode: 200, statusLine: "HTTP/1.1 200 OK", type: "main_frame", - url: getURL(dirName + formFile) + url: getURL(dirName + formFile), + initiator: getDomain(initiators.BROWSER_INITIATED) } }, { label: "s-onBeforeRequest", @@ -58,7 +61,8 @@ method: "GET", type: "script", url: getURL("requestBody/submit.js"), - frameUrl: getURL(dirName + formFile) + frameUrl: getURL(dirName + formFile), + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "s-onResponseStarted", @@ -69,7 +73,8 @@ statusCode: 200, statusLine: "HTTP/1.1 200 OK", type: "script", - url: getURL("requestBody/submit.js") + url: getURL("requestBody/submit.js"), + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "s-onCompleted", @@ -80,7 +85,8 @@ statusCode: 200, statusLine: "HTTP/1.1 200 OK", type: "script", - url: getURL("requestBody/submit.js") + url: getURL("requestBody/submit.js"), + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "b-onBeforeRequest", @@ -94,7 +100,8 @@ formData: formData } : { raw: [{bytes: {}}] // ArrayBuffer - } + }, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "b-onResponseStarted", @@ -105,7 +112,8 @@ statusCode: 200, statusLine: "HTTP/1.1 200 OK", type: "main_frame", - url: getURL("simpleLoad/a.html") + url: getURL("simpleLoad/a.html"), + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: "b-onCompleted", @@ -116,7 +124,8 @@ statusCode: 200, statusLine: "HTTP/1.1 200 OK", type: "main_frame", - url: getURL("simpleLoad/a.html") + url: getURL("simpleLoad/a.html"), + initiator: getDomain(initiators.WEB_INITIATED) } } ],
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_simple.js b/chrome/test/data/extensions/api_test/webrequest/test_simple.js index 5a2218d..7038f99 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_simple.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_simple.js
@@ -20,7 +20,8 @@ event: "onBeforeRequest", details: { url: getURL("simpleLoad/a.html"), - frameUrl: getURL("simpleLoad/a.html") + frameUrl: getURL("simpleLoad/a.html"), + initiator: getDomain(initiators.BROWSER_INITIATED), } }, { label: "a-onResponseStarted", @@ -30,6 +31,7 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED), // Request to chrome-extension:// url has no IP. } }, @@ -40,6 +42,7 @@ statusCode: 200, fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getDomain(initiators.BROWSER_INITIATED), // Request to chrome-extension:// url has no IP. } }, @@ -58,21 +61,24 @@ event: "onBeforeRequest", details: { url: getURLHttpSimpleLoadRedirect(), - frameUrl: getURLHttpSimpleLoadRedirect() + frameUrl: getURLHttpSimpleLoadRedirect(), + initiator: getServerDomain(initiators.BROWSER_INITIATED), } }, { label: "onBeforeSendHeaders-1", event: "onBeforeSendHeaders", details: { url: getURLHttpSimpleLoadRedirect(), - requestHeadersValid: true + requestHeadersValid: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED), } }, { label: "onSendHeaders-1", event: "onSendHeaders", details: { url: getURLHttpSimpleLoadRedirect(), - requestHeadersValid: true + requestHeadersValid: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED), } }, { label: "onHeadersReceived-1", @@ -81,7 +87,8 @@ url: getURLHttpSimpleLoadRedirect(), responseHeadersExist: true, statusLine: "HTTP/1.1 301 Moved Permanently", - statusCode: 301 + statusCode: 301, + initiator: getServerDomain(initiators.BROWSER_INITIATED), } }, { label: "onBeforeRedirect", @@ -93,28 +100,32 @@ responseHeadersExist: true, ip: "127.0.0.1", fromCache: false, - statusLine: "HTTP/1.1 301 Moved Permanently" + statusLine: "HTTP/1.1 301 Moved Permanently", + initiator: getServerDomain(initiators.BROWSER_INITIATED), } }, { label: "onBeforeRequest-2", event: "onBeforeRequest", details: { url: getURLHttpSimpleLoad(), - frameUrl: getURLHttpSimpleLoad() + frameUrl: getURLHttpSimpleLoad(), + initiator: getServerDomain(initiators.BROWSER_INITIATED), } }, { label: "onBeforeSendHeaders-2", event: "onBeforeSendHeaders", details: { url: getURLHttpSimpleLoad(), - requestHeadersValid: true + requestHeadersValid: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED), } }, { label: "onSendHeaders-2", event: "onSendHeaders", details: { url: getURLHttpSimpleLoad(), - requestHeadersValid: true + requestHeadersValid: true, + initiator: getServerDomain(initiators.BROWSER_INITIATED), } }, { label: "onHeadersReceived-2", @@ -123,7 +134,8 @@ url: getURLHttpSimpleLoad(), responseHeadersExist: true, statusLine: "HTTP/1.1 200 OK", - statusCode: 200 + statusCode: 200, + initiator: getServerDomain(initiators.BROWSER_INITIATED), } }, { label: "onResponseStarted", @@ -135,6 +147,7 @@ ip: "127.0.0.1", fromCache: false, statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED), } }, { label: "onCompleted", @@ -145,7 +158,8 @@ ip: "127.0.0.1", fromCache: false, responseHeadersExist: true, - statusLine: "HTTP/1.1 200 OK" + statusLine: "HTTP/1.1 200 OK", + initiator: getServerDomain(initiators.BROWSER_INITIATED), } } ], @@ -167,7 +181,8 @@ event: "onBeforeRequest", details: { url: getURL("does_not_exist.html"), - frameUrl: getURL("does_not_exist.html") + frameUrl: getURL("does_not_exist.html"), + initiator: getDomain(initiators.BROWSER_INITIATED), } }, { label: "onErrorOccurred", @@ -176,6 +191,7 @@ url: getURL("does_not_exist.html"), fromCache: false, error: "net::ERR_FILE_NOT_FOUND", + initiator: getDomain(initiators.BROWSER_INITIATED), // Request to chrome-extension:// url has no IP. } },
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_types.js b/chrome/test/data/extensions/api_test/webrequest/test_types.js index 48933454..013660f 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_types.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_types.js
@@ -70,6 +70,7 @@ // tabId 0 = tab opened by test runner; // tabId 1 = this tab. tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -78,6 +79,7 @@ type: 'stylesheet', url: getStyleURL(), tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -86,6 +88,7 @@ type: 'stylesheet', url: getStyleURL(), tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -96,6 +99,7 @@ tabId: 1, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onResponseStarted', @@ -108,6 +112,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onCompleted', @@ -120,6 +125,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders', @@ -228,6 +234,7 @@ // tabId 0 = tab opened by test runner; // tabId 1 = this tab. tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -236,6 +243,7 @@ type: 'font', url: getFontURL(), tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -244,6 +252,7 @@ type: 'font', url: getFontURL(), tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -254,6 +263,7 @@ tabId: 1, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onResponseStarted', @@ -266,6 +276,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onCompleted', @@ -278,6 +289,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders', @@ -297,6 +309,7 @@ // tabId 0 = tab opened by test runner; // tabId 1 = this tab. tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -305,6 +318,7 @@ type: 'script', url: getWorkerURL(), tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -313,6 +327,7 @@ type: 'script', url: getWorkerURL(), tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -323,6 +338,7 @@ tabId: 1, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onResponseStarted', @@ -335,6 +351,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onCompleted', @@ -347,6 +364,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders', @@ -372,6 +390,7 @@ frameUrl: 'unknown frame URL', frameId: 0, tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -382,6 +401,7 @@ url: getPingURL(), frameId: 0, tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -392,6 +412,7 @@ url: getPingURL(), frameId: 0, tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -404,6 +425,7 @@ tabId: 1, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onResponseStarted', @@ -418,6 +440,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onCompleted', @@ -432,6 +455,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders', @@ -454,6 +478,7 @@ frameUrl: 'unknown frame URL', frameId: 0, tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -464,6 +489,7 @@ url: getBeaconURL(), frameId: 0, tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -474,6 +500,7 @@ url: getBeaconURL(), frameId: 0, tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -486,6 +513,7 @@ tabId: 1, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onResponseStarted', @@ -500,6 +528,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onCompleted', @@ -514,6 +543,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders', @@ -534,6 +564,7 @@ frameId: 1, parentFrameId: 0, tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -545,6 +576,7 @@ frameId: 1, parentFrameId: 0, tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -556,6 +588,7 @@ frameId: 1, parentFrameId: 0, tabId: 1, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -572,6 +605,7 @@ tabId: -1, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onResponseStarted', @@ -587,6 +621,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onCompleted', @@ -602,6 +637,7 @@ fromCache: false, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, + initiator: getDomain(initiators.WEB_INITIATED) }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders', @@ -629,6 +665,7 @@ frameId: 1, parentFrameId: 0, tabId: 1, + initiator: getServerDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -640,6 +677,7 @@ frameId: 1, parentFrameId: 0, tabId: 1, + initiator: getServerDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -651,6 +689,7 @@ frameId: 1, parentFrameId: 0, tabId: 1, + initiator: getServerDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -664,6 +703,7 @@ tabId: 1, statusLine: 'HTTP/1.1 404 Not Found', statusCode: 404, + initiator: getServerDomain(initiators.WEB_INITIATED) }, }, { label: 'onResponseStarted', @@ -679,6 +719,7 @@ fromCache: false, statusLine: 'HTTP/1.1 404 Not Found', statusCode: 404, + initiator: getServerDomain(initiators.WEB_INITIATED) }, }, { label: 'onCompleted', @@ -694,6 +735,7 @@ fromCache: false, statusLine: 'HTTP/1.1 404 Not Found', statusCode: 404, + initiator: getServerDomain(initiators.WEB_INITIATED) }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders',
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_unload1.js b/chrome/test/data/extensions/api_test/webrequest/test_unload1.js index d46ecdd..a2b4ad10 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_unload1.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_unload1.js
@@ -7,6 +7,7 @@ // removes it. function insertSlowCrossOriginFrameAndRemove() { const url = getSlowURL('frame-in-extension-url'); + const initiator = getServerDomain(initiators.BROWSER_INITIATED); expect([ { label: 'onBeforeRequest', @@ -18,6 +19,7 @@ parentFrameId: 0, frameUrl: url, tabId: 1, + initiator: getServerDomain(initiators.BROWSER_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -28,6 +30,7 @@ frameId: 1, parentFrameId: 0, tabId: 1, + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }, { label: 'onSendHeaders', @@ -38,6 +41,7 @@ frameId: 1, parentFrameId: 0, tabId: 1, + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }, { label: 'onErrorOccurred', @@ -50,6 +54,7 @@ tabId: 1, fromCache: false, error: 'net::ERR_ABORTED', + initiator: getServerDomain(initiators.BROWSER_INITIATED) }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders',
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_unload5.js b/chrome/test/data/extensions/api_test/webrequest/test_unload5.js index 2562df0..be5afb2 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_unload5.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_unload5.js
@@ -10,6 +10,7 @@ function startXMLHttpRequestAndRemoveFrame() { const hostname = 'slow-resourcetype-xhr-immediately-remove-frame'; const url = getSlowURL(hostname); + const initiator = getServerDomain(initiators.WEB_INITIATED, hostname); const mainUrl = getPageWithFrame('empty.html', hostname); expect([ @@ -21,6 +22,7 @@ frameId: 1, parentFrameId: 0, frameUrl: 'unknown frame URL', + initiator: initiator } }, { label: 'onBeforeSendHeaders', @@ -30,6 +32,7 @@ url, frameId: 1, parentFrameId: 0, + initiator: initiator }, }, { label: 'onSendHeaders', @@ -39,6 +42,7 @@ url, frameId: 1, parentFrameId: 0, + initiator: initiator }, }, { label: 'onErrorOccurred', @@ -50,6 +54,7 @@ parentFrameId: 0, fromCache: false, error: 'net::ERR_ABORTED', + initiator: initiator }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders', @@ -76,6 +81,7 @@ function startXMLHttpRequestAndRemoveTab() { const hostname = 'slow-resourcetype-xhr-immediately-remove-tab'; const url = getSlowURL(hostname); + const initiator = getServerDomain(initiators.WEB_INITIATED, hostname); const mainUrl = getServerURL('empty.html', hostname); expect([ @@ -86,6 +92,7 @@ url, frameUrl: 'unknown frame URL', tabId: 1, + initiator: initiator } }, { label: 'onBeforeSendHeaders', @@ -94,6 +101,7 @@ type: 'xmlhttprequest', url, tabId: 1, + initiator: initiator }, }, { label: 'onSendHeaders', @@ -102,6 +110,7 @@ type: 'xmlhttprequest', url, tabId: 1, + initiator: initiator }, }, { label: 'onErrorOccurred', @@ -112,6 +121,7 @@ fromCache: false, error: 'net::ERR_ABORTED', tabId: 1, + initiator: initiator }, }], [['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders',
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_unload6.js b/chrome/test/data/extensions/api_test/webrequest/test_unload6.js index de78e2e..40d37e9 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_unload6.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_unload6.js
@@ -51,6 +51,7 @@ const mainUrl = getServerURL('empty.html', hostname1); const frameUrl1 = getSlowURL(hostname1); const frameUrl2 = getSlowURL(hostname2); + const initiator = getServerDomain(initiators.WEB_INITIATED, hostname1) awaitOnErrorOccurred(2, function(results) { // The order of the URLs doesn't matter. @@ -69,6 +70,7 @@ tabId, type: 'sub_frame', fromCache: false, + initiator: initiator, error: 'net::ERR_ABORTED', }, { method: 'GET', @@ -77,6 +79,7 @@ tabId, type: 'sub_frame', fromCache: false, + initiator: initiator, error: 'net::ERR_ABORTED', }], results); }); @@ -113,6 +116,7 @@ tabId, type: 'sub_frame', fromCache: false, + initiator: getServerDomain(initiators.WEB_INITIATED, hostname1), error: 'net::ERR_ABORTED', }], results); });
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_websocket.js b/chrome/test/data/extensions/api_test/webrequest/test_websocket.js index 87c8198b..c260460 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_websocket.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_websocket.js
@@ -17,6 +17,7 @@ type: 'websocket', // TODO(pkalinnikov): Figure out why the frame URL is unknown. frameUrl: 'unknown frame URL', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onBeforeSendHeaders', @@ -24,6 +25,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -31,6 +33,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -40,6 +43,7 @@ type: 'websocket', statusCode: 101, statusLine: 'HTTP/1.1 101 Switching Protocols', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onResponseStarted', @@ -51,6 +55,7 @@ fromCache: false, statusCode: 101, statusLine: 'HTTP/1.1 101 Switching Protocols', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onCompleted', @@ -61,6 +66,7 @@ fromCache: false, statusCode: 101, statusLine: 'HTTP/1.1 101 Switching Protocols', + initiator: getDomain(initiators.WEB_INITIATED) } }, ], @@ -86,6 +92,7 @@ url: url, type: 'websocket', frameUrl: 'unknown frame URL', + initiator: getDomain(initiators.WEB_INITIATED) }, retval: {cancel: true} }, @@ -96,6 +103,7 @@ url: url, type: 'websocket', fromCache: false, + initiator: getDomain(initiators.WEB_INITIATED), error: 'net::ERR_BLOCKED_BY_CLIENT' } }, @@ -123,6 +131,7 @@ url: url, type: 'websocket', frameUrl: 'unknown frame URL', + initiator: getDomain(initiators.WEB_INITIATED) }, retval: {redirectUrl: redirectedUrl1} }, @@ -131,6 +140,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -138,6 +148,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -147,6 +158,7 @@ type: 'websocket', statusCode: 101, statusLine: 'HTTP/1.1 101 Switching Protocols', + initiator: getDomain(initiators.WEB_INITIATED) }, retval: {redirectUrl: redirectedUrl2} }, @@ -157,6 +169,7 @@ type: 'websocket', ip: '127.0.0.1', fromCache: false, + initiator: getDomain(initiators.WEB_INITIATED), statusCode: 101, statusLine: 'HTTP/1.1 101 Switching Protocols', }, @@ -167,6 +180,7 @@ url: url, type: 'websocket', fromCache: false, + initiator: getDomain(initiators.WEB_INITIATED), statusCode: 101, statusLine: 'HTTP/1.1 101 Switching Protocols', }
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_websocket_auth.js b/chrome/test/data/extensions/api_test/webrequest/test_websocket_auth.js index b99416ca..79fc288 100644 --- a/chrome/test/data/extensions/api_test/webrequest/test_websocket_auth.js +++ b/chrome/test/data/extensions/api_test/webrequest/test_websocket_auth.js
@@ -15,6 +15,7 @@ url: url, type: 'websocket', frameUrl: 'unknown frame URL', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onBeforeSendHeaders', @@ -22,6 +23,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -29,6 +31,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onHeadersReceived', @@ -39,6 +42,7 @@ statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', responseHeadersExist: true, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onAuthRequired', @@ -53,6 +57,7 @@ statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', responseHeadersExist: true, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onResponseStarted', @@ -65,6 +70,7 @@ statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', responseHeadersExist: true, + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onErrorOccurred', @@ -74,6 +80,7 @@ type: 'websocket', fromCache: false, error: 'net::ERR_ABORTED', + initiator: getDomain(initiators.WEB_INITIATED) } }, ], @@ -99,6 +106,7 @@ url: url, type: 'websocket', frameUrl: 'unknown frame URL', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -106,6 +114,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -113,6 +122,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onHeadersReceived', @@ -122,6 +132,7 @@ type: 'websocket', statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onAuthRequired', @@ -135,6 +146,7 @@ challenger: {host: 'localhost', port: testWebSocketPort}, statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onResponseStarted', @@ -146,6 +158,7 @@ ip: '127.0.0.1', statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onErrorOccurred', @@ -155,6 +168,7 @@ type: 'websocket', fromCache: false, error: 'net::ERR_ABORTED', + initiator: getDomain(initiators.WEB_INITIATED) } }, ], @@ -178,6 +192,7 @@ url: url, type: 'websocket', frameUrl: 'unknown frame URL', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -185,6 +200,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -192,6 +208,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onHeadersReceived', @@ -201,6 +218,7 @@ type: 'websocket', statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onAuthRequired', @@ -214,6 +232,7 @@ challenger: {host: 'localhost', port: testWebSocketPort}, statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', + initiator: getDomain(initiators.WEB_INITIATED) }, retval: {cancel: true} }, @@ -226,6 +245,7 @@ ip: '127.0.0.1', statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onErrorOccurred', @@ -235,6 +255,7 @@ type: 'websocket', fromCache: false, error: 'net::ERR_ABORTED', + initiator: getDomain(initiators.WEB_INITIATED) } }, ], @@ -258,6 +279,7 @@ url: url, type: 'websocket', frameUrl: 'unknown frame URL', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onBeforeSendHeaders', @@ -265,6 +287,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onSendHeaders', @@ -272,6 +295,7 @@ details: { url: url, type: 'websocket', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onHeadersReceived', @@ -281,6 +305,7 @@ type: 'websocket', statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', + initiator: getDomain(initiators.WEB_INITIATED) } }, { label: 'onAuthRequired', @@ -294,6 +319,7 @@ challenger: {host: 'localhost', port: testWebSocketPort}, statusCode: 401, statusLine: 'HTTP/1.0 401 Unauthorized', + initiator: getDomain(initiators.WEB_INITIATED) }, // Note: The test WebSocket server accepts only these credentials. retval: {authCredentials: {username: 'test', password: 'test'}} @@ -307,6 +333,7 @@ fromCache: false, statusCode: 101, statusLine: 'HTTP/1.1 101 Switching Protocols', + initiator: getDomain(initiators.WEB_INITIATED) }, }, { label: 'onCompleted', @@ -317,6 +344,7 @@ fromCache: false, statusCode: 101, statusLine: 'HTTP/1.1 101 Switching Protocols', + initiator: getDomain(initiators.WEB_INITIATED) } }, ],
diff --git a/chrome/test/data/extensions/api_test/webrequest_permissions/initiator/background.js b/chrome/test/data/extensions/api_test/webrequest_permissions/initiator/background.js new file mode 100644 index 0000000..56fd3c2 --- /dev/null +++ b/chrome/test/data/extensions/api_test/webrequest_permissions/initiator/background.js
@@ -0,0 +1,10 @@ +// Copyright 2017 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. + +chrome.webRequest.onBeforeRequest.addListener((details) => { + chrome.test.sendMessage( + (details.initiator === undefined) ? 'NO_INITIATOR' : details.initiator); +}, {urls: ['*://*/extensions/api_test/webrequest/xhr/data.json']}, []); + +chrome.test.sendMessage('ready');
diff --git a/chrome/test/data/extensions/api_test/webrequest_permissions/initiator/manifest.json b/chrome/test/data/extensions/api_test/webrequest_permissions/initiator/manifest.json new file mode 100644 index 0000000..7ba8e971 --- /dev/null +++ b/chrome/test/data/extensions/api_test/webrequest_permissions/initiator/manifest.json
@@ -0,0 +1,15 @@ +{ + "name": "webRequest permissions initiator", + "version": "1.0", + "manifest_version": 2, + "description": "Tests circumstances of exposing initiator in webRequest API.", + "permissions": [ + "webRequest", + "*://*.example.com/*", + "*://*.example2.com/*", + "*://*.example3.com/*", + "*://*.example4.com/*"], + "background": { + "scripts": ["background.js"] + } +}
diff --git a/components/feature_engagement/README.md b/components/feature_engagement/README.md index f632bc1..51a4a2b 100644 --- a/components/feature_engagement/README.md +++ b/components/feature_engagement/README.md
@@ -288,6 +288,7 @@ { "availability": "{Comparator}", "session_rate": "{Comparator}", + "session_rate_impact": "{SessionRateImpact}", "event_used": "{EventConfig}", "event_trigger": "{EventConfig}", "event_???": "{EventConfig}", @@ -308,6 +309,10 @@ end user session. * The value of the `Comparator` is a count of total In-Product Help displayed in the current end user session. +* `session_rate_impact` + * Which other in-product help features showing the current IPH impacts. + * By default, a feature impacts every other feature. + * See `SessionRateImpact` below for details. * `event_used` * Relates to what the in-product help wants to highlight, i.e. teach the user about and increase usage of. @@ -410,6 +415,33 @@ <15 ``` +### SessionRateImpact + +Format: ```[all|none|comma-separated list]``` + +* `all` means this feature impacts every other feature regarding their + `session_rate` calculations. This is the default. +* `none` means that this feature does not impact any other features regarding + the `session_rate`. This feature may therefore be shown an unlimited amount + of times, without making other features go over their `session_rate` config. +* `[comma-separated list]` means that this feature only impacts the particular + features listed. Use the `base::Feature` name of the feature in the list. + For features in the list, this feature will affect their `session_rate` + conditions, and for features not in the list, this feature will not affect + their `session_rate` calculations. + * It is *NOT* valid to use the feature names `all` or `none`. They must + only be used alone with no comma, at which point they work as described + above. + +**Examples** + +``` +all +none +IPH_DownloadHome +IPH_DonwloadPage,IPH_DownloadHome +``` + ### Using Chrome Variations at runtime It is possible to test the whole backend from parsing the configuration,
diff --git a/components/feature_engagement/internal/chrome_variations_configuration.cc b/components/feature_engagement/internal/chrome_variations_configuration.cc index c913e48..6a04161 100644 --- a/components/feature_engagement/internal/chrome_variations_configuration.cc +++ b/components/feature_engagement/internal/chrome_variations_configuration.cc
@@ -8,6 +8,7 @@ #include <memory> #include <string> #include <tuple> +#include <utility> #include <vector> #include "base/logging.h" @@ -31,10 +32,14 @@ const char kComparatorTypeEqual[] = "=="; const char kComparatorTypeNotEqual[] = "!="; +const char kSessionRateImpactTypeAll[] = "all"; +const char kSessionRateImpactTypeNone[] = "none"; + const char kEventConfigUsedKey[] = "event_used"; const char kEventConfigTriggerKey[] = "event_trigger"; const char kEventConfigKeyPrefix[] = "event_"; const char kSessionRateKey[] = "session_rate"; +const char kSessionRateImpactKey[] = "session_rate_impact"; const char kAvailabilityKey[] = "availability"; const char kIgnoredKeyPrefix[] = "x_"; @@ -49,7 +54,7 @@ namespace { -bool ParseComparatorSubstring(base::StringPiece definition, +bool ParseComparatorSubstring(const base::StringPiece& definition, Comparator* comparator, ComparatorType type, uint32_t type_len) { @@ -64,7 +69,8 @@ return true; } -bool ParseComparator(base::StringPiece definition, Comparator* comparator) { +bool ParseComparator(const base::StringPiece& definition, + Comparator* comparator) { if (base::LowerCaseEqualsASCII(definition, kComparatorTypeAny)) { comparator->type = ANY; comparator->value = 0; @@ -106,7 +112,8 @@ return false; } -bool ParseEventConfig(base::StringPiece definition, EventConfig* event_config) { +bool ParseEventConfig(const base::StringPiece& definition, + EventConfig* event_config) { // Support definitions with at least 4 tokens. auto tokens = base::SplitStringPiece(definition, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); @@ -188,6 +195,72 @@ return has_name && has_comparator && has_window && has_storage; } +bool IsKnownFeature(const base::StringPiece& feature_name, + const FeatureVector& features) { + for (const auto* feature : features) { + if (feature->name == feature_name.as_string()) + return true; + } + return false; +} + +bool ParseSessionRateImpact(const base::StringPiece& definition, + SessionRateImpact* session_rate_impact, + const base::Feature* this_feature, + const FeatureVector& all_features) { + base::StringPiece trimmed_def = + base::TrimWhitespaceASCII(definition, base::TRIM_ALL); + + if (trimmed_def.length() == 0) + return false; + + if (base::LowerCaseEqualsASCII(trimmed_def, kSessionRateImpactTypeAll)) { + session_rate_impact->type = SessionRateImpact::Type::ALL; + return true; + } + + if (base::LowerCaseEqualsASCII(trimmed_def, kSessionRateImpactTypeNone)) { + session_rate_impact->type = SessionRateImpact::Type::NONE; + return true; + } + + auto parsed_feature_names = base::SplitStringPiece( + trimmed_def, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + if (parsed_feature_names.empty()) + return false; + + std::vector<std::string> affected_features; + for (const auto& feature_name : parsed_feature_names) { + if (feature_name.length() == 0) { + DVLOG(1) << "Empty feature name when parsing session_rate_impact " + << "for feature " << this_feature->name; + continue; + } + if (base::LowerCaseEqualsASCII(feature_name, kSessionRateImpactTypeAll) || + base::LowerCaseEqualsASCII(feature_name, kSessionRateImpactTypeNone)) { + DVLOG(1) << "Illegal feature name when parsing session_rate_impact " + << "for feature " << this_feature->name << ": " << feature_name; + return false; + } + if (!IsKnownFeature(feature_name, all_features)) { + DVLOG(1) << "Unknown feature name found when parsing session_rate_impact " + << "for feature " << this_feature->name << ": " << feature_name; + stats::RecordConfigParsingEvent( + stats::ConfigParsingEvent:: + FAILURE_SESSION_RATE_IMPACT_UNKNOWN_FEATURE); + continue; + } + affected_features.push_back(feature_name.as_string()); + } + + if (affected_features.empty()) + return false; + + session_rate_impact->type = SessionRateImpact::Type::EXPLICIT; + session_rate_impact->affected_features = std::move(affected_features); + return true; +} + } // namespace ChromeVariationsConfiguration::ChromeVariationsConfiguration() = default; @@ -195,14 +268,15 @@ ChromeVariationsConfiguration::~ChromeVariationsConfiguration() = default; void ChromeVariationsConfiguration::ParseFeatureConfigs( - FeatureVector features) { + const FeatureVector& features) { for (auto* feature : features) { - ParseFeatureConfig(feature); + ParseFeatureConfig(feature, features); } } void ChromeVariationsConfiguration::ParseFeatureConfig( - const base::Feature* feature) { + const base::Feature* feature, + const FeatureVector& all_features) { DCHECK(feature); DCHECK(configs_.find(feature->name) == configs_.end()); @@ -253,6 +327,16 @@ continue; } config.session_rate = comparator; + } else if (key == kSessionRateImpactKey) { + SessionRateImpact impact; + if (!ParseSessionRateImpact(params[key], &impact, feature, + all_features)) { + stats::RecordConfigParsingEvent( + stats::ConfigParsingEvent::FAILURE_SESSION_RATE_IMPACT_PARSE); + ++parse_errors; + continue; + } + config.session_rate_impact = impact; } else if (key == kAvailabilityKey) { Comparator comparator; if (!ParseComparator(params[key], &comparator)) { @@ -327,8 +411,16 @@ } const Configuration::ConfigMap& -ChromeVariationsConfiguration::GetRegisteredFeatures() const { +ChromeVariationsConfiguration::GetRegisteredFeatureConfigs() const { return configs_; } +const std::vector<std::string> +ChromeVariationsConfiguration::GetRegisteredFeatures() const { + std::vector<std::string> features; + for (const auto& element : configs_) + features.push_back(element.first); + return features; +} + } // namespace feature_engagement
diff --git a/components/feature_engagement/internal/chrome_variations_configuration.h b/components/feature_engagement/internal/chrome_variations_configuration.h index cfdf79b..3196f5e 100644 --- a/components/feature_engagement/internal/chrome_variations_configuration.h +++ b/components/feature_engagement/internal/chrome_variations_configuration.h
@@ -28,14 +28,16 @@ const base::Feature& feature) const override; const FeatureConfig& GetFeatureConfigByName( const std::string& feature_name) const override; - const Configuration::ConfigMap& GetRegisteredFeatures() const override; + const Configuration::ConfigMap& GetRegisteredFeatureConfigs() const override; + const std::vector<std::string> GetRegisteredFeatures() const override; // Parses the variations configuration for all of the given |features| and // stores the result. It is only valid to call ParseFeatureConfig once. - void ParseFeatureConfigs(FeatureVector features); + void ParseFeatureConfigs(const FeatureVector& features); private: - void ParseFeatureConfig(const base::Feature* feature); + void ParseFeatureConfig(const base::Feature* feature, + const FeatureVector& all_features); // The current configurations. ConfigMap configs_;
diff --git a/components/feature_engagement/internal/chrome_variations_configuration_unittest.cc b/components/feature_engagement/internal/chrome_variations_configuration_unittest.cc index 7298374..1308b2b 100644 --- a/components/feature_engagement/internal/chrome_variations_configuration_unittest.cc +++ b/components/feature_engagement/internal/chrome_variations_configuration_unittest.cc
@@ -35,6 +35,14 @@ const char kGroupName[] = "Group1"; const char kConfigParseEventName[] = "InProductHelp.Config.ParsingEvent"; +SessionRateImpact CreateSessionRateImpactExplicit( + std::vector<std::string> affected_features) { + SessionRateImpact impact; + impact.type = SessionRateImpact::Type::EXPLICIT; + impact.affected_features = affected_features; + return impact; +} + class ChromeVariationsConfigurationTest : public ::testing::Test { public: ChromeVariationsConfigurationTest() : field_trials_(nullptr) { @@ -70,7 +78,6 @@ base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting(); } - protected: void SetFeatureParams(const base::Feature& feature, std::map<std::string, std::string> params) { ASSERT_TRUE( @@ -83,6 +90,7 @@ EXPECT_EQ(params, actualParams); } + protected: void VerifyInvalid(const std::string& event_config) { std::map<std::string, std::string> foo_params; foo_params["event_used"] = "name:u;comparator:any;window:0;storage:1"; @@ -235,6 +243,268 @@ histogram_tester.ExpectTotalCount(kConfigParseEventName, 1); } +void RunSessionRateImpactTest(ChromeVariationsConfigurationTest* test, + ChromeVariationsConfiguration* configuration, + std::vector<const base::Feature*> features, + std::string session_rate_impact_param_value, + SessionRateImpact expected_impact, + bool is_valid) { + std::map<std::string, std::string> foo_params; + foo_params["event_used"] = "name:eu;comparator:any;window:0;storage:360"; + foo_params["event_trigger"] = "name:et;comparator:any;window:0;storage:360"; + foo_params["session_rate_impact"] = session_rate_impact_param_value; + test->SetFeatureParams(kTestFeatureFoo, foo_params); + + configuration->ParseFeatureConfigs(features); + FeatureConfig foo = configuration->GetFeatureConfig(kTestFeatureFoo); + EXPECT_EQ(is_valid, foo.valid); + + FeatureConfig expected_foo; + expected_foo.valid = is_valid; + expected_foo.used = EventConfig("eu", Comparator(ANY, 0), 0, 360); + expected_foo.trigger = EventConfig("et", Comparator(ANY, 0), 0, 360); + expected_foo.session_rate_impact = + is_valid ? expected_impact : SessionRateImpact(); + EXPECT_EQ(expected_foo, foo); +} + +TEST_F(ChromeVariationsConfigurationTest, SessionRateImpactAll) { + base::HistogramTester histogram_tester; + RunSessionRateImpactTest(this, &configuration_, {&kTestFeatureFoo}, "all", + SessionRateImpact(), true /* is_valid */); + + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::SUCCESS), 1); + histogram_tester.ExpectTotalCount(kConfigParseEventName, 1); +} + +TEST_F(ChromeVariationsConfigurationTest, SessionRateImpactNone) { + base::HistogramTester histogram_tester; + SessionRateImpact impact; + impact.type = SessionRateImpact::Type::NONE; + RunSessionRateImpactTest(this, &configuration_, {&kTestFeatureFoo}, "none", + impact, true /* is_valid */); + + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::SUCCESS), 1); + histogram_tester.ExpectTotalCount(kConfigParseEventName, 1); +} + +TEST_F(ChromeVariationsConfigurationTest, SessionRateImpactExplicitSelf) { + base::HistogramTester histogram_tester; + RunSessionRateImpactTest( + this, &configuration_, {&kTestFeatureFoo}, "test_foo", + CreateSessionRateImpactExplicit({kTestFeatureFoo.name}), + true /* is_valid */); + + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::SUCCESS), 1); + histogram_tester.ExpectTotalCount(kConfigParseEventName, 1); +} + +TEST_F(ChromeVariationsConfigurationTest, SessionRateImpactExplicitOther) { + base::HistogramTester histogram_tester; + RunSessionRateImpactTest( + this, &configuration_, {&kTestFeatureFoo, &kTestFeatureBar}, "test_bar", + CreateSessionRateImpactExplicit({kTestFeatureBar.name}), + true /* is_valid */); + + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::SUCCESS), 1); + // bar has no configuration. + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::FAILURE_NO_FIELD_TRIAL), 1); + histogram_tester.ExpectTotalCount(kConfigParseEventName, 2); +} + +TEST_F(ChromeVariationsConfigurationTest, SessionRateImpactExplicitMultiple) { + base::HistogramTester histogram_tester; + RunSessionRateImpactTest( + this, &configuration_, + {&kTestFeatureFoo, &kTestFeatureBar, &kTestFeatureQux}, + "test_bar,test_qux", + CreateSessionRateImpactExplicit( + {kTestFeatureBar.name, kTestFeatureQux.name}), + true /* is_valid */); + + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::SUCCESS), 1); + // bar and qux have no configuration. + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::FAILURE_NO_FIELD_TRIAL), 2); + histogram_tester.ExpectTotalCount(kConfigParseEventName, 3); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitAtLeastOneValidFeature) { + base::HistogramTester histogram_tester; + RunSessionRateImpactTest( + this, &configuration_, + {&kTestFeatureFoo, &kTestFeatureBar, &kTestFeatureQux}, + "test_foo,no_feature", + CreateSessionRateImpactExplicit( + {kTestFeatureBar.name, kTestFeatureQux.name}), + true /* is_valid */); + + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::SUCCESS), 1); + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent:: + FAILURE_SESSION_RATE_IMPACT_UNKNOWN_FEATURE), + 1); + // bar and qux have no configuration. + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::FAILURE_NO_FIELD_TRIAL), 2); + histogram_tester.ExpectTotalCount(kConfigParseEventName, 4); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitAtLeastOneValidFeaturePlusEmpty) { + base::HistogramTester histogram_tester; + RunSessionRateImpactTest( + this, &configuration_, + {&kTestFeatureFoo, &kTestFeatureBar, &kTestFeatureQux}, "test_foo, ", + CreateSessionRateImpactExplicit( + {kTestFeatureBar.name, kTestFeatureQux.name}), + true /* is_valid */); + + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::SUCCESS), 1); + // bar and qux have no configuration. + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::FAILURE_NO_FIELD_TRIAL), 2); + histogram_tester.ExpectTotalCount(kConfigParseEventName, 3); +} + +void TestInvalidSessionRateImpactParamValue( + ChromeVariationsConfigurationTest* test, + ChromeVariationsConfiguration* configuration, + std::string session_rate_impact_param_value, + uint32_t parse_errors, + uint32_t unknown_features) { + base::HistogramTester histogram_tester; + RunSessionRateImpactTest( + test, configuration, + {&kTestFeatureFoo, &kTestFeatureBar, &kTestFeatureQux}, + session_rate_impact_param_value, SessionRateImpact(), + false /* is_valid */); + + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::FAILURE), 1); + if (parse_errors > 0) { + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>( + stats::ConfigParsingEvent::FAILURE_SESSION_RATE_IMPACT_PARSE), + parse_errors); + } + if (unknown_features > 0) { + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent:: + FAILURE_SESSION_RATE_IMPACT_UNKNOWN_FEATURE), + unknown_features); + } + // bar and qux has no configuration. + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::FAILURE_NO_FIELD_TRIAL), 2); + histogram_tester.ExpectTotalCount(kConfigParseEventName, + 3 + parse_errors + unknown_features); +} + +TEST_F(ChromeVariationsConfigurationTest, SessionRateImpactExplicitEmpty) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, "", 1, 0); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitEmptyWhitespace) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, " ", 1, 0); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitOnlySeparator) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, ",", 1, 0); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitOnlySeparatorWhitespace) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, " , ", 1, 0); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitOnlyUnknownFeature) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, "not_feature", + 1, 1); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitOnlyMultipleUnknownFeatures) { + TestInvalidSessionRateImpactParamValue( + this, &configuration_, "not_feature,another_not_feature", 1, 2); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitOnlyUnknownFeaturePlusEmpty) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, "not_feature, ", + 1, 1); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitInvalidFeatureNameAllFirst) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, "all,test_foo", + 1, 0); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitInvalidFeatureNameAllLast) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, "test_foo,all", + 1, 0); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitInvalidFeatureNameAllLastInvalidFirst) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, + "not_feature,all", 1, 1); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitInvalidFeatureNameNoneFirst) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, "none,test_foo", + 1, 0); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitInvalidFeatureNameNoneLast) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, "test_foo,none", + 1, 0); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitInvalidFeatureNameNoneLastInvalidFirst) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, + "not_feature,none", 1, 1); +} + +TEST_F(ChromeVariationsConfigurationTest, + SessionRateImpactExplicitInvalidFeatureNameFromCodeReview) { + TestInvalidSessionRateImpactParamValue(this, &configuration_, + "test_foo,all,none,mwahahaha", 1, 0); +} + TEST_F(ChromeVariationsConfigurationTest, WhitespaceIsValid) { std::map<std::string, std::string> foo_params; foo_params["event_used"] = @@ -249,9 +519,12 @@ foo_params["event_5"] = "name:e5;comparator: <=\r6 ;window:8;storage:390"; foo_params["event_6"] = "name:e6;comparator:\n<=7;window:9;storage:400"; foo_params["event_7"] = "name:e7;comparator:<=8\n;window:10;storage:410"; + foo_params["session_rate_impact"] = " test_bar, test_qux "; SetFeatureParams(kTestFeatureFoo, foo_params); - std::vector<const base::Feature*> features = {&kTestFeatureFoo}; + base::HistogramTester histogram_tester; + std::vector<const base::Feature*> features = { + &kTestFeatureFoo, &kTestFeatureBar, &kTestFeatureQux}; configuration_.ParseFeatureConfigs(features); FeatureConfig foo = configuration_.GetFeatureConfig(kTestFeatureFoo); @@ -277,7 +550,17 @@ EventConfig("e6", Comparator(LESS_THAN_OR_EQUAL, 7), 9, 400)); expected_foo.event_configs.insert( EventConfig("e7", Comparator(LESS_THAN_OR_EQUAL, 8), 10, 410)); + expected_foo.session_rate_impact = CreateSessionRateImpactExplicit( + {kTestFeatureBar.name, kTestFeatureQux.name}); EXPECT_EQ(expected_foo, foo); + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::SUCCESS), 1); + // bar and qux have no configuration. + histogram_tester.ExpectBucketCount( + kConfigParseEventName, + static_cast<int>(stats::ConfigParsingEvent::FAILURE_NO_FIELD_TRIAL), 2); + histogram_tester.ExpectTotalCount(kConfigParseEventName, 3); } TEST_F(ChromeVariationsConfigurationTest, IgnoresInvalidConfigKeys) {
diff --git a/components/feature_engagement/internal/condition_validator.h b/components/feature_engagement/internal/condition_validator.h index 809f4579..a17e569c 100644 --- a/components/feature_engagement/internal/condition_validator.h +++ b/components/feature_engagement/internal/condition_validator.h
@@ -9,6 +9,7 @@ #include <ostream> #include <string> +#include <vector> #include "base/macros.h" #include "components/feature_engagement/public/feature_list.h" @@ -79,7 +80,10 @@ uint32_t current_day) const = 0; // Must be called to notify that the |feature| is currently showing. - virtual void NotifyIsShowing(const base::Feature& feature) = 0; + virtual void NotifyIsShowing( + const base::Feature& feature, + const FeatureConfig& config, + const std::vector<std::string>& all_feature_names) = 0; // Must be called to notify that the |feature| is no longer showing. virtual void NotifyDismissed(const base::Feature& feature) = 0;
diff --git a/components/feature_engagement/internal/configuration.cc b/components/feature_engagement/internal/configuration.cc index 048eb63d..a14c11a 100644 --- a/components/feature_engagement/internal/configuration.cc +++ b/components/feature_engagement/internal/configuration.cc
@@ -7,8 +7,25 @@ #include <string> #include "base/logging.h" +#include "base/optional.h" namespace feature_engagement { +namespace { +std::ostream& operator<<(std::ostream& os, const SessionRateImpact::Type type) { + switch (type) { + case SessionRateImpact::Type::ALL: + return os << "ALL"; + case SessionRateImpact::Type::NONE: + return os << "NONE"; + case SessionRateImpact::Type::EXPLICIT: + return os << "EXPLICIT"; + default: + // All cases should be covered. + NOTREACHED(); + return os; + } +} +} // namespace Comparator::Comparator() : type(ANY), value(0) {} @@ -80,6 +97,36 @@ << ", storage: " << event_config.storage << " }"; } +SessionRateImpact::SessionRateImpact() + : type(SessionRateImpact::Type::ALL), affected_features() {} + +SessionRateImpact::SessionRateImpact(const SessionRateImpact& other) = default; + +SessionRateImpact::~SessionRateImpact() = default; + +std::ostream& operator<<(std::ostream& os, const SessionRateImpact& impact) { + os << "{ type: " << impact.type << ", affected_features: "; + if (!impact.affected_features.has_value()) + return os << "NO VALUE }"; + + os << "["; + bool first = true; + for (const auto& affected_feature : impact.affected_features.value()) { + if (first) { + first = false; + os << affected_feature; + } else { + os << ", " << affected_feature; + } + } + return os << "] }"; +} + +bool operator==(const SessionRateImpact& lhs, const SessionRateImpact& rhs) { + return std::tie(lhs.type, lhs.affected_features) == + std::tie(rhs.type, rhs.affected_features); +} + FeatureConfig::FeatureConfig() : valid(false) {} FeatureConfig::FeatureConfig(const FeatureConfig& other) = default;
diff --git a/components/feature_engagement/internal/configuration.h b/components/feature_engagement/internal/configuration.h index 891a62e..7b352f1 100644 --- a/components/feature_engagement/internal/configuration.h +++ b/components/feature_engagement/internal/configuration.h
@@ -12,6 +12,7 @@ #include <vector> #include "base/macros.h" +#include "base/optional.h" namespace base { struct Feature; @@ -80,6 +81,33 @@ bool operator<(const EventConfig& lhs, const EventConfig& rhs); std::ostream& operator<<(std::ostream& os, const EventConfig& event_config); +// A SessionRateImpact describes which features the |session_rate| of a given +// FeatureConfig should affect. It can affect either |ALL| (default), |NONE|, +// or an |EXPLICIT| list of the features. In the latter case, a list of affected +// features is given as their base::Feature name. +struct SessionRateImpact { + public: + enum class Type { + ALL = 0, // Affects all other features. + NONE = 1, // Affects no other features. + EXPLICIT = 2 // Affects only features in |affected_features|. + }; + + SessionRateImpact(); + SessionRateImpact(const SessionRateImpact& other); + ~SessionRateImpact(); + + // Describes which features are impacted. + Type type; + + // In the case of the Type |EXPLICIT|, this is the list of affected + // base::Feature names. + base::Optional<std::vector<std::string>> affected_features; +}; + +bool operator==(const SessionRateImpact& lhs, const SessionRateImpact& rhs); +std::ostream& operator<<(std::ostream& os, const SessionRateImpact& impact); + // A FeatureConfig contains all the configuration for a given feature. struct FeatureConfig { public: @@ -106,6 +134,9 @@ // comparison. Comparator session_rate; + // Which features the showing this in-product help impacts. + SessionRateImpact session_rate_impact; + // Number of days the in-product help has been available must fit this // comparison. Comparator availability; @@ -135,7 +166,10 @@ const std::string& feature_name) const = 0; // Returns the immutable ConfigMap that contains all registered features. - virtual const ConfigMap& GetRegisteredFeatures() const = 0; + virtual const ConfigMap& GetRegisteredFeatureConfigs() const = 0; + + // Returns the list of the names of all registred features. + virtual const std::vector<std::string> GetRegisteredFeatures() const = 0; protected: Configuration() = default;
diff --git a/components/feature_engagement/internal/editable_configuration.cc b/components/feature_engagement/internal/editable_configuration.cc index cdce5386..24781436 100644 --- a/components/feature_engagement/internal/editable_configuration.cc +++ b/components/feature_engagement/internal/editable_configuration.cc
@@ -36,9 +36,17 @@ return it->second; } -const Configuration::ConfigMap& EditableConfiguration::GetRegisteredFeatures() - const { +const Configuration::ConfigMap& +EditableConfiguration::GetRegisteredFeatureConfigs() const { return configs_; } +const std::vector<std::string> EditableConfiguration::GetRegisteredFeatures() + const { + std::vector<std::string> features; + for (const auto& element : configs_) + features.push_back(element.first); + return features; +} + } // namespace feature_engagement
diff --git a/components/feature_engagement/internal/editable_configuration.h b/components/feature_engagement/internal/editable_configuration.h index b92b3da5..27164df 100644 --- a/components/feature_engagement/internal/editable_configuration.h +++ b/components/feature_engagement/internal/editable_configuration.h
@@ -27,7 +27,8 @@ const base::Feature& feature) const override; const FeatureConfig& GetFeatureConfigByName( const std::string& feature_name) const override; - const Configuration::ConfigMap& GetRegisteredFeatures() const override; + const Configuration::ConfigMap& GetRegisteredFeatureConfigs() const override; + const std::vector<std::string> GetRegisteredFeatures() const override; // Adds a new FeatureConfig to the current configurations. If it already // exists, the contents are replaced.
diff --git a/components/feature_engagement/internal/feature_config_condition_validator.cc b/components/feature_engagement/internal/feature_config_condition_validator.cc index 2ec5b83..9a18a9f2d 100644 --- a/components/feature_engagement/internal/feature_config_condition_validator.cc +++ b/components/feature_engagement/internal/feature_config_condition_validator.cc
@@ -4,6 +4,10 @@ #include "components/feature_engagement/internal/feature_config_condition_validator.h" +#include <algorithm> +#include <string> +#include <vector> + #include "base/feature_list.h" #include "components/feature_engagement/internal/availability_model.h" #include "components/feature_engagement/internal/configuration.h" @@ -14,7 +18,7 @@ namespace feature_engagement { FeatureConfigConditionValidator::FeatureConfigConditionValidator() - : currently_showing_(false), times_shown_(0u) {} + : currently_showing_(false) {} FeatureConfigConditionValidator::~FeatureConfigConditionValidator() = default; @@ -39,7 +43,8 @@ EventConfigMeetsConditions(event_config, event_model, current_day); } - result.session_rate_ok = config.session_rate.MeetsCriteria(times_shown_); + result.session_rate_ok = + SessionRateMeetsConditions(config.session_rate, feature); result.availability_model_ready_ok = availability_model.IsReady(); @@ -50,12 +55,35 @@ } void FeatureConfigConditionValidator::NotifyIsShowing( - const base::Feature& feature) { + const base::Feature& feature, + const FeatureConfig& config, + const std::vector<std::string>& all_feature_names) { DCHECK(!currently_showing_); DCHECK(base::FeatureList::IsEnabled(feature)); currently_showing_ = true; - ++times_shown_; + + switch (config.session_rate_impact.type) { + case SessionRateImpact::Type::ALL: + for (const std::string& feature_name : all_feature_names) + ++times_shown_for_feature_[feature_name]; + break; + case SessionRateImpact::Type::NONE: + // Intentionally ignore, since no features should be impacted. + break; + case SessionRateImpact::Type::EXPLICIT: + DCHECK(config.session_rate_impact.affected_features.has_value()); + for (const std::string& feature_name : + config.session_rate_impact.affected_features.value()) { + DCHECK(std::find(all_feature_names.begin(), all_feature_names.end(), + feature_name) != all_feature_names.end()); + ++times_shown_for_feature_[feature_name]; + } + break; + default: + // All cases should be covered. + NOTREACHED(); + } } void FeatureConfigConditionValidator::NotifyDismissed( @@ -119,4 +147,15 @@ return comparator.MeetsCriteria(days_available); } +bool FeatureConfigConditionValidator::SessionRateMeetsConditions( + const Comparator session_rate, + const base::Feature& feature) const { + const auto it = times_shown_for_feature_.find(feature.name); + if (it == times_shown_for_feature_.end()) { + return session_rate.MeetsCriteria(0u); + } else { + return session_rate.MeetsCriteria(it->second); + } +} + } // namespace feature_engagement
diff --git a/components/feature_engagement/internal/feature_config_condition_validator.h b/components/feature_engagement/internal/feature_config_condition_validator.h index 949eaa7..4e74b385 100644 --- a/components/feature_engagement/internal/feature_config_condition_validator.h +++ b/components/feature_engagement/internal/feature_config_condition_validator.h
@@ -6,6 +6,7 @@ #define COMPONENTS_FEATURE_ENGAGEMENT_INTERNAL_FEATURE_CONFIG_CONDITION_VALIDATOR_H_ #include <stdint.h> +#include <map> #include "base/macros.h" #include "components/feature_engagement/internal/condition_validator.h" @@ -29,7 +30,10 @@ const EventModel& event_model, const AvailabilityModel& availability_model, uint32_t current_day) const override; - void NotifyIsShowing(const base::Feature& feature) override; + void NotifyIsShowing( + const base::Feature& feature, + const FeatureConfig& config, + const std::vector<std::string>& all_feature_names) override; void NotifyDismissed(const base::Feature& feature) override; private: @@ -42,11 +46,16 @@ const AvailabilityModel& availability_model, uint32_t current_day) const; + bool SessionRateMeetsConditions(const Comparator session_rate, + const base::Feature& feature) const; + // Whether in-product help is currently being shown. bool currently_showing_; - // Number of times in-product help has been shown within the current session. - uint32_t times_shown_; + // Stores how many times features that impact a given feature have been shown. + // By default, all features impact each other, but some features override this + // through the use of |session_rate_impact|. + std::map<std::string, uint32_t> times_shown_for_feature_; DISALLOW_COPY_AND_ASSIGN(FeatureConfigConditionValidator); };
diff --git a/components/feature_engagement/internal/feature_config_condition_validator_unittest.cc b/components/feature_engagement/internal/feature_config_condition_validator_unittest.cc index 5f6420f8..ceba7ce 100644 --- a/components/feature_engagement/internal/feature_config_condition_validator_unittest.cc +++ b/components/feature_engagement/internal/feature_config_condition_validator_unittest.cc
@@ -25,6 +25,10 @@ base::FEATURE_DISABLED_BY_DEFAULT}; const base::Feature kTestFeatureBar{"test_bar", base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kTestFeatureQux{"test_qux", + base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kTestFeatureXyz{"test_xyz", + base::FEATURE_DISABLED_BY_DEFAULT}; FeatureConfig GetValidFeatureConfig() { FeatureConfig config; @@ -42,6 +46,14 @@ return config; } +SessionRateImpact CreateSessionRateImpactExplicit( + std::vector<std::string> affected_features) { + SessionRateImpact impact; + impact.type = SessionRateImpact::Type::EXPLICIT; + impact.affected_features = affected_features; + return impact; +} + class TestEventModel : public EventModel { public: TestEventModel() : ready_(true) {} @@ -130,6 +142,13 @@ availability_model_, 0); } + ConditionValidator::Result GetResultForDayZeroForFeature( + const base::Feature& feature, + const FeatureConfig& config) { + return validator_.MeetsConditions(feature, config, event_model_, + availability_model_, 0); + } + TestEventModel event_model_; TestAvailabilityModel availability_model_; FeatureConfigConditionValidator validator_; @@ -192,7 +211,8 @@ base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitWithFeatures({kTestFeatureFoo, kTestFeatureBar}, {}); - validator_.NotifyIsShowing(kTestFeatureBar); + validator_.NotifyIsShowing(kTestFeatureBar, FeatureConfig(), + {kTestFeatureFoo.name, kTestFeatureBar.name}); ConditionValidator::Result result = GetResultForDayZero(GetAcceptingFeatureConfig()); EXPECT_FALSE(result.NoErrors()); @@ -290,29 +310,195 @@ TEST_F(FeatureConfigConditionValidatorTest, SessionRate) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitWithFeatures({kTestFeatureFoo, kTestFeatureBar}, {}); + std::vector<std::string> all_feature_names = {kTestFeatureFoo.name, + kTestFeatureBar.name}; - FeatureConfig config = GetAcceptingFeatureConfig(); - config.session_rate = Comparator(LESS_THAN, 2u); + FeatureConfig foo_config = GetAcceptingFeatureConfig(); + foo_config.session_rate = Comparator(LESS_THAN, 2u); + FeatureConfig bar_config = GetAcceptingFeatureConfig(); - EXPECT_TRUE(GetResultForDayZero(config).NoErrors()); + EXPECT_TRUE(GetResultForDayZero(foo_config).NoErrors()); - validator_.NotifyIsShowing(kTestFeatureBar); + validator_.NotifyIsShowing(kTestFeatureBar, bar_config, all_feature_names); validator_.NotifyDismissed(kTestFeatureBar); - EXPECT_TRUE(GetResultForDayZero(config).NoErrors()); + EXPECT_TRUE(GetResultForDayZero(foo_config).NoErrors()); - validator_.NotifyIsShowing(kTestFeatureBar); + validator_.NotifyIsShowing(kTestFeatureBar, bar_config, all_feature_names); validator_.NotifyDismissed(kTestFeatureBar); - ConditionValidator::Result result = GetResultForDayZero(config); + ConditionValidator::Result result = GetResultForDayZero(foo_config); EXPECT_FALSE(result.NoErrors()); EXPECT_FALSE(result.session_rate_ok); - validator_.NotifyIsShowing(kTestFeatureBar); + validator_.NotifyIsShowing(kTestFeatureBar, bar_config, all_feature_names); validator_.NotifyDismissed(kTestFeatureBar); - result = GetResultForDayZero(config); + result = GetResultForDayZero(foo_config); EXPECT_FALSE(result.NoErrors()); EXPECT_FALSE(result.session_rate_ok); } +TEST_F(FeatureConfigConditionValidatorTest, SessionRateImpactAffectsNone) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeatures({kTestFeatureFoo, kTestFeatureBar}, {}); + std::vector<std::string> all_feature_names = {kTestFeatureFoo.name, + kTestFeatureBar.name}; + + FeatureConfig foo_config = GetAcceptingFeatureConfig(); + foo_config.session_rate = Comparator(LESS_THAN, 2u); + FeatureConfig affects_none_config = GetAcceptingFeatureConfig(); + affects_none_config.session_rate_impact = SessionRateImpact(); + affects_none_config.session_rate_impact.type = SessionRateImpact::Type::NONE; + + EXPECT_TRUE(GetResultForDayZero(foo_config).NoErrors()); + + validator_.NotifyIsShowing(kTestFeatureBar, affects_none_config, + all_feature_names); + validator_.NotifyDismissed(kTestFeatureBar); + EXPECT_TRUE(GetResultForDayZero(foo_config).NoErrors()); + + validator_.NotifyIsShowing(kTestFeatureBar, affects_none_config, + all_feature_names); + validator_.NotifyDismissed(kTestFeatureBar); + EXPECT_TRUE(GetResultForDayZero(foo_config).NoErrors()); + + validator_.NotifyIsShowing(kTestFeatureBar, affects_none_config, + all_feature_names); + validator_.NotifyDismissed(kTestFeatureBar); + EXPECT_TRUE(GetResultForDayZero(foo_config).NoErrors()); +} + +TEST_F(FeatureConfigConditionValidatorTest, SessionRateImpactAffectsExplicit) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeatures( + {kTestFeatureFoo, kTestFeatureBar, kTestFeatureQux}, {}); + std::vector<std::string> all_feature_names = { + kTestFeatureFoo.name, kTestFeatureBar.name, kTestFeatureQux.name}; + + FeatureConfig foo_config = GetAcceptingFeatureConfig(); + foo_config.session_rate = Comparator(LESS_THAN, 2u); + FeatureConfig bar_config = GetAcceptingFeatureConfig(); + bar_config.session_rate = Comparator(LESS_THAN, 2u); + + FeatureConfig affects_only_foo_config = GetAcceptingFeatureConfig(); + affects_only_foo_config.session_rate_impact = + CreateSessionRateImpactExplicit({kTestFeatureFoo.name}); + + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureFoo, foo_config).NoErrors()); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureBar, bar_config).NoErrors()); + + validator_.NotifyIsShowing(kTestFeatureQux, affects_only_foo_config, + all_feature_names); + validator_.NotifyDismissed(kTestFeatureQux); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureFoo, foo_config).NoErrors()); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureBar, bar_config).NoErrors()); + + validator_.NotifyIsShowing(kTestFeatureQux, affects_only_foo_config, + all_feature_names); + validator_.NotifyDismissed(kTestFeatureQux); + ConditionValidator::Result result = + GetResultForDayZeroForFeature(kTestFeatureFoo, foo_config); + EXPECT_FALSE(result.NoErrors()); + EXPECT_FALSE(result.session_rate_ok); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureBar, bar_config).NoErrors()); +} + +TEST_F(FeatureConfigConditionValidatorTest, SessionRateImpactAffectsSelf) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeatures( + {kTestFeatureFoo, kTestFeatureBar, kTestFeatureQux}, {}); + std::vector<std::string> all_feature_names = {kTestFeatureFoo.name, + kTestFeatureBar.name}; + + FeatureConfig foo_config = GetAcceptingFeatureConfig(); + foo_config.session_rate = Comparator(LESS_THAN, 2u); + FeatureConfig bar_config = GetAcceptingFeatureConfig(); + bar_config.session_rate = Comparator(LESS_THAN, 2u); + + FeatureConfig affects_only_foo_config = GetAcceptingFeatureConfig(); + affects_only_foo_config.session_rate_impact = + CreateSessionRateImpactExplicit({kTestFeatureFoo.name}); + + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureFoo, foo_config).NoErrors()); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureBar, bar_config).NoErrors()); + + validator_.NotifyIsShowing(kTestFeatureFoo, affects_only_foo_config, + all_feature_names); + validator_.NotifyDismissed(kTestFeatureFoo); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureFoo, foo_config).NoErrors()); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureBar, bar_config).NoErrors()); + + validator_.NotifyIsShowing(kTestFeatureFoo, affects_only_foo_config, + all_feature_names); + validator_.NotifyDismissed(kTestFeatureFoo); + ConditionValidator::Result result = + GetResultForDayZeroForFeature(kTestFeatureFoo, foo_config); + EXPECT_FALSE(result.NoErrors()); + EXPECT_FALSE(result.session_rate_ok); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureBar, bar_config).NoErrors()); +} + +TEST_F(FeatureConfigConditionValidatorTest, + SessionRateImpactAffectsExplicitMultipleFeatures) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeatures( + {kTestFeatureFoo, kTestFeatureBar, kTestFeatureQux, kTestFeatureXyz}, {}); + std::vector<std::string> all_feature_names = { + kTestFeatureFoo.name, kTestFeatureBar.name, kTestFeatureQux.name, + kTestFeatureXyz.name}; + + FeatureConfig foo_config = GetAcceptingFeatureConfig(); + foo_config.session_rate = Comparator(LESS_THAN, 2u); + FeatureConfig bar_config = GetAcceptingFeatureConfig(); + bar_config.session_rate = Comparator(LESS_THAN, 2u); + FeatureConfig xyz_config = GetAcceptingFeatureConfig(); + xyz_config.session_rate = Comparator(LESS_THAN, 2u); + + FeatureConfig affects_foo_and_bar_config = GetAcceptingFeatureConfig(); + affects_foo_and_bar_config.session_rate_impact = + CreateSessionRateImpactExplicit( + {kTestFeatureFoo.name, kTestFeatureBar.name}); + + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureFoo, foo_config).NoErrors()); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureBar, bar_config).NoErrors()); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureXyz, xyz_config).NoErrors()); + + validator_.NotifyIsShowing(kTestFeatureQux, affects_foo_and_bar_config, + all_feature_names); + validator_.NotifyDismissed(kTestFeatureQux); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureFoo, foo_config).NoErrors()); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureBar, bar_config).NoErrors()); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureXyz, xyz_config).NoErrors()); + + validator_.NotifyIsShowing(kTestFeatureQux, affects_foo_and_bar_config, + all_feature_names); + validator_.NotifyDismissed(kTestFeatureQux); + ConditionValidator::Result foo_result = + GetResultForDayZeroForFeature(kTestFeatureFoo, foo_config); + EXPECT_FALSE(foo_result.NoErrors()); + EXPECT_FALSE(foo_result.session_rate_ok); + ConditionValidator::Result bar_result = + GetResultForDayZeroForFeature(kTestFeatureFoo, bar_config); + EXPECT_FALSE(bar_result.NoErrors()); + EXPECT_FALSE(bar_result.session_rate_ok); + EXPECT_TRUE( + GetResultForDayZeroForFeature(kTestFeatureXyz, xyz_config).NoErrors()); +} + TEST_F(FeatureConfigConditionValidatorTest, Availability) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitWithFeatures({kTestFeatureFoo, kTestFeatureBar}, {});
diff --git a/components/feature_engagement/internal/never_condition_validator.cc b/components/feature_engagement/internal/never_condition_validator.cc index 038eedf..7b937cb2 100644 --- a/components/feature_engagement/internal/never_condition_validator.cc +++ b/components/feature_engagement/internal/never_condition_validator.cc
@@ -19,7 +19,10 @@ return ConditionValidator::Result(false); } -void NeverConditionValidator::NotifyIsShowing(const base::Feature& feature) {} +void NeverConditionValidator::NotifyIsShowing( + const base::Feature& feature, + const FeatureConfig& config, + const std::vector<std::string>& all_feature_names) {} void NeverConditionValidator::NotifyDismissed(const base::Feature& feature) {}
diff --git a/components/feature_engagement/internal/never_condition_validator.h b/components/feature_engagement/internal/never_condition_validator.h index b915f838..4d7842c 100644 --- a/components/feature_engagement/internal/never_condition_validator.h +++ b/components/feature_engagement/internal/never_condition_validator.h
@@ -31,7 +31,10 @@ const EventModel& event_model, const AvailabilityModel& availability_model, uint32_t current_day) const override; - void NotifyIsShowing(const base::Feature& feature) override; + void NotifyIsShowing( + const base::Feature& feature, + const FeatureConfig& config, + const std::vector<std::string>& all_feature_names) override; void NotifyDismissed(const base::Feature& feature) override; private:
diff --git a/components/feature_engagement/internal/once_condition_validator.cc b/components/feature_engagement/internal/once_condition_validator.cc index f3068f3..29d46fbb2e 100644 --- a/components/feature_engagement/internal/once_condition_validator.cc +++ b/components/feature_engagement/internal/once_condition_validator.cc
@@ -34,7 +34,10 @@ return result; } -void OnceConditionValidator::NotifyIsShowing(const base::Feature& feature) { +void OnceConditionValidator::NotifyIsShowing( + const base::Feature& feature, + const FeatureConfig& config, + const std::vector<std::string>& all_feature_names) { DCHECK(currently_showing_feature_.empty()); DCHECK(shown_features_.find(feature.name) == shown_features_.end()); shown_features_.insert(feature.name);
diff --git a/components/feature_engagement/internal/once_condition_validator.h b/components/feature_engagement/internal/once_condition_validator.h index 0bafc34..c6721e3 100644 --- a/components/feature_engagement/internal/once_condition_validator.h +++ b/components/feature_engagement/internal/once_condition_validator.h
@@ -44,7 +44,10 @@ const EventModel& event_model, const AvailabilityModel& availability_model, uint32_t current_day) const override; - void NotifyIsShowing(const base::Feature& feature) override; + void NotifyIsShowing( + const base::Feature& feature, + const FeatureConfig& config, + const std::vector<std::string>& all_feature_names) override; void NotifyDismissed(const base::Feature& feature) override; private:
diff --git a/components/feature_engagement/internal/once_condition_validator_unittest.cc b/components/feature_engagement/internal/once_condition_validator_unittest.cc index cdfc1a6..b0316df 100644 --- a/components/feature_engagement/internal/once_condition_validator_unittest.cc +++ b/components/feature_engagement/internal/once_condition_validator_unittest.cc
@@ -72,7 +72,7 @@ .MeetsConditions(kTestFeatureFoo, kValidFeatureConfig, event_model_, availability_model_, 0u) .NoErrors()); - validator_.NotifyIsShowing(kTestFeatureFoo); + validator_.NotifyIsShowing(kTestFeatureFoo, FeatureConfig(), {""}); ConditionValidator::Result result = validator_.MeetsConditions(kTestFeatureFoo, kValidFeatureConfig, event_model_, availability_model_, 0u); @@ -125,7 +125,7 @@ } TEST_F(OnceConditionValidatorTest, OnlyTriggerIfNothingElseIsShowing) { - validator_.NotifyIsShowing(kTestFeatureBar); + validator_.NotifyIsShowing(kTestFeatureBar, FeatureConfig(), {""}); ConditionValidator::Result result = validator_.MeetsConditions(kTestFeatureFoo, kValidFeatureConfig, event_model_, availability_model_, 0u);
diff --git a/components/feature_engagement/internal/single_invalid_configuration.cc b/components/feature_engagement/internal/single_invalid_configuration.cc index 5d06652..94a7010b 100644 --- a/components/feature_engagement/internal/single_invalid_configuration.cc +++ b/components/feature_engagement/internal/single_invalid_configuration.cc
@@ -26,8 +26,13 @@ } const Configuration::ConfigMap& -SingleInvalidConfiguration::GetRegisteredFeatures() const { +SingleInvalidConfiguration::GetRegisteredFeatureConfigs() const { return configs_; } +const std::vector<std::string> +SingleInvalidConfiguration::GetRegisteredFeatures() const { + return {}; +} + } // namespace feature_engagement
diff --git a/components/feature_engagement/internal/single_invalid_configuration.h b/components/feature_engagement/internal/single_invalid_configuration.h index de6ce42c..82766e1a7 100644 --- a/components/feature_engagement/internal/single_invalid_configuration.h +++ b/components/feature_engagement/internal/single_invalid_configuration.h
@@ -29,7 +29,8 @@ const base::Feature& feature) const override; const FeatureConfig& GetFeatureConfigByName( const std::string& feature_name) const override; - const Configuration::ConfigMap& GetRegisteredFeatures() const override; + const Configuration::ConfigMap& GetRegisteredFeatureConfigs() const override; + const std::vector<std::string> GetRegisteredFeatures() const override; private: // The invalid configuration to always return.
diff --git a/components/feature_engagement/internal/stats.cc b/components/feature_engagement/internal/stats.cc index 5cc79b2..2888247 100644 --- a/components/feature_engagement/internal/stats.cc +++ b/components/feature_engagement/internal/stats.cc
@@ -56,7 +56,8 @@ DCHECK(config); // Find which feature this event belongs to. - const Configuration::ConfigMap& features = config->GetRegisteredFeatures(); + const Configuration::ConfigMap& features = + config->GetRegisteredFeatureConfigs(); std::string feature_name; for (const auto& element : features) { const std::string fname = element.first;
diff --git a/components/feature_engagement/internal/stats.h b/components/feature_engagement/internal/stats.h index 1a40220..0d8e983 100644 --- a/components/feature_engagement/internal/stats.h +++ b/components/feature_engagement/internal/stats.h
@@ -20,6 +20,7 @@ // Most of the fields maps to |ConditionValidator::Result|. // The failure reasons are not mutually exclusive. // Out-dated entries shouldn't be deleted but marked as obselete. +// Keep this synced with the enum in //tools/metrics/histograms/enums.xml. enum class TriggerHelpUIResult { // The help UI is triggered. SUCCESS = 0, @@ -64,6 +65,7 @@ // Used in the metrics to track the configuration parsing event. // The failure reasons are not mutually exclusive. // Out-dated entries shouldn't be deleted but marked as obsolete. +// Keep this synced with the enum in //tools/metrics/histograms/enums.xml. enum class ConfigParsingEvent { // The configuration is parsed correctly. SUCCESS = 0, @@ -98,8 +100,14 @@ // UnKnown key in configuration parameters. FAILURE_UNKNOWN_KEY = 10, + // Fails to parse the session rate impact. + FAILURE_SESSION_RATE_IMPACT_PARSE = 11, + + // Fails to parse the session rate impact. + FAILURE_SESSION_RATE_IMPACT_UNKNOWN_FEATURE = 12, + // Last entry for the enum. - COUNT = 11, + COUNT = 13, }; // Used in metrics to track database states. Each type will match to a suffix
diff --git a/components/feature_engagement/internal/tracker_impl.cc b/components/feature_engagement/internal/tracker_impl.cc index 8dc508a..4b33b02 100644 --- a/components/feature_engagement/internal/tracker_impl.cc +++ b/components/feature_engagement/internal/tracker_impl.cc
@@ -174,7 +174,9 @@ feature, configuration_->GetFeatureConfig(feature), *event_model_, *availability_model_, time_provider_->GetCurrentDay()); if (result.NoErrors()) { - condition_validator_->NotifyIsShowing(feature); + condition_validator_->NotifyIsShowing( + feature, configuration_->GetFeatureConfig(feature), + configuration_->GetRegisteredFeatures()); FeatureConfig feature_config = configuration_->GetFeatureConfig(feature); DCHECK_NE("", feature_config.trigger.name); event_model_->IncrementEvent(feature_config.trigger.name,
diff --git a/components/onc/docs/onc_spec.md b/components/onc/docs/onc_spec.md index 5abadf97..7fe473f 100644 --- a/components/onc/docs/onc_spec.md +++ b/components/onc/docs/onc_spec.md
@@ -510,10 +510,7 @@ * **Type** * (required) - **string** - * `Allowed values are` *IPsec*, - *L2TP-IPsec*, - *OpenVPN*, and - *ThirdPartyVPN*. + * `Allowed values are` *L2TP-IPsec*, *OpenVPN*, *ThirdPartyVPN*, *ARCVPN* * Type of the VPN. ## IPsec-based VPN types
diff --git a/components/user_manager/user.h b/components/user_manager/user.h index 645cf88..ee68858 100644 --- a/components/user_manager/user.h +++ b/components/user_manager/user.h
@@ -31,6 +31,10 @@ class UserSessionManager; } +namespace policy { +class ProfilePolicyConnectorTest; +} + namespace user_manager { class UserManagerBase; @@ -191,6 +195,7 @@ friend class chromeos::FakeChromeUserManager; friend class chromeos::MockUserManager; friend class chromeos::UserAddingScreenTest; + friend class policy::ProfilePolicyConnectorTest; FRIEND_TEST_ALL_PREFIXES(UserTest, DeviceLocalAccountAffiliation); FRIEND_TEST_ALL_PREFIXES(UserTest, UserSessionInitialized);
diff --git a/components/user_manager/user_manager.cc b/components/user_manager/user_manager.cc index a4f4282..27cea02 100644 --- a/components/user_manager/user_manager.cc +++ b/components/user_manager/user_manager.cc
@@ -23,6 +23,8 @@ const User& user, const gfx::ImageSkia& profile_image) {} +void UserManager::Observer::OnChildStatusChanged(const User& user) {} + void UserManager::UserSessionStateObserver::ActiveUserChanged( const User* active_user) { } @@ -35,9 +37,6 @@ const std::string& hash) { } -void UserManager::UserSessionStateObserver::UserChangedChildStatus(User* user) { -} - UserManager::UserSessionStateObserver::~UserSessionStateObserver() { }
diff --git a/components/user_manager/user_manager.h b/components/user_manager/user_manager.h index 0586d762e..e58a3f86 100644 --- a/components/user_manager/user_manager.h +++ b/components/user_manager/user_manager.h
@@ -51,6 +51,9 @@ virtual void OnUserProfileImageUpdated(const User& user, const gfx::ImageSkia& profile_image); + // Called when the child status of the given user has changed. + virtual void OnChildStatusChanged(const User& user); + protected: virtual ~Observer(); }; @@ -70,9 +73,6 @@ // on account_id hash would be accessing up-to-date value. virtual void ActiveUserHashChanged(const std::string& hash); - // Called when child status has changed. - virtual void UserChangedChildStatus(User* user); - protected: virtual ~UserSessionStateObserver(); };
diff --git a/components/user_manager/user_manager_base.cc b/components/user_manager/user_manager_base.cc index 6ed0497..961e3b73 100644 --- a/components/user_manager/user_manager_base.cc +++ b/components/user_manager/user_manager_base.cc
@@ -1028,8 +1028,8 @@ SaveUserType(user->GetAccountId(), is_child ? user_manager::USER_TYPE_CHILD : user_manager::USER_TYPE_REGULAR); - for (auto& observer : session_state_observer_list_) - observer.UserChangedChildStatus(user); + for (auto& observer : observer_list_) + observer.OnChildStatusChanged(*user); } void UserManagerBase::ResetProfileEverInitialized(const AccountId& account_id) {
diff --git a/content/browser/accessibility/accessibility_tree_formatter_blink.cc b/content/browser/accessibility/accessibility_tree_formatter_blink.cc index fc64639..1c1c81a 100644 --- a/content/browser/accessibility/accessibility_tree_formatter_blink.cc +++ b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
@@ -138,7 +138,8 @@ dict->SetInteger("boundsWidth", bounds.width()); dict->SetInteger("boundsHeight", bounds.height()); - gfx::Rect page_bounds = node.GetPageBoundsRect(); + bool offscreen = false; + gfx::Rect page_bounds = node.GetPageBoundsRect(&offscreen); dict->SetInteger("pageBoundsX", page_bounds.x()); dict->SetInteger("pageBoundsY", page_bounds.y()); dict->SetInteger("pageBoundsWidth", page_bounds.width()); @@ -151,11 +152,16 @@ for (int state_index = ui::AX_STATE_NONE; state_index <= ui::AX_STATE_LAST; ++state_index) { + if (state_index == ui::AX_STATE_OFFSCREEN) + continue; auto state = static_cast<ui::AXState>(state_index); if (node.HasState(state)) dict->SetBoolean(ui::ToString(state), true); } + if (offscreen) + dict->SetBoolean(ui::ToString(ui::AX_STATE_OFFSCREEN), true); + for (int attr_index = ui::AX_STRING_ATTRIBUTE_NONE; attr_index <= ui::AX_STRING_ATTRIBUTE_LAST; ++attr_index) {
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc index ac0ffff..1612f40 100644 --- a/content/browser/accessibility/browser_accessibility.cc +++ b/content/browser/accessibility/browser_accessibility.cc
@@ -335,8 +335,8 @@ return RelativeToAbsoluteBounds(gfx::RectF(), true); } -gfx::Rect BrowserAccessibility::GetPageBoundsRect() const { - return RelativeToAbsoluteBounds(gfx::RectF(), false); +gfx::Rect BrowserAccessibility::GetPageBoundsRect(bool* offscreen) const { + return RelativeToAbsoluteBounds(gfx::RectF(), false, offscreen); } gfx::Rect BrowserAccessibility::GetPageBoundsForRange(int start, int len) @@ -854,11 +854,12 @@ gfx::Rect BrowserAccessibility::RelativeToAbsoluteBounds( gfx::RectF bounds, - bool frame_only) const { + bool frame_only, + bool* offscreen) const { const BrowserAccessibility* node = this; while (node) { - bounds = - node->manager()->ax_tree()->RelativeToTreeBounds(node->node(), bounds); + bounds = node->manager()->ax_tree()->RelativeToTreeBounds( + node->node(), bounds, offscreen); // On some platforms we need to unapply root scroll offsets. const BrowserAccessibility* root = node->manager()->GetRoot();
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h index 9114604c..3ddedd3 100644 --- a/content/browser/accessibility/browser_accessibility.h +++ b/content/browser/accessibility/browser_accessibility.h
@@ -152,7 +152,9 @@ // Returns the bounds of this object in coordinates relative to the // page (specifically, the top-left corner of the topmost web contents). - gfx::Rect GetPageBoundsRect() const; + // Optionally updates |offscreen| to be true if the element is offscreen + // within its page. + gfx::Rect GetPageBoundsRect(bool* offscreen = nullptr) const; // Returns the bounds of the given range in coordinates relative to the // top-left corner of the overall web area. Only valid when the @@ -166,8 +168,10 @@ // (which is relative to its nearest scrollable ancestor) to // absolute bounds, either in page coordinates (when |frameOnly| is // false), or in frame coordinates (when |frameOnly| is true). + // Updates optional |offscreen| to be true if the node is offscreen. virtual gfx::Rect RelativeToAbsoluteBounds(gfx::RectF bounds, - bool frame_only) const; + bool frame_only, + bool* offscreen = nullptr) const; // This is to handle the cases such as ARIA textbox, where the value should // be calculated from the object's inner text.
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc index fd2e3cd1..ecbb9a34 100644 --- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc +++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -1494,6 +1494,15 @@ RunHtmlTest(FILE_PATH_LITERAL("object.html")); } +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityOffscreen) { + RunHtmlTest(FILE_PATH_LITERAL("offscreen.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityOffscreenScroll) { + RunHtmlTest(FILE_PATH_LITERAL("offscreen-scroll.html")); +} + IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityOptgroup) { RunHtmlTest(FILE_PATH_LITERAL("optgroup.html")); }
diff --git a/content/browser/download/download_item_impl_unittest.cc b/content/browser/download/download_item_impl_unittest.cc index 8d00725..f682b55 100644 --- a/content/browser/download/download_item_impl_unittest.cc +++ b/content/browser/download/download_item_impl_unittest.cc
@@ -19,6 +19,7 @@ #include "base/feature_list.h" #include "base/files/file_util.h" #include "base/memory/ptr_util.h" +#include "base/test/histogram_tester.h" #include "base/test/scoped_task_environment.h" #include "base/threading/thread.h" #include "content/browser/byte_stream.h" @@ -65,6 +66,11 @@ namespace { +template <typename T> +base::HistogramBase::Sample ToHistogramSample(T t) { + return static_cast<base::HistogramBase::Sample>(t); +} + class MockDelegate : public DownloadItemImplDelegate { public: MockDelegate() : DownloadItemImplDelegate() { @@ -645,6 +651,7 @@ Property(&DownloadUrlParameters::offset, 1)), _)); + base::HistogramTester histogram_tester; item->DestinationObserverAsWeakPtr()->DestinationError( DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, 1, std::unique_ptr<crypto::SecureHash>()); @@ -660,6 +667,14 @@ // the automatic resumption was triggered. task_environment_.RunUntilIdle(); + // Interrupt reason is recorded in auto resumption even when download is not + // finally interrupted. + histogram_tester.ExpectBucketCount( + "Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR), + 1); + // The download item is currently in RESUMING_INTERNAL state, which maps to // IN_PROGRESS. CleanupItem(item, nullptr, DownloadItem::IN_PROGRESS); @@ -684,6 +699,7 @@ Property(&base::FilePath::empty, true)), _)); + base::HistogramTester histogram_tester; item->DestinationObserverAsWeakPtr()->DestinationError( DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE, 1, std::unique_ptr<crypto::SecureHash>()); @@ -692,8 +708,15 @@ // Since the download is resumed automatically, the interrupt count doesn't // increase. ASSERT_EQ(0, observer.interrupt_count()); - task_environment_.RunUntilIdle(); + task_environment_.RunUntilIdle(); + // Auto resumption will record interrupt reason even if download is not + // finally interrupted. + histogram_tester.ExpectBucketCount( + "Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE), + 1); CleanupItem(item, nullptr, DownloadItem::IN_PROGRESS); } @@ -707,6 +730,7 @@ // Interrupt the download, using a restartable interrupt. EXPECT_CALL(*download_file, Cancel()); + base::HistogramTester histogram_tester; item->DestinationObserverAsWeakPtr()->DestinationError( DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, 1, std::unique_ptr<crypto::SecureHash>()); @@ -714,8 +738,12 @@ // Should not try to auto-resume. ASSERT_EQ(1, observer.interrupt_count()); ASSERT_EQ(0, observer.resume_count()); - task_environment_.RunUntilIdle(); + task_environment_.RunUntilIdle(); + histogram_tester.ExpectBucketCount("Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED), + 1); CleanupItem(item, nullptr, DownloadItem::INTERRUPTED); } @@ -744,6 +772,7 @@ Property(&DownloadUrlParameters::offset, 1)), _)); + base::HistogramTester histogram_tester; item->DestinationObserverAsWeakPtr()->DestinationError( DOWNLOAD_INTERRUPT_REASON_SERVER_CONTENT_LENGTH_MISMATCH, 1, std::unique_ptr<crypto::SecureHash>()); @@ -754,6 +783,11 @@ ASSERT_EQ(0, observer.resume_count()); task_environment_.RunUntilIdle(); + histogram_tester.ExpectBucketCount( + "Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_SERVER_CONTENT_LENGTH_MISMATCH), + 1); CleanupItem(item, nullptr, DownloadItem::IN_PROGRESS); } @@ -774,10 +808,16 @@ EXPECT_CALL(*download_file, Cancel()); // Complete download to trigger final rename. + base::HistogramTester histogram_tester; item->DestinationObserverAsWeakPtr()->DestinationCompleted( 0, std::unique_ptr<crypto::SecureHash>()); - task_environment_.RunUntilIdle(); + task_environment_.RunUntilIdle(); + histogram_tester.ExpectBucketCount( + "Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED), + 1); ASSERT_TRUE(observer.CheckAndResetDownloadUpdated()); // Should not try to auto-resume. ASSERT_EQ(1, observer.interrupt_count()); @@ -787,6 +827,7 @@ } TEST_F(DownloadItemTest, AutomaticResumption_AttemptLimit) { + base::HistogramTester histogram_tester; DownloadItemImpl* item = CreateDownloadItem(); base::WeakPtr<DownloadDestinationObserver> as_observer( item->DestinationObserverAsWeakPtr()); @@ -854,6 +895,11 @@ ::testing::Mock::VerifyAndClearExpectations(mock_download_file_ref); } + histogram_tester.ExpectBucketCount( + "Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR), + DownloadItemImpl::kMaxAutoResumeAttempts + 1); EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); EXPECT_EQ(1, observer.interrupt_count()); CleanupItem(item, nullptr, DownloadItem::INTERRUPTED); @@ -1126,6 +1172,7 @@ std::unique_ptr<MockRequestHandle> request_handle = base::MakeUnique<MockRequestHandle>(); + base::HistogramTester histogram_tester; EXPECT_CALL(*file, Cancel()); EXPECT_CALL(*request_handle, CancelRequest(_)); EXPECT_CALL(*file, Initialize(_, _, _, _)) @@ -1151,11 +1198,17 @@ item->GetLastReason()); EXPECT_FALSE(item->GetTargetFilePath().empty()); EXPECT_TRUE(item->GetFullPath().empty()); + histogram_tester.ExpectBucketCount( + "Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED), + 1); } // Handling of downloads initiated via a failed request. In this case, Start() // will get called with a DownloadCreateInfo with a non-zero interrupt_reason. TEST_F(DownloadItemTest, StartFailedDownload) { + base::HistogramTester histogram_tester; create_info()->result = DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED; DownloadItemImpl* item = CreateDownloadItem(); @@ -1182,6 +1235,12 @@ DOWNLOAD_INTERRUPT_REASON_NONE); task_environment_.RunUntilIdle(); + // Interrupt reason carried in create info should be recorded. + histogram_tester.ExpectBucketCount( + "Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED), + 1); EXPECT_EQ(target_path, item->GetTargetFilePath()); CleanupItem(item, NULL, DownloadItem::INTERRUPTED); } @@ -1228,6 +1287,7 @@ TEST_F(DownloadItemTest, CallbackAfterInterruptedRename) { DownloadItemImpl* item = CreateDownloadItem(); DownloadTargetCallback callback; + base::HistogramTester histogram_tester; MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); base::FilePath final_path( base::FilePath(kDummyTargetPath).AppendASCII("foo.bar")); @@ -1247,9 +1307,14 @@ // All the callbacks should have happened by now. ::testing::Mock::VerifyAndClearExpectations(download_file); mock_delegate()->VerifyAndClearExpectations(); + histogram_tester.ExpectBucketCount("Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED), + 1); } TEST_F(DownloadItemTest, Interrupted) { + base::HistogramTester histogram_tester; DownloadItemImpl* item = CreateDownloadItem(); MockDownloadFile* download_file = DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); @@ -1269,11 +1334,16 @@ item->Cancel(true); EXPECT_EQ(DownloadItem::CANCELLED, item->GetState()); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_USER_CANCELED, item->GetLastReason()); + + histogram_tester.ExpectBucketCount( + "Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>(reason), 1); } // Destination errors that occur before the intermediate rename shouldn't cause // the download to be marked as interrupted until after the intermediate rename. TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Restart) { + base::HistogramTester histogram_tester; DownloadItemImpl* item = CreateDownloadItem(); DownloadTargetCallback callback; MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); @@ -1303,12 +1373,17 @@ EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); EXPECT_TRUE(item->GetFullPath().empty()); EXPECT_EQ(final_path, item->GetTargetFilePath()); + histogram_tester.ExpectBucketCount("Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED), + 1); } // As above. But if the download can be resumed by continuing, then the // intermediate path should be retained when the download is interrupted after // the intermediate rename succeeds. TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Continue) { + base::HistogramTester histogram_tester; DownloadItemImpl* item = CreateDownloadItem(); DownloadTargetCallback callback; MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); @@ -1342,11 +1417,17 @@ EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); EXPECT_EQ(new_intermediate_path, item->GetFullPath()); EXPECT_EQ(final_path, item->GetTargetFilePath()); + histogram_tester.ExpectBucketCount( + "Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED), + 1); } // As above. If the intermediate rename fails, then the interrupt reason should // be set to the file error and the intermediate path should be empty. TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Failed) { + base::HistogramTester histogram_tester; DownloadItemImpl* item = CreateDownloadItem(); DownloadTargetCallback callback; MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); @@ -1377,6 +1458,15 @@ EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, item->GetLastReason()); EXPECT_TRUE(item->GetFullPath().empty()); EXPECT_EQ(final_path, item->GetTargetFilePath()); + + // Rename error will overwrite the previous network interrupt reason. + // TODO(xingliu): See if we should report both interrupted reasons or the + // first one, see https://crbug.com/769040. + histogram_tester.ExpectBucketCount("Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED), + 1); + histogram_tester.ExpectTotalCount("Download.InterruptedReason", 1); } TEST_F(DownloadItemTest, Canceled) { @@ -1476,6 +1566,7 @@ } TEST_F(DownloadItemTest, DestinationError_NoRestartRequired) { + base::HistogramTester histogram_tester; DownloadItemImpl* item = CreateDownloadItem(); MockDownloadFile* download_file = DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); @@ -1501,9 +1592,15 @@ EXPECT_EQ( std::string(std::begin(kHashOfTestData1), std::end(kHashOfTestData1)), item->GetHash()); + histogram_tester.ExpectBucketCount( + "Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED), + 1); } TEST_F(DownloadItemTest, DestinationError_RestartRequired) { + base::HistogramTester histogram_tester; DownloadItemImpl* item = CreateDownloadItem(); MockDownloadFile* download_file = DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); @@ -1527,9 +1624,14 @@ EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, item->GetLastReason()); EXPECT_EQ(std::string(), item->GetHash()); + histogram_tester.ExpectBucketCount("Download.InterruptedReason", + ToHistogramSample<DownloadInterruptReason>( + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED), + 1); } TEST_F(DownloadItemTest, DestinationCompleted) { + base::HistogramTester histogram_tester; DownloadItemImpl* item = CreateDownloadItem(); MockDownloadFile* download_file = DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); @@ -1568,6 +1670,8 @@ // target determination hasn't completed, hence the download item is stuck in // TARGET_PENDING. CleanupItem(item, download_file, DownloadItem::IN_PROGRESS); + + histogram_tester.ExpectTotalCount("Download.InterruptedReason", 0); } TEST_F(DownloadItemTest, EnabledActionsForNormalDownload) {
diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc index 0eb0fb0..7742e3b 100644 --- a/content/browser/download/download_manager_impl.cc +++ b/content/browser/download/download_manager_impl.cc
@@ -38,6 +38,7 @@ #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/storage_partition_impl.h" #include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/throttling_url_loader.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/download_interrupt_reasons.h" @@ -118,7 +119,7 @@ params->callback())); } -std::unique_ptr<UrlDownloader, BrowserThread::DeleteOnIOThread> BeginDownload( +DownloadManagerImpl::UniqueUrlDownloadHandlerPtr BeginDownload( std::unique_ptr<DownloadUrlParameters> params, content::ResourceContext* resource_context, uint32_t download_id, @@ -156,14 +157,13 @@ return nullptr; } - return std::unique_ptr<UrlDownloader, BrowserThread::DeleteOnIOThread>( + return DownloadManagerImpl::UniqueUrlDownloadHandlerPtr( UrlDownloader::BeginDownload(download_manager, std::move(url_request), params->referrer(), false) .release()); } -std::unique_ptr<ResourceDownloader, BrowserThread::DeleteOnIOThread> -BeginResourceDownload( +DownloadManagerImpl::UniqueUrlDownloadHandlerPtr BeginResourceDownload( std::unique_ptr<DownloadUrlParameters> params, std::unique_ptr<ResourceRequest> request, scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter, @@ -181,14 +181,38 @@ return nullptr; } - return std::unique_ptr<ResourceDownloader, BrowserThread::DeleteOnIOThread>( - ResourceDownloader::BeginDownload(download_manager, std::move(params), - std::move(request), - url_loader_factory_getter, download_id, - false) + return DownloadManagerImpl::UniqueUrlDownloadHandlerPtr( + ResourceDownloader::BeginDownload( + download_manager, std::move(params), std::move(request), + url_loader_factory_getter, download_id, false) .release()); } +// Creates a ResourceDownloader to own the URLLoader and intercept the response, +// and passes it back to the DownloadManager. +void InterceptNavigationResponse( + base::OnceCallback<void(DownloadManagerImpl::UniqueUrlDownloadHandlerPtr)> + callback, + base::WeakPtr<DownloadManagerImpl> download_manager, + const scoped_refptr<ResourceResponse>& response, + mojo::ScopedDataPipeConsumerHandle consumer_handle, + const SSLStatus& ssl_status, + std::unique_ptr<ResourceRequest> resource_request, + std::unique_ptr<ThrottlingURLLoader> url_loader, + base::Optional<ResourceRequestCompletionStatus> completion_status) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DownloadManagerImpl::UniqueUrlDownloadHandlerPtr resource_downloader( + ResourceDownloader::InterceptNavigationResponse( + download_manager, std::move(resource_request), response, + std::move(consumer_handle), ssl_status, std::move(url_loader), + std::move(completion_status)) + .release()); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::BindOnce(std::move(callback), std::move(resource_downloader))); +} + class DownloadItemFactoryImpl : public DownloadItemFactory { public: DownloadItemFactoryImpl() {} @@ -603,8 +627,7 @@ } void DownloadManagerImpl::AddUrlDownloadHandler( - std::unique_ptr<UrlDownloadHandler, BrowserThread::DeleteOnIOThread> - downloader) { + UniqueUrlDownloadHandlerPtr downloader) { if (downloader) url_download_handlers_.push_back(std::move(downloader)); } @@ -665,6 +688,19 @@ return DOWNLOAD_INTERRUPT_REASON_NONE; } +NavigationURLLoader::NavigationInterceptionCB +DownloadManagerImpl::GetNavigationInterceptionCB( + const scoped_refptr<ResourceResponse>& response, + mojo::ScopedDataPipeConsumerHandle consumer_handle, + const SSLStatus& ssl_status) { + return base::BindOnce( + &InterceptNavigationResponse, + base::BindOnce(&DownloadManagerImpl::AddUrlDownloadHandler, + weak_factory_.GetWeakPtr()), + weak_factory_.GetWeakPtr(), response, std::move(consumer_handle), + ssl_status); +} + int DownloadManagerImpl::RemoveDownloadsByURLAndTime( const base::Callback<bool(const GURL&)>& url_filter, base::Time remove_begin,
diff --git a/content/browser/download/download_manager_impl.h b/content/browser/download/download_manager_impl.h index 49ca8ed..14e66faf 100644 --- a/content/browser/download/download_manager_impl.h +++ b/content/browser/download/download_manager_impl.h
@@ -22,11 +22,13 @@ #include "base/synchronization/lock.h" #include "content/browser/download/download_item_impl_delegate.h" #include "content/browser/download/url_download_handler.h" +#include "content/browser/loader/navigation_url_loader.h" #include "content/common/content_export.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/download_manager_delegate.h" #include "content/public/browser/download_url_parameters.h" +#include "content/public/browser/ssl_status.h" namespace net { class NetLog; @@ -44,6 +46,8 @@ private DownloadItemImplDelegate { public: using DownloadItemImplCreated = base::Callback<void(DownloadItemImpl*)>; + using UniqueUrlDownloadHandlerPtr = + std::unique_ptr<UrlDownloadHandler, BrowserThread::DeleteOnIOThread>; // Caller guarantees that |net_log| will remain valid // for the lifetime of DownloadManagerImpl (until Shutdown() is called). @@ -143,6 +147,12 @@ int render_frame_route_id, bool do_not_prompt_for_login); + // Returns the callback to intercept the navigation response. + NavigationURLLoader::NavigationInterceptionCB GetNavigationInterceptionCB( + const scoped_refptr<ResourceResponse>& response, + mojo::ScopedDataPipeConsumerHandle consumer_handle, + const SSLStatus& ssl_status); + private: using DownloadSet = std::set<DownloadItem*>; using DownloadGuidMap = std::unordered_map<std::string, DownloadItemImpl*>; @@ -199,9 +209,7 @@ void ShowDownloadInShell(DownloadItemImpl* download) override; void DownloadRemoved(DownloadItemImpl* download) override; - void AddUrlDownloadHandler( - std::unique_ptr<UrlDownloadHandler, BrowserThread::DeleteOnIOThread> - downloader); + void AddUrlDownloadHandler(UniqueUrlDownloadHandlerPtr downloader); // Factory for creation of downloads items. std::unique_ptr<DownloadItemFactory> item_factory_; @@ -240,9 +248,7 @@ net::NetLog* net_log_; - std::vector< - std::unique_ptr<UrlDownloadHandler, BrowserThread::DeleteOnIOThread>> - url_download_handlers_; + std::vector<UniqueUrlDownloadHandlerPtr> url_download_handlers_; base::WeakPtrFactory<DownloadManagerImpl> weak_factory_;
diff --git a/content/browser/download/download_response_handler.cc b/content/browser/download/download_response_handler.cc index 58cf742..2e1bc68 100644 --- a/content/browser/download/download_response_handler.cc +++ b/content/browser/download/download_response_handler.cc
@@ -45,22 +45,24 @@ } // namespace -DownloadResponseHandler::DownloadResponseHandler(DownloadUrlParameters* params, - Delegate* delegate, - bool is_parallel_request) +DownloadResponseHandler::DownloadResponseHandler( + ResourceRequest* resource_request, + Delegate* delegate, + std::unique_ptr<DownloadSaveInfo> save_info, + bool is_parallel_request, + bool is_transient) : delegate_(delegate), started_(false), - save_info_(base::MakeUnique<DownloadSaveInfo>(params->GetSaveInfo())), - url_chain_(1, params->url()), - guid_(params->guid()), - method_(params->method()), - referrer_(params->referrer().url), - is_transient_(params->is_transient()), + save_info_(std::move(save_info)), + url_chain_(1, resource_request->url), + method_(resource_request->method), + referrer_(resource_request->referrer), + is_transient_(is_transient), has_strong_validators_(false) { if (!is_parallel_request) RecordDownloadCount(UNTHROTTLED_COUNT); - if (params->initiator().has_value()) - origin_ = params->initiator().value().GetURL(); + if (resource_request->request_initiator.has_value()) + origin_ = resource_request->request_initiator.value().GetURL(); } DownloadResponseHandler::~DownloadResponseHandler() = default; @@ -118,7 +120,6 @@ create_info->connection_info = head.connection_info; create_info->url_chain = url_chain_; create_info->referrer_url = referrer_; - create_info->guid = guid_; create_info->transient = is_transient_; create_info->response_headers = head.headers; create_info->offset = create_info->save_info->offset;
diff --git a/content/browser/download/download_response_handler.h b/content/browser/download/download_response_handler.h index b080d1e..0881b1d 100644 --- a/content/browser/download/download_response_handler.h +++ b/content/browser/download/download_response_handler.h
@@ -16,7 +16,6 @@ namespace content { -class DownloadUrlParameters; struct DownloadCreateInfo; struct DownloadSaveInfo; @@ -35,9 +34,11 @@ virtual void OnReceiveRedirect() = 0; }; - DownloadResponseHandler(DownloadUrlParameters* params, + DownloadResponseHandler(ResourceRequest* resource_request, Delegate* delegate, - bool is_parallel_request); + std::unique_ptr<DownloadSaveInfo> save_info, + bool is_parallel_request, + bool is_transient); ~DownloadResponseHandler() override; // mojom::URLLoaderClient @@ -73,7 +74,6 @@ // Information needed to create DownloadCreateInfo when the time comes. std::unique_ptr<DownloadSaveInfo> save_info_; std::vector<GURL> url_chain_; - std::string guid_; std::string method_; GURL referrer_; bool is_transient_;
diff --git a/content/browser/download/resource_downloader.cc b/content/browser/download/resource_downloader.cc index e066986..2b8d74f5 100644 --- a/content/browser/download/resource_downloader.cc +++ b/content/browser/download/resource_downloader.cc
@@ -5,7 +5,6 @@ #include "content/browser/download/resource_downloader.h" #include "content/browser/download/download_utils.h" -#include "content/browser/url_loader_factory_getter.h" #include "content/common/throttling_url_loader.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" @@ -45,60 +44,106 @@ // static std::unique_ptr<ResourceDownloader> ResourceDownloader::BeginDownload( base::WeakPtr<UrlDownloadHandler::Delegate> delegate, - std::unique_ptr<DownloadUrlParameters> download_url_parameters, + std::unique_ptr<DownloadUrlParameters> params, std::unique_ptr<ResourceRequest> request, scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter, uint32_t download_id, bool is_parallel_request) { + mojom::URLLoaderFactoryPtr* factory = + params->url().SchemeIs(url::kBlobScheme) + ? url_loader_factory_getter->GetBlobFactory() + : url_loader_factory_getter->GetNetworkFactory(); auto downloader = base::MakeUnique<ResourceDownloader>( - delegate, std::move(download_url_parameters), url_loader_factory_getter, - download_id, is_parallel_request); - downloader->Start(std::move(request)); + delegate, std::move(request), + base::MakeUnique<DownloadSaveInfo>(params->GetSaveInfo()), download_id, + params->guid(), is_parallel_request, params->is_transient()); + downloader->Start(factory, std::move(params)); return downloader; } +// static +std::unique_ptr<ResourceDownloader> +ResourceDownloader::InterceptNavigationResponse( + base::WeakPtr<UrlDownloadHandler::Delegate> delegate, + std::unique_ptr<ResourceRequest> resource_request, + const scoped_refptr<ResourceResponse>& response, + mojo::ScopedDataPipeConsumerHandle consumer_handle, + const SSLStatus& ssl_status, + std::unique_ptr<ThrottlingURLLoader> url_loader, + base::Optional<ResourceRequestCompletionStatus> completion_status) { + auto downloader = base::MakeUnique<ResourceDownloader>( + delegate, std::move(resource_request), + base::MakeUnique<DownloadSaveInfo>(), content::DownloadItem::kInvalidId, + std::string(), false, false); + downloader->InterceptResponse(std::move(url_loader), response, + std::move(consumer_handle), ssl_status, + std::move(completion_status)); + return downloader; +} + ResourceDownloader::ResourceDownloader( base::WeakPtr<UrlDownloadHandler::Delegate> delegate, - std::unique_ptr<DownloadUrlParameters> download_url_parameters, - scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter, + std::unique_ptr<ResourceRequest> resource_request, + std::unique_ptr<DownloadSaveInfo> save_info, uint32_t download_id, - bool is_parallel_request) + std::string guid, + bool is_parallel_request, + bool is_transient) : delegate_(delegate), - download_url_parameters_(std::move(download_url_parameters)), - url_loader_factory_getter_(url_loader_factory_getter), - response_handler_(download_url_parameters_.get(), + resource_request_(std::move(resource_request)), + response_handler_(resource_request_.get(), this, - is_parallel_request), + std::move(save_info), + is_parallel_request, + is_transient), download_id_(download_id), + guid_(guid), weak_ptr_factory_(this) {} ResourceDownloader::~ResourceDownloader() = default; -void ResourceDownloader::Start(std::unique_ptr<ResourceRequest> request) { - mojom::URLLoaderFactoryPtr* factory = - download_url_parameters_->url().SchemeIs(url::kBlobScheme) - ? url_loader_factory_getter_->GetBlobFactory() - : url_loader_factory_getter_->GetNetworkFactory(); +void ResourceDownloader::Start( + mojom::URLLoaderFactoryPtr* factory, + std::unique_ptr<DownloadUrlParameters> download_url_parameters) { + callback_ = download_url_parameters->callback(); url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart( factory->get(), std::vector<std::unique_ptr<URLLoaderThrottle>>(), 0, // routing_id 0, // request_id mojom::kURLLoadOptionSendSSLInfo | mojom::kURLLoadOptionSniffMimeType, - *(request.get()), &response_handler_, - download_url_parameters_->GetNetworkTrafficAnnotation()); + *(resource_request_.get()), &response_handler_, + download_url_parameters->GetNetworkTrafficAnnotation()); url_loader_->SetPriority(net::RequestPriority::IDLE, 0 /* intra_priority_value */); } +void ResourceDownloader::InterceptResponse( + std::unique_ptr<ThrottlingURLLoader> url_loader, + const scoped_refptr<ResourceResponse>& response, + mojo::ScopedDataPipeConsumerHandle consumer_handle, + const SSLStatus& ssl_status, + base::Optional<ResourceRequestCompletionStatus> completion_status) { + url_loader_ = std::move(url_loader); + url_loader_->set_forwarding_client(&response_handler_); + net::SSLInfo info; + info.cert_status = ssl_status.cert_status; + response_handler_.OnReceiveResponse(response->head, + base::Optional<net::SSLInfo>(info), + mojom::DownloadedTempFilePtr()); + response_handler_.OnStartLoadingResponseBody(std::move(consumer_handle)); + if (completion_status.has_value()) + response_handler_.OnComplete(completion_status.value()); +} + void ResourceDownloader::OnResponseStarted( std::unique_ptr<DownloadCreateInfo> download_create_info, mojom::DownloadStreamHandlePtr stream_handle) { download_create_info->download_id = download_id_; - if (download_url_parameters_->render_process_host_id() >= 0) { + download_create_info->guid = guid_; + if (resource_request_->origin_pid >= 0) { download_create_info->request_handle.reset(new RequestHandle( - download_url_parameters_->render_process_host_id(), - download_url_parameters_->render_frame_host_routing_id())); + resource_request_->origin_pid, resource_request_->render_frame_id)); } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -106,7 +151,7 @@ delegate_, std::move(download_create_info), base::MakeUnique<DownloadManager::InputStream>( std::move(stream_handle)), - download_url_parameters_->callback())); + callback_)); } void ResourceDownloader::OnReceiveRedirect() {
diff --git a/content/browser/download/resource_downloader.h b/content/browser/download/resource_downloader.h index b5146e8f..6b00e687 100644 --- a/content/browser/download/resource_downloader.h +++ b/content/browser/download/resource_downloader.h
@@ -7,13 +7,14 @@ #include "content/browser/download/download_response_handler.h" #include "content/browser/download/url_download_handler.h" +#include "content/browser/url_loader_factory_getter.h" +#include "content/public/browser/ssl_status.h" #include "content/public/common/resource_request.h" #include "content/public/common/url_loader.mojom.h" namespace content { class ThrottlingURLLoader; -class URLLoaderFactoryGetter; // Class for handing the download of a url. class ResourceDownloader : public UrlDownloadHandler, @@ -28,12 +29,25 @@ uint32_t download_id, bool is_parallel_request); - ResourceDownloader( + // Called to intercept a navigation response. + static std::unique_ptr<ResourceDownloader> InterceptNavigationResponse( base::WeakPtr<UrlDownloadHandler::Delegate> delegate, - std::unique_ptr<DownloadUrlParameters> download_url_parameters, - scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter, - uint32_t download_id, - bool is_parallel_request); + std::unique_ptr<ResourceRequest> resource_request, + const scoped_refptr<ResourceResponse>& response, + mojo::ScopedDataPipeConsumerHandle consumer_handle, + const SSLStatus& ssl_status, + std::unique_ptr<ThrottlingURLLoader> url_loader, + base::Optional<ResourceRequestCompletionStatus> completion_status); + + ResourceDownloader(base::WeakPtr<UrlDownloadHandler::Delegate> delegate, + std::unique_ptr<ResourceRequest> resource_request, + std::unique_ptr<DownloadSaveInfo> save_info, + uint32_t download_id, + std::string guid, + bool is_parallel_request, + bool is_transient); + ResourceDownloader(base::WeakPtr<UrlDownloadHandler::Delegate> delegate, + std::unique_ptr<ThrottlingURLLoader> url_loader); ~ResourceDownloader() override; // DownloadResponseHandler::Delegate @@ -44,18 +58,24 @@ private: // Helper method to start the network request. - void Start(std::unique_ptr<ResourceRequest> request); + void Start(mojom::URLLoaderFactoryPtr* factory, + std::unique_ptr<DownloadUrlParameters> download_url_parameters); + + // Intercepts the navigation response and takes ownership of the |url_loader|. + void InterceptResponse( + std::unique_ptr<ThrottlingURLLoader> url_loader, + const scoped_refptr<ResourceResponse>& response, + mojo::ScopedDataPipeConsumerHandle consumer_handle, + const SSLStatus& ssl_status, + base::Optional<ResourceRequestCompletionStatus> completion_status); base::WeakPtr<UrlDownloadHandler::Delegate> delegate_; // URLLoader for sending out the request. std::unique_ptr<ThrottlingURLLoader> url_loader_; - // Parameters for constructing the ResourceRequest. - std::unique_ptr<DownloadUrlParameters> download_url_parameters_; - - // Object for retrieving the URLLoaderFactory. - scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter_; + // The ResourceRequest for this object. + std::unique_ptr<ResourceRequest> resource_request_; // Object for handing the server response. DownloadResponseHandler response_handler_; @@ -64,6 +84,12 @@ // download. uint32_t download_id_; + // GUID of the download, or empty if this is a new download. + std::string guid_; + + // Callback to run after download starts. + DownloadUrlParameters::OnStartedCallback callback_; + base::WeakPtrFactory<ResourceDownloader> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(ResourceDownloader);
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc index ee570c4..b4ece03 100644 --- a/content/browser/frame_host/navigation_request.cc +++ b/content/browser/frame_host/navigation_request.cc
@@ -13,6 +13,7 @@ #include "content/browser/appcache/chrome_appcache_service.h" #include "content/browser/child_process_security_policy_impl.h" #include "content/browser/devtools/render_frame_devtools_agent_host.h" +#include "content/browser/download/download_manager_impl.h" #include "content/browser/frame_host/debug_urls.h" #include "content/browser/frame_host/frame_tree.h" #include "content/browser/frame_host/frame_tree_node.h" @@ -728,6 +729,8 @@ response_ = response; body_ = std::move(body); handle_ = std::move(consumer_handle); + ssl_status_ = ssl_status; + is_download_ = is_download; subresource_loader_factory_info_ = std::move(subresource_loader_factory_info); @@ -970,8 +973,23 @@ // If the NavigationThrottles allowed the navigation to continue, have the // processing of the response resume in the network stack. - if (result.action() == NavigationThrottle::PROCEED) + if (result.action() == NavigationThrottle::PROCEED) { + // If this is a download, intercept the navigation response and pass it to + // DownloadManager, and cancel the navigation. + if (is_download_ && + base::FeatureList::IsEnabled(features::kNetworkService)) { + BrowserContext* browser_context = + frame_tree_node_->navigator()->GetController()->GetBrowserContext(); + DownloadManagerImpl* download_manager = static_cast<DownloadManagerImpl*>( + BrowserContext::GetDownloadManager(browser_context)); + loader_->InterceptNavigation( + download_manager->GetNavigationInterceptionCB( + response_, std::move(handle_), ssl_status_)); + OnRequestFailed(false, net::ERR_ABORTED, base::nullopt, false); + return; + } loader_->ProceedWithResponse(); + } // Abort the request if needed. This includes requests that were blocked by // NavigationThrottles and requests that should not commit (e.g. downloads,
diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h index c78665b..f2ee387 100644 --- a/content/browser/frame_host/navigation_request.h +++ b/content/browser/frame_host/navigation_request.h
@@ -316,6 +316,8 @@ scoped_refptr<ResourceResponse> response_; std::unique_ptr<StreamHandle> body_; mojo::ScopedDataPipeConsumerHandle handle_; + SSLStatus ssl_status_; + bool is_download_; base::Closure on_start_checks_complete_closure_;
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc index fff0f247..19ca15a 100644 --- a/content/browser/frame_host/render_frame_host_impl.cc +++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -3372,6 +3372,19 @@ !IsURLHandledByNetworkStack(common_params.url) || FrameMsg_Navigate_Type::IsSameDocument(common_params.navigation_type) || IsRendererDebugURL(common_params.url)); + + // TODO(arthursonzogni): Consider using separate methods and IPCs for + // javascript-url navigation. Excluding this case from the general one will + // prevent us from doing inappropriate things with javascript-url. + // See https://crbug.com/766149. + if (common_params.url.SchemeIs(url::kJavaScriptScheme)) { + Send(new FrameMsg_CommitNavigation( + routing_id_, ResourceResponseHead(), GURL(), + FrameMsg_CommitDataNetworkService_Params(), common_params, + request_params)); + return; + } + UpdatePermissionsForNavigation(common_params, request_params); // Get back to a clean state, in case we start a new navigation without
diff --git a/content/browser/loader/navigation_url_loader.h b/content/browser/loader/navigation_url_loader.h index e015126..208fa14 100644 --- a/content/browser/loader/navigation_url_loader.h +++ b/content/browser/loader/navigation_url_loader.h
@@ -7,8 +7,11 @@ #include <memory> +#include "base/callback.h" #include "base/macros.h" +#include "base/optional.h" #include "content/common/content_export.h" +#include "content/public/common/resource_request_completion_status.h" namespace content { @@ -19,7 +22,9 @@ class ResourceContext; class ServiceWorkerNavigationHandle; class StoragePartition; +class ThrottlingURLLoader; struct NavigationRequestInfo; +struct ResourceRequest; // PlzNavigate: The navigation logic's UI thread entry point into the resource // loading stack. It exposes an interface to control the request prior to @@ -56,6 +61,20 @@ // Called in response to OnResponseStarted to process the response. virtual void ProceedWithResponse() = 0; + // Callback to intercept the response from the URLLoader. Only used when + // network service is enabled. Args: the initial resource request, + // the URLLoader for sending the request, optional completion status if + // it has already been received. + using NavigationInterceptionCB = + base::OnceCallback<void(std::unique_ptr<ResourceRequest>, + std::unique_ptr<ThrottlingURLLoader>, + base::Optional<ResourceRequestCompletionStatus>)>; + + // This method is called to intercept the url response. Caller is responsible + // for handling the URLLoader later on. The callback should be called on the + // same thread that URLLoader is constructed. + virtual void InterceptNavigation(NavigationInterceptionCB callback) = 0; + protected: NavigationURLLoader() {}
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc index 6371909..6b77e7f 100644 --- a/content/browser/loader/navigation_url_loader_impl.cc +++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -85,6 +85,9 @@ base::BindOnce(&NavigationURLLoaderImplCore::ProceedWithResponse, core_)); } +void NavigationURLLoaderImpl::InterceptNavigation( + NavigationURLLoader::NavigationInterceptionCB callback) {} + void NavigationURLLoaderImpl::NotifyRequestRedirected( const net::RedirectInfo& redirect_info, const scoped_refptr<ResourceResponse>& response) {
diff --git a/content/browser/loader/navigation_url_loader_impl.h b/content/browser/loader/navigation_url_loader_impl.h index 3349458..86f0e021 100644 --- a/content/browser/loader/navigation_url_loader_impl.h +++ b/content/browser/loader/navigation_url_loader_impl.h
@@ -45,6 +45,7 @@ // NavigationURLLoader implementation. void FollowRedirect() override; void ProceedWithResponse() override; + void InterceptNavigation(NavigationInterceptionCB callback) override; private: friend class NavigationURLLoaderImplCore;
diff --git a/content/browser/loader/navigation_url_loader_network_service.cc b/content/browser/loader/navigation_url_loader_network_service.cc index 85ee1ec..7fb89fe 100644 --- a/content/browser/loader/navigation_url_loader_network_service.cc +++ b/content/browser/loader/navigation_url_loader_network_service.cc
@@ -333,6 +333,17 @@ Restart(); } + // Navigation is intercepted, transfer the |resource_request_|, |url_loader_| + // and the |completion_status_| to the new owner. The new owner is + // responsible for handling all the mojom::URLLoaderClient callbacks from now + // on. + void InterceptNavigation( + NavigationURLLoader::NavigationInterceptionCB callback) { + std::move(callback).Run(std::move(resource_request_), + std::move(url_loader_), + std::move(completion_status_)); + } + // Ownership of the URLLoaderFactoryPtrInfo instance is transferred to the // caller. mojom::URLLoaderFactoryPtrInfo GetSubresourceURLLoaderFactory() { @@ -418,6 +429,7 @@ if (MaybeCreateLoaderForResponse(ResourceResponseHead())) return; } + completion_status_ = completion_status; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::BindOnce(&NavigationURLLoaderNetworkService::OnComplete, owner_, @@ -479,6 +491,12 @@ // URLLoaderClient::OnReceivedResponse() is called. bool received_response_ = false; + // The completion status if it has been received. This is needed to handle + // the case that the response is intercepted by download, and OnComplete() is + // already called while we are transferring the |url_loader_| and response + // body to download code. + base::Optional<ResourceRequestCompletionStatus> completion_status_; + DISALLOW_COPY_AND_ASSIGN(URLLoaderRequestController); }; @@ -583,6 +601,15 @@ void NavigationURLLoaderNetworkService::ProceedWithResponse() {} +void NavigationURLLoaderNetworkService::InterceptNavigation( + NavigationURLLoader::NavigationInterceptionCB callback) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&URLLoaderRequestController::InterceptNavigation, + base::Unretained(request_controller_.get()), + std::move(callback))); +} + void NavigationURLLoaderNetworkService::OnReceiveResponse( scoped_refptr<ResourceResponse> response, const base::Optional<net::SSLInfo>& ssl_info,
diff --git a/content/browser/loader/navigation_url_loader_network_service.h b/content/browser/loader/navigation_url_loader_network_service.h index 9e7f5b9..a73b2048 100644 --- a/content/browser/loader/navigation_url_loader_network_service.h +++ b/content/browser/loader/navigation_url_loader_network_service.h
@@ -38,6 +38,7 @@ // NavigationURLLoader implementation: void FollowRedirect() override; void ProceedWithResponse() override; + void InterceptNavigation(NavigationInterceptionCB callback) override; void OnReceiveResponse(scoped_refptr<ResourceResponse> response, const base::Optional<net::SSLInfo>& ssl_info,
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index bcdc434..c384508 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -182,6 +182,7 @@ #include "ipc/ipc_logging.h" #include "media/base/media_switches.h" #include "media/media_features.h" +#include "media/mojo/services/video_decode_perf_history.h" #include "mojo/edk/embedder/embedder.h" #include "mojo/public/cpp/bindings/associated_interface_ptr.h" #include "mojo/public/cpp/bindings/strong_binding.h" @@ -1941,6 +1942,9 @@ storage_partition_impl_->GetBlobRegistry(), GetID())); } + registry->AddInterface( + base::Bind(&media::VideoDecodePerfHistory::BindRequest)); + ServiceManagerConnection* service_manager_connection = BrowserContext::GetServiceManagerConnectionFor(browser_context_); std::unique_ptr<ConnectionFilterImpl> connection_filter(
diff --git a/content/browser/service_worker/service_worker_script_url_loader.cc b/content/browser/service_worker/service_worker_script_url_loader.cc index 865738bc..cde94a4 100644 --- a/content/browser/service_worker/service_worker_script_url_loader.cc +++ b/content/browser/service_worker/service_worker_script_url_loader.cc
@@ -14,6 +14,7 @@ #include "content/browser/service_worker/service_worker_version.h" #include "content/browser/service_worker/service_worker_write_to_cache_job.h" #include "content/browser/url_loader_factory_getter.h" +#include "content/common/service_worker/service_worker_utils.h" #include "content/public/common/resource_response.h" #include "third_party/WebKit/common/mime_util/mime_util.h" @@ -157,9 +158,6 @@ return; } - // TODO(nhiroki): Check the path restriction. - // (See ServiceWorkerWriteToCacheJob::CheckPathRestriction()) - // TODO(nhiroki): Check the SSL certificate. if (resource_type_ == RESOURCE_TYPE_SERVICE_WORKER) { @@ -170,6 +168,23 @@ ResourceRequestCompletionStatus(net::ERR_INSECURE_RESPONSE)); return; } + + // Check the path restriction defined in the spec: + // https://w3c.github.io/ServiceWorker/#service-worker-script-response + const char kServiceWorkerAllowed[] = "Service-Worker-Allowed"; + std::string service_worker_allowed; + bool has_header = response_head.headers->EnumerateHeader( + nullptr, kServiceWorkerAllowed, &service_worker_allowed); + std::string error_message; + if (!ServiceWorkerUtils::IsPathRestrictionSatisfied( + version_->scope(), request_url_, + has_header ? &service_worker_allowed : nullptr, &error_message)) { + // TODO(nhiroki): Report |error_message|. + CommitCompleted( + ResourceRequestCompletionStatus(net::ERR_INSECURE_RESPONSE)); + return; + } + version_->SetMainScriptHttpResponseInfo(*response_info); }
diff --git a/content/browser/service_worker/service_worker_script_url_loader_unittest.cc b/content/browser/service_worker/service_worker_script_url_loader_unittest.cc index 688b2be..d2de0530 100644 --- a/content/browser/service_worker/service_worker_script_url_loader_unittest.cc +++ b/content/browser/service_worker/service_worker_script_url_loader_unittest.cc
@@ -138,7 +138,7 @@ mock_server_->AddResponse(GURL(kNormalScriptURL), std::string("HTTP/1.1 200 OK\n" - "CONTENT-TYPE: text/javascript\n\n"), + "Content-Type: text/javascript\n\n"), std::string("this body came from the network")); // Initialize URLLoaderFactory. @@ -159,8 +159,7 @@ // Sets up ServiceWorkerRegistration and ServiceWorkerVersion. This should be // called before DoRequest(). - void SetUpRegistration(const GURL& script_url) { - GURL scope = script_url; + void SetUpRegistration(const GURL& script_url, const GURL& scope) { registration_ = base::MakeRefCounted<ServiceWorkerRegistration>( blink::mojom::ServiceWorkerRegistrationOptions(scope), 1L, helper_->context()->AsWeakPtr()); @@ -169,6 +168,12 @@ version_->SetStatus(ServiceWorkerVersion::NEW); } + // Sets up ServiceWorkerRegistration and ServiceWorkerVersion with the default + // scope. + void SetUpRegistration(const GURL& script_url) { + SetUpRegistration(script_url, script_url.GetWithoutFilename()); + } + void DoRequest(const GURL& request_url) { DCHECK(registration_); DCHECK(version_); @@ -271,7 +276,7 @@ const GURL kEmptyScriptURL("https://example.com/empty.js"); mock_server_->AddResponse(kEmptyScriptURL, std::string("HTTP/1.1 200 OK\n" - "CONTENT-TYPE: text/javascript\n\n"), + "Content-Type: text/javascript\n\n"), std::string()); SetUpRegistration(kEmptyScriptURL); DoRequest(kEmptyScriptURL); @@ -328,7 +333,7 @@ const GURL kBadMimeTypeScriptURL("https://example.com/bad-mime-type.js"); mock_server_->AddResponse(kBadMimeTypeScriptURL, std::string("HTTP/1.1 200 OK\n" - "CONTENT-TYPE: text/css\n\n"), + "Content-Type: text/css\n\n"), std::string("body with bad MIME type")); SetUpRegistration(kBadMimeTypeScriptURL); DoRequest(kBadMimeTypeScriptURL); @@ -343,6 +348,55 @@ EXPECT_FALSE(VerifyStoredResponse(kBadMimeTypeScriptURL)); } +TEST_F(ServiceWorkerScriptURLLoaderTest, Success_PathRestriction) { + // |kScope| is not under the default scope ("/out-of-scope/"), but the + // Service-Worker-Allowed header allows it. + const GURL kScriptURL("https://example.com/out-of-scope/normal.js"); + const GURL kScope("https://example.com/in-scope/"); + mock_server_->AddResponse( + kScriptURL, + std::string("HTTP/1.1 200 OK\n" + "Content-Type: text/javascript\n" + "Service-Worker-Allowed: /in-scope/\n\n"), + std::string()); + SetUpRegistration(kScriptURL, kScope); + DoRequest(kScriptURL); + client_.RunUntilComplete(); + EXPECT_EQ(net::OK, client_.completion_status().error_code); + + // The client should have received the response. + EXPECT_TRUE(client_.has_received_response()); + EXPECT_TRUE(client_.response_body().is_valid()); + std::string response; + EXPECT_TRUE(mojo::common::BlockingCopyToString( + client_.response_body_release(), &response)); + EXPECT_EQ(mock_server_->GetBody(kScriptURL), response); + + // The response should also be stored in the storage. + EXPECT_TRUE(VerifyStoredResponse(kScriptURL)); +} + +TEST_F(ServiceWorkerScriptURLLoaderTest, Error_PathRestriction) { + // |kScope| is not under the default scope ("/out-of-scope/") and the + // Service-Worker-Allowed header is not specified. + const GURL kScriptURL("https://example.com/out-of-scope/normal.js"); + const GURL kScope("https://example.com/in-scope/"); + mock_server_->AddResponse(kScriptURL, + std::string("HTTP/1.1 200 OK\n" + "Content-Type: text/javascript\n\n"), + std::string()); + SetUpRegistration(kScriptURL, kScope); + DoRequest(kScriptURL); + client_.RunUntilComplete(); + + // The request should be failed because the scope is not allowed. + EXPECT_EQ(net::ERR_INSECURE_RESPONSE, client_.completion_status().error_code); + EXPECT_FALSE(client_.has_received_response()); + + // The response shouldn't be stored in the storage. + EXPECT_FALSE(VerifyStoredResponse(kScriptURL)); +} + TEST_F(ServiceWorkerScriptURLLoaderTest, Error_RedundantWorker) { GURL script_url(kNormalScriptURL); SetUpRegistration(script_url);
diff --git a/content/common/throttling_url_loader.h b/content/common/throttling_url_loader.h index ec666656..e5711b9 100644 --- a/content/common/throttling_url_loader.h +++ b/content/common/throttling_url_loader.h
@@ -73,6 +73,11 @@ // Disconnects the client connection and releases the URLLoader. void DisconnectClient(); + // Sets the forwarding client to receive all subsequent notifications. + void set_forwarding_client(mojom::URLLoaderClient* client) { + forwarding_client_ = client; + } + private: class ForwardingThrottleDelegate;
diff --git a/content/public/app/mojo/content_browser_manifest.json b/content/public/app/mojo/content_browser_manifest.json index aa4ea8c9..83093cd4 100644 --- a/content/public/app/mojo/content_browser_manifest.json +++ b/content/public/app/mojo/content_browser_manifest.json
@@ -42,6 +42,7 @@ "device::mojom::BatteryMonitor", "device::mojom::GamepadMonitor", "discardable_memory::mojom::DiscardableSharedMemoryManager", + "media::mojom::VideoDecodePerfHistory", "memory_coordinator::mojom::MemoryCoordinatorHandle", "metrics::mojom::SingleSampleMetricsProvider", "payments::mojom::PaymentManager",
diff --git a/content/test/data/accessibility/html/offscreen-expected-blink.txt b/content/test/data/accessibility/html/offscreen-expected-blink.txt new file mode 100644 index 0000000..a372eb4 --- /dev/null +++ b/content/test/data/accessibility/html/offscreen-expected-blink.txt
@@ -0,0 +1,4 @@ +rootWebArea +++button name='Onscreen' +++button offscreen name='Offscreen' +<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/offscreen-scroll-expected-blink.txt b/content/test/data/accessibility/html/offscreen-scroll-expected-blink.txt new file mode 100644 index 0000000..1251327b --- /dev/null +++ b/content/test/data/accessibility/html/offscreen-scroll-expected-blink.txt
@@ -0,0 +1,4 @@ +rootWebArea scrollY=100 +++button offscreen name='Onscreen before scroll' +++button name='Scrolled Button' +<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/offscreen-scroll.html b/content/test/data/accessibility/html/offscreen-scroll.html new file mode 100644 index 0000000..76ef9fd --- /dev/null +++ b/content/test/data/accessibility/html/offscreen-scroll.html
@@ -0,0 +1,23 @@ +<!-- +@BLINK-ALLOW:scrollY=* +@BLINK-ALLOW:offscreen +@WAIT-FOR:Scrolled Button +--> + +<html> +<head> + <script> + window.onload = function(unused) { + setTimeout(function() { + window.scrollTo(0, 100); + document.getElementById('second').setAttribute('aria-label','Scrolled Button'); + }, 100); + }; + </script> +</head> +<body> + <button>Onscreen before scroll</button> + <div style="height:650px"></div> + <button id="second">Offscreen before scroll</button> +</body> +</html>
diff --git a/content/test/data/accessibility/html/offscreen.html b/content/test/data/accessibility/html/offscreen.html new file mode 100644 index 0000000..3d843215 --- /dev/null +++ b/content/test/data/accessibility/html/offscreen.html
@@ -0,0 +1,11 @@ +<!-- +@BLINK-ALLOW:offscreen +--> + +<html> +<body> +<button>Onscreen</button> +<div style="height:650px"></div> +<button>Offscreen</button> +</body> +</html>
diff --git a/content/test/test_navigation_url_loader.cc b/content/test/test_navigation_url_loader.cc index 367e0f90..f481a2f 100644 --- a/content/test/test_navigation_url_loader.cc +++ b/content/test/test_navigation_url_loader.cc
@@ -38,6 +38,9 @@ response_proceeded_ = true; } +void TestNavigationURLLoader::InterceptNavigation( + NavigationURLLoader::NavigationInterceptionCB callback) {} + void TestNavigationURLLoader::SimulateServerRedirect(const GURL& redirect_url) { net::RedirectInfo redirect_info; redirect_info.status_code = 302;
diff --git a/content/test/test_navigation_url_loader.h b/content/test/test_navigation_url_loader.h index 8edcdfc..741e1054 100644 --- a/content/test/test_navigation_url_loader.h +++ b/content/test/test_navigation_url_loader.h
@@ -36,6 +36,7 @@ // NavigationURLLoader implementation. void FollowRedirect() override; void ProceedWithResponse() override; + void InterceptNavigation(NavigationInterceptionCB callback) override; NavigationRequestInfo* request_info() const { return request_info_.get(); }
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc index 9078d58..5a56fe8 100644 --- a/extensions/browser/api/web_request/web_request_api.cc +++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -650,8 +650,8 @@ event_details->SetRequestBody(request); initialize_blocked_requests |= - DispatchEvent(browser_context, request, listeners, navigation_ui_data, - std::move(event_details)); + DispatchEvent(browser_context, extension_info_map, request, listeners, + navigation_ui_data, std::move(event_details)); } if (!initialize_blocked_requests) @@ -704,8 +704,8 @@ event_details->SetRequestHeaders(*headers); initialize_blocked_requests |= - DispatchEvent(browser_context, request, listeners, navigation_ui_data, - std::move(event_details)); + DispatchEvent(browser_context, extension_info_map, request, listeners, + navigation_ui_data, std::move(event_details)); } if (!initialize_blocked_requests) @@ -755,8 +755,8 @@ CreateEventDetails(request, extra_info_spec)); event_details->SetRequestHeaders(headers); - DispatchEvent(browser_context, request, listeners, navigation_ui_data, - std::move(event_details)); + DispatchEvent(browser_context, extension_info_map, request, listeners, + navigation_ui_data, std::move(event_details)); } int ExtensionWebRequestEventRouter::OnHeadersReceived( @@ -793,8 +793,8 @@ event_details->SetResponseHeaders(request, original_response_headers); initialize_blocked_requests |= - DispatchEvent(browser_context, request, listeners, navigation_ui_data, - std::move(event_details)); + DispatchEvent(browser_context, extension_info_map, request, listeners, + navigation_ui_data, std::move(event_details)); } if (!initialize_blocked_requests) @@ -849,8 +849,8 @@ event_details->SetResponseHeaders(request, request->response_headers()); event_details->SetAuthInfo(auth_info); - if (DispatchEvent(browser_context, request, listeners, navigation_ui_data, - std::move(event_details))) { + if (DispatchEvent(browser_context, extension_info_map, request, listeners, + navigation_ui_data, std::move(event_details))) { BlockedRequest& blocked_request = blocked_requests_[request->identifier()]; blocked_request.event = kOnAuthRequired; blocked_request.is_incognito |= IsIncognitoBrowserContext(browser_context); @@ -896,8 +896,8 @@ event_details->SetResponseSource(request); event_details->SetString(keys::kRedirectUrlKey, new_location.spec()); - DispatchEvent(browser_context, request, listeners, navigation_ui_data, - std::move(event_details)); + DispatchEvent(browser_context, extension_info_map, request, listeners, + navigation_ui_data, std::move(event_details)); } void ExtensionWebRequestEventRouter::OnResponseStarted( @@ -930,8 +930,8 @@ event_details->SetResponseHeaders(request, request->response_headers()); event_details->SetResponseSource(request); - DispatchEvent(browser_context, request, listeners, navigation_ui_data, - std::move(event_details)); + DispatchEvent(browser_context, extension_info_map, request, listeners, + navigation_ui_data, std::move(event_details)); } // Deprecated. @@ -984,8 +984,8 @@ event_details->SetResponseHeaders(request, request->response_headers()); event_details->SetResponseSource(request); - DispatchEvent(browser_context, request, listeners, navigation_ui_data, - std::move(event_details)); + DispatchEvent(browser_context, extension_info_map, request, listeners, + navigation_ui_data, std::move(event_details)); } // Deprecated. @@ -1055,8 +1055,8 @@ event_details->SetBoolean(keys::kFromCache, request->was_cached()); event_details->SetString(keys::kErrorKey, net::ErrorToString(net_error)); - DispatchEvent(browser_context, request, listeners, navigation_ui_data, - std::move(event_details)); + DispatchEvent(browser_context, extension_info_map, request, listeners, + navigation_ui_data, std::move(event_details)); } void ExtensionWebRequestEventRouter::OnErrorOccurred( @@ -1086,6 +1086,7 @@ bool ExtensionWebRequestEventRouter::DispatchEvent( void* browser_context, + const InfoMap* extension_info_map, net::URLRequest* request, const RawListeners& listeners, ExtensionNavigationUIData* navigation_ui_data, @@ -1130,7 +1131,8 @@ IsResourceTypeFrame(info->GetResourceType())) { DCHECK(navigation_ui_data); event_details->SetFrameData(navigation_ui_data->frame_data()); - DispatchEventToListeners(browser_context, std::move(listeners_to_dispatch), + DispatchEventToListeners(browser_context, extension_info_map, + std::move(listeners_to_dispatch), std::move(event_details)); } else { // This Unretained is safe because the ExtensionWebRequestEventRouter @@ -1138,6 +1140,7 @@ event_details.release()->DetermineFrameDataOnIO( base::Bind(&ExtensionWebRequestEventRouter::DispatchEventToListeners, base::Unretained(this), browser_context, + base::RetainedRef(extension_info_map), base::Passed(&listeners_to_dispatch))); } @@ -1155,6 +1158,7 @@ void ExtensionWebRequestEventRouter::DispatchEventToListeners( void* browser_context, + const InfoMap* extension_info_map, std::unique_ptr<ListenerIDs> listener_ids, std::unique_ptr<WebRequestEventDetails> event_details) { DCHECK_CURRENTLY_ON(BrowserThread::IO); @@ -1194,8 +1198,11 @@ // Filter out the optional keys that this listener didn't request. std::unique_ptr<base::ListValue> args_filtered(new base::ListValue); - args_filtered->Append( - event_details->GetFilteredDict(listener->extra_info_spec)); + void* cross_browser_context = GetCrossBrowserContext(browser_context); + + args_filtered->Append(event_details->GetFilteredDict( + listener->extra_info_spec, extension_info_map, + listener->id.extension_id, (cross_browser_context != 0))); EventRouter::DispatchEventToSender( listener->ipc_sender.get(), browser_context, listener->id.extension_id,
diff --git a/extensions/browser/api/web_request/web_request_api.h b/extensions/browser/api/web_request/web_request_api.h index 2abfae7..b493c71 100644 --- a/extensions/browser/api/web_request/web_request_api.h +++ b/extensions/browser/api/web_request/web_request_api.h
@@ -405,6 +405,7 @@ void ClearPendingCallbacks(const net::URLRequest* request); bool DispatchEvent(void* browser_context, + const InfoMap* extension_info_map, net::URLRequest* request, const RawListeners& listener_ids, ExtensionNavigationUIData* navigation_ui_data, @@ -412,6 +413,7 @@ void DispatchEventToListeners( void* browser_context, + const InfoMap* extension_info_map, std::unique_ptr<ListenerIDs> listener_ids, std::unique_ptr<WebRequestEventDetails> event_details);
diff --git a/extensions/browser/api/web_request/web_request_api_constants.cc b/extensions/browser/api/web_request/web_request_api_constants.cc index 7b464ee..50b0688 100644 --- a/extensions/browser/api/web_request/web_request_api_constants.cc +++ b/extensions/browser/api/web_request/web_request_api_constants.cc
@@ -43,6 +43,7 @@ const char kAuthCredentialsKey[] = "authCredentials"; const char kUsernameKey[] = "username"; const char kPasswordKey[] = "password"; +const char kInitiatorKey[] = "initiator"; const char kOnBeforeRedirectEvent[] = "webRequest.onBeforeRedirect"; const char kOnBeforeSendHeadersEvent[] = "webRequest.onBeforeSendHeaders";
diff --git a/extensions/browser/api/web_request/web_request_api_constants.h b/extensions/browser/api/web_request/web_request_api_constants.h index 4f89dad..cc33f1c 100644 --- a/extensions/browser/api/web_request/web_request_api_constants.h +++ b/extensions/browser/api/web_request/web_request_api_constants.h
@@ -50,6 +50,7 @@ extern const char kAuthCredentialsKey[]; extern const char kUsernameKey[]; extern const char kPasswordKey[]; +extern const char kInitiatorKey[]; // Events. extern const char kOnAuthRequiredEvent[];
diff --git a/extensions/browser/api/web_request/web_request_event_details.cc b/extensions/browser/api/web_request/web_request_event_details.cc index c89edeb..3ba38ec6 100644 --- a/extensions/browser/api/web_request/web_request_event_details.cc +++ b/extensions/browser/api/web_request/web_request_event_details.cc
@@ -19,7 +19,9 @@ #include "extensions/browser/api/web_request/upload_data_presenter.h" #include "extensions/browser/api/web_request/web_request_api_constants.h" #include "extensions/browser/api/web_request/web_request_api_helpers.h" +#include "extensions/browser/api/web_request/web_request_permissions.h" #include "extensions/browser/api/web_request/web_request_resource_type.h" +#include "extensions/common/permissions/permissions_data.h" #include "ipc/ipc_message.h" #include "net/base/auth.h" #include "net/base/upload_data_stream.h" @@ -68,6 +70,8 @@ dict_.SetString(keys::kTypeKey, WebRequestResourceTypeToString(resource_type)); dict_.SetString(keys::kUrlKey, request->url().spec()); + if (request->initiator()) + initiator_ = request->initiator(); } WebRequestEventDetails::~WebRequestEventDetails() {} @@ -201,7 +205,10 @@ } std::unique_ptr<base::DictionaryValue> WebRequestEventDetails::GetFilteredDict( - int extra_info_spec) const { + int extra_info_spec, + const extensions::InfoMap* extension_info_map, + const extensions::ExtensionId& extension_id, + bool crosses_incognito) const { std::unique_ptr<base::DictionaryValue> result = dict_.CreateDeepCopy(); if ((extra_info_spec & ExtraInfoSpec::REQUEST_BODY) && request_body_) { result->SetKey(keys::kRequestBodyKey, request_body_->Clone()); @@ -213,6 +220,17 @@ response_headers_) { result->SetKey(keys::kResponseHeadersKey, response_headers_->Clone()); } + + // Only listeners with a permission for the initiator should recieve it. + if (extension_info_map && initiator_) { + int tab_id = -1; + dict_.GetInteger(keys::kTabIdKey, &tab_id); + if (WebRequestPermissions::CanExtensionAccessInitiator( + extension_info_map, extension_id, initiator_, tab_id, + crosses_incognito)) { + result->SetString(keys::kInitiatorKey, initiator_->Serialize()); + } + } return result; }
diff --git a/extensions/browser/api/web_request/web_request_event_details.h b/extensions/browser/api/web_request/web_request_event_details.h index 4172675..3c4661c 100644 --- a/extensions/browser/api/web_request/web_request_event_details.h +++ b/extensions/browser/api/web_request/web_request_event_details.h
@@ -11,8 +11,11 @@ #include "base/callback_forward.h" #include "base/gtest_prod_util.h" #include "base/macros.h" +#include "base/optional.h" #include "base/values.h" #include "extensions/browser/extension_api_frame_id_map.h" +#include "extensions/common/extension_id.h" +#include "url/origin.h" namespace net { class AuthChallengeInfo; @@ -23,6 +26,8 @@ namespace extensions { +class InfoMap; + // This helper class is used to construct the details for a webRequest event // dictionary. Some keys are present on every event, others are only relevant // for a few events. And some keys must only be added to the event details if @@ -118,10 +123,15 @@ void DetermineFrameDataOnIO(const DeterminedFrameDataCallback& callback); // Create an event dictionary that contains all required keys, and also the - // extra keys as specified by the |extra_info_spec| filter. + // extra keys as specified by the |extra_info_spec| filter. If the listener + // this event will be dispatched to doesn't have permission for the initiator + // then the initiator will not be populated. // This can be called from any thread. std::unique_ptr<base::DictionaryValue> GetFilteredDict( - int extra_info_spec) const; + int extra_info_spec, + const InfoMap* extension_info_map, + const ExtensionId& extension_id, + bool crosses_incognito) const; // Get the internal dictionary, unfiltered. After this call, the internal // dictionary is empty. @@ -149,6 +159,7 @@ std::unique_ptr<base::DictionaryValue> request_body_; std::unique_ptr<base::ListValue> request_headers_; std::unique_ptr<base::ListValue> response_headers_; + base::Optional<url::Origin> initiator_; int extra_info_spec_;
diff --git a/extensions/browser/api/web_request/web_request_permissions.cc b/extensions/browser/api/web_request/web_request_permissions.cc index ccfe22ba..0a1ddf5 100644 --- a/extensions/browser/api/web_request/web_request_permissions.cc +++ b/extensions/browser/api/web_request/web_request_permissions.cc
@@ -10,6 +10,7 @@ #include "chromeos/login/login_state.h" #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/resource_request_info.h" +#include "extensions/browser/api/web_request/web_request_api_constants.h" #include "extensions/browser/extension_navigation_ui_data.h" #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h" #include "extensions/browser/info_map.h" @@ -211,3 +212,20 @@ return access; } + +// static +bool WebRequestPermissions::CanExtensionAccessInitiator( + const extensions::InfoMap* extension_info_map, + const extensions::ExtensionId extension_id, + const base::Optional<url::Origin>& initiator, + int tab_id, + bool crosses_incognito) { + PermissionsData::AccessType access = PermissionsData::ACCESS_ALLOWED; + if (initiator) { + access = CanExtensionAccessURL( + extension_info_map, extension_id, initiator->GetURL(), tab_id, + crosses_incognito, WebRequestPermissions::REQUIRE_HOST_PERMISSION, + base::nullopt); + } + return access == PermissionsData::ACCESS_ALLOWED; +}
diff --git a/extensions/browser/api/web_request/web_request_permissions.h b/extensions/browser/api/web_request/web_request_permissions.h index 74d9475..2d4c9d4 100644 --- a/extensions/browser/api/web_request/web_request_permissions.h +++ b/extensions/browser/api/web_request/web_request_permissions.h
@@ -59,6 +59,13 @@ HostPermissionsCheck host_permissions_check, const base::Optional<url::Origin>& initiator); + static bool CanExtensionAccessInitiator( + const extensions::InfoMap* extension_info_map, + const extensions::ExtensionId extension_id, + const base::Optional<url::Origin>& initiator, + int tab_id, + bool crosses_incognito); + private: DISALLOW_IMPLICIT_CONSTRUCTORS(WebRequestPermissions); };
diff --git a/extensions/common/api/networking_onc.idl b/extensions/common/api/networking_onc.idl index 58bf8289..c51bbcc 100644 --- a/extensions/common/api/networking_onc.idl +++ b/extensions/common/api/networking_onc.idl
@@ -48,7 +48,7 @@ }; enum NetworkType { - All, Cellular, Ethernet, VPN, Wireless, WiFi, WiMAX + All, Cellular, Ethernet, Tether, VPN, Wireless, WiFi, WiMAX }; enum ProxySettingsType { @@ -559,7 +559,7 @@ boolean? AutoConnect; // The VPN host. DOMString? Host; - // The VPN type. + // The VPN type. This can not be an enum because of 'L2TP-IPSec'. DOMString? Type; };
diff --git a/extensions/common/api/networking_private.idl b/extensions/common/api/networking_private.idl index eb032487..63c6da2 100644 --- a/extensions/common/api/networking_private.idl +++ b/extensions/common/api/networking_private.idl
@@ -600,6 +600,7 @@ L2TPProperties? L2TP; OpenVPNProperties? OpenVPN; ThirdPartyVPNProperties? ThirdPartyVPN; + // 'Type' can not be an enum because of 'L2TP-IPSec'. DOMString? Type; };
diff --git a/extensions/common/api/test.json b/extensions/common/api/test.json index 97dff284..94bddaf 100644 --- a/extensions/common/api/test.json +++ b/extensions/common/api/test.json
@@ -60,6 +60,11 @@ "description": "The port on which the test WebSocket server is listening.", "minimum": 0, "maximum": 65535 + }, + "browserSideNavigationEnabled": { + "type": "boolean", + "optional": true, + "description": "Whether navigation requets are initiated by the browser or renderer (PlzNavigate)." } } }
diff --git a/extensions/common/api/web_request.json b/extensions/common/api/web_request.json index 110a6d3..9b21cb6 100644 --- a/extensions/common/api/web_request.json +++ b/extensions/common/api/web_request.json
@@ -204,6 +204,7 @@ }, "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "initiator": {"type": "string", "optional": true, "description": "The origin where the request was initiated. This does not change through redirects."}, "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."} } } @@ -246,6 +247,7 @@ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."}, "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, + "initiator": {"type": "string", "optional": true, "description": "The origin where the request was initiated. This does not change through redirects."}, "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that are going to be sent out with this request."} @@ -291,6 +293,7 @@ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "initiator": {"type": "string", "optional": true, "description": "The origin where the request was initiated. This does not change through redirects."}, "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that have been sent out with this request."} } @@ -330,6 +333,7 @@ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "initiator": {"type": "string", "optional": true, "description": "The origin where the request was initiated. This does not change through redirects."}, "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line)."}, "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that have been received with this response."}, @@ -376,6 +380,7 @@ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "initiator": {"type": "string", "optional": true, "description": "The origin where the request was initiated. This does not change through redirects."}, "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, "scheme": {"type": "string", "description": "The authentication scheme, e.g. Basic or Digest."}, "realm": {"type": "string", "description": "The authentication realm provided by the server, if there is one.", "optional": true}, @@ -435,6 +440,7 @@ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "initiator": {"type": "string", "optional": true, "description": "The origin where the request was initiated. This does not change through redirects."}, "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."}, "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."}, @@ -478,6 +484,7 @@ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "initiator": {"type": "string", "optional": true, "description": "The origin where the request was initiated. This does not change through redirects."}, "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."}, "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."}, @@ -522,6 +529,7 @@ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "initiator": {"type": "string", "optional": true, "description": "The origin where the request was initiated. This does not change through redirects."}, "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."}, "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."}, @@ -564,6 +572,7 @@ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."}, "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."}, "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."}, + "initiator": {"type": "string", "optional": true, "description": "The origin where the request was initiated. This does not change through redirects."}, "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}, "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."}, "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
diff --git a/ipc/ipc_channel_proxy.cc b/ipc/ipc_channel_proxy.cc index 49bfdbf..872a518 100644 --- a/ipc/ipc_channel_proxy.cc +++ b/ipc/ipc_channel_proxy.cc
@@ -24,16 +24,6 @@ #include "ipc/message_filter.h" #include "ipc/message_filter_router.h" -// This is temporary to try to locate a core trampler. -// TODO(bcwhite): Remove when crbug/736675 is resolved. -#if defined(OS_ANDROID) -#include "base/metrics/statistics_recorder.h" -#define VALIDATE_ALL_HISTOGRAMS(x) \ - base::StatisticsRecorder::ValidateAllHistograms(static_cast<int>(x)) -#else -#define VALIDATE_ALL_HISTOGRAMS(x) -#endif - namespace IPC { //------------------------------------------------------------------------------ @@ -314,8 +304,6 @@ if (!listener_) return; - VALIDATE_ALL_HISTOGRAMS(message.type()); - OnDispatchConnected(); #if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED) @@ -329,11 +317,9 @@ logger->OnPreDispatchMessage(message); #endif - VALIDATE_ALL_HISTOGRAMS(message.type()); listener_->OnMessageReceived(message); if (message.dispatch_error()) listener_->OnBadMessageReceived(message); - VALIDATE_ALL_HISTOGRAMS(message.type()); #if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED) if (logger->Enabled())
diff --git a/media/blink/BUILD.gn b/media/blink/BUILD.gn index b521394..041d988e 100644 --- a/media/blink/BUILD.gn +++ b/media/blink/BUILD.gn
@@ -80,6 +80,7 @@ "//media:shared_memory_support", "//media/mojo/interfaces", "//net", + "//services/service_manager/public/cpp:cpp", "//skia", "//third_party/WebKit/public:blink", "//ui/gfx",
diff --git a/media/blink/DEPS b/media/blink/DEPS index 0fb853c9..96eff59 100644 --- a/media/blink/DEPS +++ b/media/blink/DEPS
@@ -10,6 +10,7 @@ "+mojo/public/cpp/bindings", "+net/base", "+net/http", + "+services/service_manager/public/cpp", "+third_party/WebKit/public/platform", "+third_party/WebKit/public/web",
diff --git a/media/blink/webmediacapabilitiesclient_impl.cc b/media/blink/webmediacapabilitiesclient_impl.cc index d973d2f..21eec988 100644 --- a/media/blink/webmediacapabilitiesclient_impl.cc +++ b/media/blink/webmediacapabilitiesclient_impl.cc
@@ -4,11 +4,15 @@ #include "media/blink/webmediacapabilitiesclient_impl.h" +#include "base/bind_helpers.h" #include "media/base/audio_codecs.h" #include "media/base/decode_capabilities.h" #include "media/base/mime_util.h" #include "media/base/video_codecs.h" #include "media/base/video_color_space.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "services/service_manager/public/cpp/connector.h" +#include "third_party/WebKit/public/platform/Platform.h" #include "third_party/WebKit/public/platform/modules/media_capabilities/WebAudioConfiguration.h" #include "third_party/WebKit/public/platform/modules/media_capabilities/WebMediaCapabilitiesInfo.h" #include "third_party/WebKit/public/platform/modules/media_capabilities/WebMediaConfiguration.h" @@ -16,19 +20,38 @@ namespace media { +void BindToHistoryService(mojom::VideoDecodePerfHistoryPtr* history_ptr) { + blink::Platform* platform = blink::Platform::Current(); + service_manager::Connector* connector = platform->GetConnector(); + + connector->BindInterface(platform->GetBrowserServiceName(), + mojo::MakeRequest(history_ptr)); +} + WebMediaCapabilitiesClientImpl::WebMediaCapabilitiesClientImpl() = default; WebMediaCapabilitiesClientImpl::~WebMediaCapabilitiesClientImpl() = default; +void VideoPerfInfoCallback( + std::unique_ptr<blink::WebMediaCapabilitiesQueryCallbacks> callbacks, + std::unique_ptr<blink::WebMediaCapabilitiesInfo> info, + bool is_smooth, + bool is_power_efficient) { + DCHECK(info->supported); + info->smooth = is_smooth; + info->power_efficient = is_power_efficient; + callbacks->OnSuccess(std::move(info)); +} + void WebMediaCapabilitiesClientImpl::DecodingInfo( const blink::WebMediaConfiguration& configuration, std::unique_ptr<blink::WebMediaCapabilitiesQueryCallbacks> callbacks) { std::unique_ptr<blink::WebMediaCapabilitiesInfo> info( new blink::WebMediaCapabilitiesInfo()); - bool audio_supported = true; - bool video_supported = true; + // TODO(chcunningham): split this up with helper methods. + bool audio_supported = true; if (configuration.audio_configuration) { const blink::WebAudioConfiguration& audio_config = configuration.audio_configuration.value(); @@ -40,8 +63,9 @@ &is_audio_codec_ambiguous, &audio_codec)) { // TODO(chcunningham): Replace this and other DVLOGs here with MEDIA_LOG. // MediaCapabilities may need its own tab in chrome://media-internals. - DVLOG(2) << __func__ << " Failed to parse audio codec string:" - << audio_config.codec.Ascii(); + DVLOG(2) << __func__ << " Failed to parse audio contentType: " + << audio_config.mime_type.Ascii() + << "; codecs=" << audio_config.codec.Ascii(); audio_supported = false; } else if (is_audio_codec_ambiguous) { DVLOG(2) << __func__ << " Invalid (ambiguous) audio codec string:" @@ -53,39 +77,65 @@ } } - if (configuration.video_configuration) { - const blink::WebVideoConfiguration& video_config = - configuration.video_configuration.value(); - VideoCodec video_codec; - VideoCodecProfile video_profile; - uint8_t video_level; - VideoColorSpace video_color_space; - bool is_video_codec_ambiguous; - - if (!ParseVideoCodecString( - video_config.mime_type.Ascii(), video_config.codec.Ascii(), - &is_video_codec_ambiguous, &video_codec, &video_profile, - &video_level, &video_color_space)) { - DVLOG(2) << __func__ << " Failed to parse video codec string:" - << video_config.codec.Ascii(); - video_supported = false; - } else if (is_video_codec_ambiguous) { - DVLOG(2) << __func__ << " Invalid (ambiguous) video codec string:" - << video_config.codec.Ascii(); - video_supported = false; - } else { - VideoConfig video_config = {video_codec, video_profile, video_level, - video_color_space}; - video_supported = IsSupportedVideoConfig(video_config); - } + // No need to check video capabilities if video not included in configuration + // or when audio is already known to be unsupported. + if (!audio_supported || !configuration.video_configuration) { + // Supported audio-only configurations are always considered smooth and + // power efficient. + info->supported = info->smooth = info->power_efficient = audio_supported; + callbacks->OnSuccess(std::move(info)); + return; } - info->supported = audio_supported && video_supported; + // Audio is supported and video configuration is provided in the query; all + // that remains is to check video support and performance. + bool video_supported; + DCHECK(audio_supported); + DCHECK(configuration.video_configuration); + const blink::WebVideoConfiguration& video_config = + configuration.video_configuration.value(); + VideoCodec video_codec; + VideoCodecProfile video_profile; + uint8_t video_level; + VideoColorSpace video_color_space; + bool is_video_codec_ambiguous; - // TODO(chcunningham, mlamouri): real implementation for these. - info->smooth = info->power_efficient = info->supported; + if (!ParseVideoCodecString( + video_config.mime_type.Ascii(), video_config.codec.Ascii(), + &is_video_codec_ambiguous, &video_codec, &video_profile, &video_level, + &video_color_space)) { + DVLOG(2) << __func__ << " Failed to parse video contentType: " + << video_config.mime_type.Ascii() + << "; codecs=" << video_config.codec.Ascii(); + video_supported = false; + } else if (is_video_codec_ambiguous) { + DVLOG(2) << __func__ << " Invalid (ambiguous) video codec string:" + << video_config.codec.Ascii(); + video_supported = false; + } else { + video_supported = IsSupportedVideoConfig( + {video_codec, video_profile, video_level, video_color_space}); + } - callbacks->OnSuccess(std::move(info)); + // Return early for unsupported configurations. + if (!video_supported) { + info->supported = info->smooth = info->power_efficient = video_supported; + callbacks->OnSuccess(std::move(info)); + return; + } + + // Video is supported! Check its performance history. + info->supported = true; + + if (!decode_history_ptr_.is_bound()) + BindToHistoryService(&decode_history_ptr_); + DCHECK(decode_history_ptr_.is_bound()); + + decode_history_ptr_->GetPerfInfo( + video_profile, gfx::Size(video_config.width, video_config.height), + video_config.framerate, + base::BindOnce(&VideoPerfInfoCallback, std::move(callbacks), + std::move(info))); } } // namespace media
diff --git a/media/blink/webmediacapabilitiesclient_impl.h b/media/blink/webmediacapabilitiesclient_impl.h index 97d3c0f..18ebc21 100644 --- a/media/blink/webmediacapabilitiesclient_impl.h +++ b/media/blink/webmediacapabilitiesclient_impl.h
@@ -8,6 +8,7 @@ #include "base/compiler_specific.h" #include "base/macros.h" #include "media/blink/media_blink_export.h" +#include "media/mojo/interfaces/video_decode_perf_history.mojom.h" #include "third_party/WebKit/public/platform/modules/media_capabilities/WebMediaCapabilitiesClient.h" namespace media { @@ -24,6 +25,8 @@ std::unique_ptr<blink::WebMediaCapabilitiesQueryCallbacks>) override; private: + mojom::VideoDecodePerfHistoryPtr decode_history_ptr_; + DISALLOW_COPY_AND_ASSIGN(WebMediaCapabilitiesClientImpl); };
diff --git a/media/mojo/BUILD.gn b/media/mojo/BUILD.gn index 5d43865..92f91c6d 100644 --- a/media/mojo/BUILD.gn +++ b/media/mojo/BUILD.gn
@@ -87,6 +87,7 @@ "interfaces/video_frame_struct_traits_unittest.cc", "mojo_video_encode_accelerator_integration_test.cc", "services/mojo_audio_output_stream_unittest.cc", + "services/video_decode_perf_history_unittest.cc", "services/watch_time_recorder_unittest.cc", ]
diff --git a/media/mojo/interfaces/BUILD.gn b/media/mojo/interfaces/BUILD.gn index 51e8c99..c22a23e 100644 --- a/media/mojo/interfaces/BUILD.gn +++ b/media/mojo/interfaces/BUILD.gn
@@ -20,6 +20,7 @@ "platform_verification.mojom", "provision_fetcher.mojom", "renderer.mojom", + "video_decode_perf_history.mojom", "video_decode_stats_recorder.mojom", "video_decoder.mojom", "video_encode_accelerator.mojom",
diff --git a/media/mojo/interfaces/video_decode_perf_history.mojom b/media/mojo/interfaces/video_decode_perf_history.mojom new file mode 100644 index 0000000..9ee5747 --- /dev/null +++ b/media/mojo/interfaces/video_decode_perf_history.mojom
@@ -0,0 +1,17 @@ +// Copyright 2017 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. + +module media.mojom; + +import "media/mojo/interfaces/media_types.mojom"; +import "ui/gfx/geometry/mojo/geometry.mojom"; + +// This service will query the history of playback stats to evaluate how +// a video stream with the given configuration will perform. +interface VideoDecodePerfHistory { + // Parameters describe the stream characteristics for which we will return + // performance info. + GetPerfInfo(VideoCodecProfile profile, gfx.mojom.Size video_size, + int32 frames_per_sec) => (bool is_smooth, bool is_power_efficient); +}; \ No newline at end of file
diff --git a/media/mojo/services/BUILD.gn b/media/mojo/services/BUILD.gn index 13006ed..533ac35 100644 --- a/media/mojo/services/BUILD.gn +++ b/media/mojo/services/BUILD.gn
@@ -16,6 +16,8 @@ "gpu_mojo_media_client.h", "interface_factory_impl.cc", "interface_factory_impl.h", + "media_capabilities_database.cc", + "media_capabilities_database.h", "media_interface_provider.cc", "media_interface_provider.h", "media_mojo_export.h", @@ -57,6 +59,8 @@ "mojo_video_encode_accelerator_service.h", "test_mojo_media_client.cc", "test_mojo_media_client.h", + "video_decode_perf_history.cc", + "video_decode_perf_history.h", "video_decode_stats_recorder.cc", "video_decode_stats_recorder.h", "watch_time_recorder.cc",
diff --git a/media/mojo/services/media_capabilities_database.cc b/media/mojo/services/media_capabilities_database.cc new file mode 100644 index 0000000..fd8e57e --- /dev/null +++ b/media/mojo/services/media_capabilities_database.cc
@@ -0,0 +1,18 @@ +// Copyright 2017 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 "media/mojo/services/media_capabilities_database.h" + +namespace media { + +MediaCapabilitiesDatabase::Entry::Entry(VideoCodecProfile codec_profile, + const gfx::Size& size, + int frame_rate) + : codec_profile_(codec_profile), size_(size), frame_rate_(frame_rate) {} + +MediaCapabilitiesDatabase::Info::Info(uint32_t frames_decoded, + uint32_t frames_dropped) + : frames_decoded(frames_decoded), frames_dropped(frames_dropped) {} + +} // namespace media \ No newline at end of file
diff --git a/media/mojo/services/media_capabilities_database.h b/media/mojo/services/media_capabilities_database.h new file mode 100644 index 0000000..6d43e78 --- /dev/null +++ b/media/mojo/services/media_capabilities_database.h
@@ -0,0 +1,64 @@ +// Copyright 2017 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 MEDIA_MOJO_SERVICES_MEDIA_CAPABILITIES_DATABASE_H_ +#define MEDIA_MOJO_SERVICES_MEDIA_CAPABILITIES_DATABASE_H_ + +#include <memory> + +#include "base/callback.h" +#include "base/macros.h" +#include "media/base/video_codecs.h" +#include "media/mojo/services/media_mojo_export.h" +#include "ui/gfx/geometry/size.h" + +namespace media { + +// This defines the interface to be used by various media capabilities services +// to store/retrieve decoding performance statistics. +class MEDIA_MOJO_EXPORT MediaCapabilitiesDatabase { + public: + // Representation of the information used to identify a type of media + // playback. + class MEDIA_MOJO_EXPORT Entry { + public: + Entry(VideoCodecProfile codec_profile, + const gfx::Size& size, + int frame_rate); + + VideoCodecProfile codec_profile() const { return codec_profile_; } + const gfx::Size& size() const { return size_; } + int frame_rate() const { return frame_rate_; } + + private: + VideoCodecProfile codec_profile_; + gfx::Size size_; + int frame_rate_; + }; + + // Information saves to identify the capabilities related to a given |Entry|. + struct MEDIA_MOJO_EXPORT Info { + Info(uint32_t frames_decoded, uint32_t frames_dropped); + uint32_t frames_decoded; + uint32_t frames_dropped; + }; + + virtual ~MediaCapabilitiesDatabase() {} + + // Adds `info` data into the database records associated with `entry`. The + // operation is asynchronous. The caller should be aware of potential race + // conditions when calling this method for the same `entry` very close to + // each others. + virtual void AppendInfoToEntry(const Entry& entry, const Info& info) = 0; + + // Returns the `info` associated with `entry`. The `callback` will received + // the `info` in addition to a boolean signaling if the call was successful. + // `info` can be nullptr if there was no data associated with `entry`. + using GetInfoCallback = base::OnceCallback<void(bool, std::unique_ptr<Info>)>; + virtual void GetInfo(const Entry& entry, GetInfoCallback callback) = 0; +}; + +} // namespace media + +#endif // MEDIA_MOJO_SERVICES_MEDIA_CAPABILITIES_DATABASE_H_ \ No newline at end of file
diff --git a/media/mojo/services/video_decode_perf_history.cc b/media/mojo/services/video_decode_perf_history.cc new file mode 100644 index 0000000..e51aefa --- /dev/null +++ b/media/mojo/services/video_decode_perf_history.cc
@@ -0,0 +1,124 @@ +// Copyright 2017 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 "media/mojo/services/video_decode_perf_history.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/strings/stringprintf.h" +#include "media/base/video_codecs.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace media { + +MediaCapabilitiesDatabase* g_database = nullptr; + +// static +void VideoDecodePerfHistory::Initialize( + MediaCapabilitiesDatabase* db_instance) { + DVLOG(2) << __func__; + g_database = db_instance; +} + +// static +void VideoDecodePerfHistory::BindRequest( + mojom::VideoDecodePerfHistoryRequest request) { + DVLOG(2) << __func__; + + // Single static instance should serve all requests. + static VideoDecodePerfHistory* instance = new VideoDecodePerfHistory(); + + instance->BindRequestInternal(std::move(request)); +} + +VideoDecodePerfHistory::VideoDecodePerfHistory() { + DVLOG(2) << __func__; +} + +VideoDecodePerfHistory::~VideoDecodePerfHistory() { + DVLOG(2) << __func__; + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +void VideoDecodePerfHistory::BindRequestInternal( + mojom::VideoDecodePerfHistoryRequest request) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + bindings_.AddBinding(this, std::move(request)); +} + +void VideoDecodePerfHistory::OnGotPerfInfo( + const MediaCapabilitiesDatabase::Entry& entry, + GetPerfInfoCallback mojo_cb, + bool database_success, + std::unique_ptr<MediaCapabilitiesDatabase::Info> info) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(!mojo_cb.is_null()); + + bool is_power_efficient; + bool is_smooth; + double percent_dropped = 0; + + if (info.get()) { + DCHECK(database_success); + percent_dropped = + static_cast<double>(info->frames_dropped) / info->frames_decoded; + + // TODO(chcunningham): add statistics for power efficiency to database. + is_power_efficient = true; + is_smooth = percent_dropped <= kMaxSmoothDroppedFramesPercent; + } else { + // TODO(chcunningham/mlamouri): Refactor database API to give us nearby + // entry info whenever we don't have a perfect match. If higher + // resolutions/framerates are known to be smooth, we can report this as + // smooth. If lower resolutions/frames are known to be janky, we can assume + // this will be janky. + + // No entry? Lets be optimistic. + is_power_efficient = true; + is_smooth = true; + } + + DVLOG(3) << __func__ + << base::StringPrintf(" profile:%s size:%s fps:%d --> ", + GetProfileName(entry.codec_profile()).c_str(), + entry.size().ToString().c_str(), + entry.frame_rate()) + << (info.get() + ? base::StringPrintf( + "smooth:%d frames_decoded:%d pcnt_dropped:%f", + is_smooth, info->frames_decoded, percent_dropped) + : (database_success ? "no info" : "query FAILED")); + + std::move(mojo_cb).Run(is_smooth, is_power_efficient); +} + +void VideoDecodePerfHistory::GetPerfInfo(VideoCodecProfile profile, + const gfx::Size& natural_size, + int frame_rate, + GetPerfInfoCallback callback) { + DVLOG(3) << __func__; + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + DCHECK_NE(profile, VIDEO_CODEC_PROFILE_UNKNOWN); + DCHECK_GT(frame_rate, 0); + DCHECK(natural_size.width() > 0 && natural_size.height() > 0); + + // TODO(chcunningham): Make this an error condition once the database impl + // has landed. Hardcoding results for now. + if (!g_database) { + DVLOG(2) << __func__ << " No database! Assuming smooth/efficient perf."; + std::move(callback).Run(true, true); + return; + } + + MediaCapabilitiesDatabase::Entry db_entry(profile, natural_size, frame_rate); + + // Unretained is safe because this is a leaky singleton. + g_database->GetInfo( + db_entry, + base::BindOnce(&VideoDecodePerfHistory::OnGotPerfInfo, + base::Unretained(this), db_entry, std::move(callback))); +} + +} // namespace media
diff --git a/media/mojo/services/video_decode_perf_history.h b/media/mojo/services/video_decode_perf_history.h new file mode 100644 index 0000000..6965648 --- /dev/null +++ b/media/mojo/services/video_decode_perf_history.h
@@ -0,0 +1,87 @@ +// Copyright 2017 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 MEDIA_MOJO_SERVICES_VIDEO_DECODE_PERF_HISTORY_H_ +#define MEDIA_MOJO_SERVICES_VIDEO_DECODE_PERF_HISTORY_H_ + +#include <stdint.h> +#include <memory> +#include <string> + +#include "base/callback.h" +#include "base/sequence_checker.h" +#include "media/base/video_codecs.h" +#include "media/mojo/interfaces/video_decode_perf_history.mojom.h" +#include "media/mojo/services/media_capabilities_database.h" +#include "media/mojo/services/media_mojo_export.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "ui/gfx/geometry/size.h" + +namespace media { + +// This browser-process service helps render-process clients respond to +// MediaCapabilities queries by looking up past performance for a given video +// configuration. +// +// Smoothness and power efficiency are assessed by evaluating raw stats from the +// MediaCapabilitiesDatabse. +// +// The object is lazily created upon the first call to BindRequest(). Future +// calls will bind to the same instance. +// +// This class is not thread safe. All calls to BindRequest() and GetPerfInfo() +// should be made on the same sequence (generally stemming from inbound Mojo +// IPCs on the browser IO sequence). +class MEDIA_MOJO_EXPORT VideoDecodePerfHistory + : public mojom::VideoDecodePerfHistory { + public: + // Provides |db_instance| for use once VideoDecodePerfHistory is lazily (upon + // first BindRequest) instantiated. Database lifetime should match/exceed that + // of the VideoDecodePerfHistory singleton. + static void Initialize(MediaCapabilitiesDatabase* db_instance); + + // Bind the request to singleton instance. + static void BindRequest(mojom::VideoDecodePerfHistoryRequest request); + + // mojom::VideoDecodePerfHistory implementation: + void GetPerfInfo(VideoCodecProfile profile, + const gfx::Size& natural_size, + int frame_rate, + GetPerfInfoCallback callback) override; + + private: + // Friends so it can create its own instances with mock database. + friend class VideoDecodePerfHistoryTest; + + // Decode capabilities will be described as "smooth" whenever the percentage + // of dropped frames is less-than-or-equal-to this value. 10% chosen as a + // lenient value after manual testing. + static constexpr double kMaxSmoothDroppedFramesPercent = .10; + + VideoDecodePerfHistory(); + ~VideoDecodePerfHistory() override; + + // Binds |request| to this instance. + void BindRequestInternal(mojom::VideoDecodePerfHistoryRequest request); + + // Internal callback for |database_| queries. Will assess performance history + // from database and pass results on to |mojo_cb|. + void OnGotPerfInfo(const MediaCapabilitiesDatabase::Entry& entry, + GetPerfInfoCallback mojo_cb, + bool database_success, + std::unique_ptr<MediaCapabilitiesDatabase::Info> info); + + // Maps bindings from several render-processes to this single browser-process + // service. + mojo::BindingSet<mojom::VideoDecodePerfHistory> bindings_; + + // Ensures all access to class members come on the same sequence. + SEQUENCE_CHECKER(sequence_checker_); + + DISALLOW_COPY_AND_ASSIGN(VideoDecodePerfHistory); +}; + +} // namespace media + +#endif // MEDIA_MOJO_SERVICES_VIDEO_DECODE_PERF_HISTORY_H_ \ No newline at end of file
diff --git a/media/mojo/services/video_decode_perf_history_unittest.cc b/media/mojo/services/video_decode_perf_history_unittest.cc new file mode 100644 index 0000000..f35270a2 --- /dev/null +++ b/media/mojo/services/video_decode_perf_history_unittest.cc
@@ -0,0 +1,142 @@ +// Copyright 2017 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 <map> +#include <sstream> +#include <string> + +#include "base/memory/ptr_util.h" +#include "media/mojo/services/media_capabilities_database.h" +#include "media/mojo/services/video_decode_perf_history.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +class FakeCapabilitiesDatabase : public MediaCapabilitiesDatabase { + public: + FakeCapabilitiesDatabase() = default; + ~FakeCapabilitiesDatabase() override {} + + void AppendInfoToEntry(const Entry& entry, const Info& info) override { + std::string key = MakeEntryKey(entry); + if (entries_.find(key) == entries_.end()) { + entries_.emplace(std::make_pair(key, info)); + } else { + const Info& known_info = entries_.at(key); + uint32_t frames_decoded = known_info.frames_decoded + info.frames_decoded; + uint32_t frames_dropped = known_info.frames_dropped + info.frames_dropped; + entries_.at(key) = Info(frames_decoded, frames_dropped); + } + } + + void GetInfo(const Entry& entry, GetInfoCallback callback) override { + auto entry_it = entries_.find(MakeEntryKey(entry)); + if (entry_it == entries_.end()) { + std::move(callback).Run(true, nullptr); + } else { + std::move(callback).Run(true, base::MakeUnique<Info>(entry_it->second)); + } + } + + private: + static std::string MakeEntryKey(const Entry& entry) { + std::stringstream ss; + ss << entry.codec_profile() << "|" << entry.size().ToString() << "|" + << entry.frame_rate(); + return ss.str(); + } + + std::map<std::string, Info> entries_; +}; + +class VideoDecodePerfHistoryTest : public ::testing::Test { + public: + void SetUp() override { + // Sniff the database pointer so tests can inject entries. + database_ = base::MakeUnique<FakeCapabilitiesDatabase>(); + VideoDecodePerfHistory::Initialize(database_.get()); + + // Make tests hermetic by creating a new instance. Cannot use unique_ptr + // here because the constructor/destructor are private and only accessible + // to this test via friending. + perf_history_ = new VideoDecodePerfHistory(); + } + + void TearDown() override { + // Avoid leaking between tests. + delete perf_history_; + perf_history_ = nullptr; + } + + // Tests may set this as the callback for VideoDecodePerfHistory::GetPerfInfo + // to check the results of the call. + MOCK_METHOD2(MockPerfInfoCb, void(bool is_smooth, bool is_power_efficient)); + + protected: + using Entry = media::MediaCapabilitiesDatabase::Entry; + using Info = media::MediaCapabilitiesDatabase::Info; + + static constexpr double kMaxSmoothDroppedFramesPercent = + VideoDecodePerfHistory::kMaxSmoothDroppedFramesPercent; + + // The VideoDecodeStatsReporter being tested. + VideoDecodePerfHistory* perf_history_; + + // The database |perf_history_| uses to store/query performance stats. + std::unique_ptr<FakeCapabilitiesDatabase> database_; +}; + +TEST_F(VideoDecodePerfHistoryTest, GetPerfInfo_Smooth) { + // Prepare database with 2 entries. The second entry has a higher framerate + // and a higher number of dropped frames such that it is "not smooth". + const VideoCodecProfile kKnownProfile = VP9PROFILE_PROFILE0; + const gfx::Size kKownSize(100, 200); + const int kSmoothFrameRate = 30; + const int kNotSmoothFrameRate = 90; + const int kFramesDecoded = 1000; + // Sets the ratio of dropped frames to barely qualify as smooth. + const int kSmoothFramesDropped = + kFramesDecoded * kMaxSmoothDroppedFramesPercent; + // Set the ratio of dropped frames to barely qualify as NOT smooth. + const int kNotSmoothFramesDropped = + kFramesDecoded * kMaxSmoothDroppedFramesPercent + 1; + + // Add the entries. + database_->AppendInfoToEntry( + Entry(kKnownProfile, kKownSize, kSmoothFrameRate), + Info(kFramesDecoded, kSmoothFramesDropped)); + database_->AppendInfoToEntry( + Entry(kKnownProfile, kKownSize, kNotSmoothFrameRate), + Info(kFramesDecoded, kNotSmoothFramesDropped)); + + // Verify perf history returns is_smooth = true for the smooth entry. + bool is_smooth = true; + bool is_power_efficient = true; + EXPECT_CALL(*this, MockPerfInfoCb(is_smooth, is_power_efficient)); + perf_history_->GetPerfInfo( + kKnownProfile, kKownSize, kSmoothFrameRate, + base::BindOnce(&VideoDecodePerfHistoryTest::MockPerfInfoCb, + base::Unretained(this))); + + // Verify perf history returns is_smooth = false for the NOT smooth entry. + is_smooth = false; + EXPECT_CALL(*this, MockPerfInfoCb(is_smooth, is_power_efficient)); + perf_history_->GetPerfInfo( + kKnownProfile, kKownSize, kNotSmoothFrameRate, + base::BindOnce(&VideoDecodePerfHistoryTest::MockPerfInfoCb, + base::Unretained(this))); + + // Verify perf history optimistically returns is_smooth = true when no entry + // can be found with the given configuration. + const VideoCodecProfile kUnknownProfile = VP9PROFILE_PROFILE2; + is_smooth = true; + EXPECT_CALL(*this, MockPerfInfoCb(is_smooth, is_power_efficient)); + perf_history_->GetPerfInfo( + kUnknownProfile, kKownSize, kNotSmoothFrameRate, + base::BindOnce(&VideoDecodePerfHistoryTest::MockPerfInfoCb, + base::Unretained(this))); +} + +} // namespace media
diff --git a/mojo/public/cpp/system/BUILD.gn b/mojo/public/cpp/system/BUILD.gn index 6058fbfc..71156a5 100644 --- a/mojo/public/cpp/system/BUILD.gn +++ b/mojo/public/cpp/system/BUILD.gn
@@ -27,8 +27,8 @@ "buffer.h", "core.h", "data_pipe.h", - "data_pipe_string_writer.cc", - "data_pipe_string_writer.h", + "file_data_pipe_producer.cc", + "file_data_pipe_producer.g", "functions.h", "handle.h", "handle_signal_tracker.cc", @@ -41,6 +41,8 @@ "platform_handle.h", "simple_watcher.cc", "simple_watcher.h", + "string_data_pipe_producer.cc", + "string_data_pipe_producer.h", "system_export.h", "wait.cc", "wait.h",
diff --git a/mojo/public/cpp/system/file_data_pipe_producer.cc b/mojo/public/cpp/system/file_data_pipe_producer.cc new file mode 100644 index 0000000..ab511e1f --- /dev/null +++ b/mojo/public/cpp/system/file_data_pipe_producer.cc
@@ -0,0 +1,215 @@ +// Copyright 2017 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 "mojo/public/cpp/system/file_data_pipe_producer.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted_delete_on_sequence.h" +#include "base/numerics/safe_conversions.h" +#include "base/sequenced_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/task_scheduler/post_task.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "mojo/public/cpp/system/simple_watcher.h" + +namespace mojo { + +namespace { + +// No good reason not to attempt very large pipe transactions in case the data +// pipe in use has a very large capacity available, so we default to trying +// 64 MB chunks whenever a producer is writable. +constexpr uint32_t kDefaultMaxReadSize = 64 * 1024 * 1024; + +} // namespace + +class FileDataPipeProducer::FileSequenceState + : public base::RefCountedDeleteOnSequence<FileSequenceState> { + public: + using CompletionCallback = + base::OnceCallback<void(ScopedDataPipeProducerHandle producer, + MojoResult result)>; + + FileSequenceState( + ScopedDataPipeProducerHandle producer_handle, + scoped_refptr<base::SequencedTaskRunner> file_task_runner, + CompletionCallback callback, + scoped_refptr<base::SequencedTaskRunner> callback_task_runner) + : base::RefCountedDeleteOnSequence<FileSequenceState>(file_task_runner), + file_task_runner_(std::move(file_task_runner)), + callback_task_runner_(std::move(callback_task_runner)), + producer_handle_(std::move(producer_handle)), + callback_(std::move(callback)) {} + + void Cancel() { + base::AutoLock lock(lock_); + is_cancelled_ = true; + } + + void StartFromFile(base::File file) { + file_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&FileSequenceState::StartFromFileOnFileSequence, this, + std::move(file))); + } + + void StartFromPath(const base::FilePath& path) { + file_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&FileSequenceState::StartFromPathOnFileSequence, this, + path)); + } + + private: + friend class base::DeleteHelper<FileSequenceState>; + friend class base::RefCountedDeleteOnSequence<FileSequenceState>; + + ~FileSequenceState() = default; + + void StartFromFileOnFileSequence(base::File file) { + file_ = std::move(file); + TransferSomeBytes(); + if (producer_handle_.is_valid()) { + // If we didn't nail it all on the first transaction attempt, setup a + // watcher and complete the read asynchronously. + watcher_ = base::MakeUnique<SimpleWatcher>( + FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + watcher_->Watch(producer_handle_.get(), MOJO_HANDLE_SIGNAL_WRITABLE, + MOJO_WATCH_CONDITION_SATISFIED, + base::Bind(&FileSequenceState::OnHandleReady, this)); + } + } + + void StartFromPathOnFileSequence(const base::FilePath& path) { + StartFromFileOnFileSequence( + base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ)); + } + + void OnHandleReady(MojoResult result, const HandleSignalsState& state) { + { + // Stop ourselves from doing redundant work if we've been cancelled from + // another thread. Note that we do not rely on this for any kind of thread + // safety concerns. + base::AutoLock lock(lock_); + if (is_cancelled_) + return; + } + + if (result != MOJO_RESULT_OK) { + // Either the consumer pipe has been closed or something terrible + // happened. In any case, we'll never be able to write more data. + Finish(MOJO_RESULT_ABORTED); + return; + } + + TransferSomeBytes(); + } + + void TransferSomeBytes() { + while (true) { + // Lock as much of the pipe as we can. + void* pipe_buffer; + uint32_t size = kDefaultMaxReadSize; + MojoResult result = producer_handle_->BeginWriteData( + &pipe_buffer, &size, MOJO_WRITE_DATA_FLAG_NONE); + if (result == MOJO_RESULT_SHOULD_WAIT) + return; + if (result != MOJO_RESULT_OK) { + Finish(MOJO_RESULT_ABORTED); + return; + } + + // Attempt to read that many bytes from the file, directly into the data + // pipe. + DCHECK(base::IsValueInRangeForNumericType<int>(size)); + int attempted_read_size = static_cast<int>(size); + int read_size = file_.ReadAtCurrentPos(static_cast<char*>(pipe_buffer), + attempted_read_size); + producer_handle_->EndWriteData( + read_size >= 0 ? static_cast<uint32_t>(read_size) : 0); + + if (read_size < 0) { + Finish(MOJO_RESULT_ABORTED); + return; + } + + if (read_size < attempted_read_size) { + // ReadAtCurrentPos makes a best effort to read all requested bytes. We + // reasonably assume if it fails to read what we ask for, we've hit EOF. + Finish(MOJO_RESULT_OK); + return; + } + } + } + + void Finish(MojoResult result) { + watcher_.reset(); + callback_task_runner_->PostTask( + FROM_HERE, base::BindOnce(std::move(callback_), + std::move(producer_handle_), result)); + } + + const scoped_refptr<base::SequencedTaskRunner> file_task_runner_; + const scoped_refptr<base::SequencedTaskRunner> callback_task_runner_; + + // State which is effectively owned and used only on the file sequence. + ScopedDataPipeProducerHandle producer_handle_; + base::File file_; + CompletionCallback callback_; + std::unique_ptr<SimpleWatcher> watcher_; + + // Guards |is_cancelled_|. + base::Lock lock_; + bool is_cancelled_ = false; + + DISALLOW_COPY_AND_ASSIGN(FileSequenceState); +}; + +FileDataPipeProducer::FileDataPipeProducer( + ScopedDataPipeProducerHandle producer) + : producer_(std::move(producer)), weak_factory_(this) {} + +FileDataPipeProducer::~FileDataPipeProducer() { + if (file_sequence_state_) + file_sequence_state_->Cancel(); +} + +void FileDataPipeProducer::WriteFromFile(base::File file, + CompletionCallback callback) { + InitializeNewRequest(std::move(callback)); + file_sequence_state_->StartFromFile(std::move(file)); +} + +void FileDataPipeProducer::WriteFromPath(const base::FilePath& path, + CompletionCallback callback) { + InitializeNewRequest(std::move(callback)); + file_sequence_state_->StartFromPath(path); +} + +void FileDataPipeProducer::InitializeNewRequest(CompletionCallback callback) { + DCHECK(!file_sequence_state_); + auto file_task_runner = base::CreateSequencedTaskRunnerWithTraits( + {base::MayBlock(), base::TaskPriority::BACKGROUND}); + file_sequence_state_ = new FileSequenceState( + std::move(producer_), file_task_runner, + base::BindOnce(&FileDataPipeProducer::OnWriteComplete, + weak_factory_.GetWeakPtr(), std::move(callback)), + base::SequencedTaskRunnerHandle::Get()); +} + +void FileDataPipeProducer::OnWriteComplete( + CompletionCallback callback, + ScopedDataPipeProducerHandle producer, + MojoResult ready_result) { + producer_ = std::move(producer); + file_sequence_state_ = nullptr; + std::move(callback).Run(ready_result); +} + +} // namespace mojo
diff --git a/mojo/public/cpp/system/file_data_pipe_producer.h b/mojo/public/cpp/system/file_data_pipe_producer.h new file mode 100644 index 0000000..15a808c --- /dev/null +++ b/mojo/public/cpp/system/file_data_pipe_producer.h
@@ -0,0 +1,76 @@ +// Copyright 2017 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 MOJO_PUBLIC_CPP_SYSTEM_FILE_DATA_PIPE_PRODUCER_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_FILE_DATA_PIPE_PRODUCER_H_ + +#include "base/callback_forward.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/system/data_pipe.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace mojo { + +// Helper class which takes ownership of a ScopedDataPipeProducerHandle and +// assumes responsibility for feeding it the contents of a given file. This +// takes care of waiting for pipe capacity as needed, and can notify callers +// asynchronously when the operation is complete. +// +// Note that the FileDataPipeProducer must be kept alive until notified of +// completion to ensure that all of the intended contents are written to the +// pipe. Premature destruction may result in partial or total truncation of data +// made available to the consumer. +class MOJO_CPP_SYSTEM_EXPORT FileDataPipeProducer { + public: + using CompletionCallback = base::OnceCallback<void(MojoResult result)>; + + // Constructs a new FileDataPipeProducer which will write data to |producer|. + explicit FileDataPipeProducer(ScopedDataPipeProducerHandle producer); + ~FileDataPipeProducer(); + + // Attempts to eventually write all of |file|'s contents to the pipe. Invokes + // |callback| asynchronously when done. Note that |callback| IS allowed to + // delete this FileDataPipeProducer. + // + // If the write is successful |result| will be |MOJO_RESULT_OK|. Otherwise + // (e.g. if the producer detects the consumer is closed and the pipe has no + // remaining capacity, or if file reads fail for any reason) |result| will be + // |MOJO_RESULT_ABORTED|. + // + // Note that if the FileDataPipeProducer is destroyed before |callback| can be + // invoked, |callback| is *never* invoked, and the write will be permanently + // interrupted (and the producer handle closed) after making potentially only + // partial progress. + // + // Multiple writes may be performed in sequence (each one after the last + // completes), but Write() must not be called before the |callback| for the + // previous call to Write() (if any) has returned. + void WriteFromFile(base::File file, CompletionCallback callback); + + // Same as above but takes a FilePath instead of an opened File. Opens the + // file on an appropriate sequence and then proceeds as WriteFromFile() would. + void WriteFromPath(const base::FilePath& path, CompletionCallback callback); + + private: + class FileSequenceState; + + void InitializeNewRequest(CompletionCallback callback); + void OnWriteComplete(CompletionCallback callback, + ScopedDataPipeProducerHandle producer, + MojoResult result); + + ScopedDataPipeProducerHandle producer_; + scoped_refptr<FileSequenceState> file_sequence_state_; + base::WeakPtrFactory<FileDataPipeProducer> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileDataPipeProducer); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_FILE_DATA_PIPE_PRODUCER_H_
diff --git a/mojo/public/cpp/system/data_pipe_string_writer.cc b/mojo/public/cpp/system/string_data_pipe_producer.cc similarity index 85% rename from mojo/public/cpp/system/data_pipe_string_writer.cc rename to mojo/public/cpp/system/string_data_pipe_producer.cc index 646eca6..34b25d60 100644 --- a/mojo/public/cpp/system/data_pipe_string_writer.cc +++ b/mojo/public/cpp/system/string_data_pipe_producer.cc
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "mojo/public/cpp/system/data_pipe_string_writer.h" +#include "mojo/public/cpp/system/string_data_pipe_producer.h" #include <algorithm> @@ -55,16 +55,16 @@ } // namespace -DataPipeStringWriter::DataPipeStringWriter( +StringDataPipeProducer::StringDataPipeProducer( ScopedDataPipeProducerHandle producer) : producer_(std::move(producer)), watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC), weak_factory_(this) {} -DataPipeStringWriter::~DataPipeStringWriter() = default; +StringDataPipeProducer::~StringDataPipeProducer() = default; -void DataPipeStringWriter::Write(const base::StringPiece& data, - CompletionCallback callback) { +void StringDataPipeProducer::Write(const base::StringPiece& data, + CompletionCallback callback) { DCHECK(!callback_); callback_ = std::move(callback); @@ -75,7 +75,7 @@ WriteDataToProducerHandle(producer_.get(), data.data(), &size); if (result == MOJO_RESULT_OK && size == data.size()) { base::SequencedTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&DataPipeStringWriter::InvokeCallback, + FROM_HERE, base::BindOnce(&StringDataPipeProducer::InvokeCallback, weak_factory_.GetWeakPtr(), MOJO_RESULT_OK)); } else { // Copy whatever data didn't make the cut and try again when the pipe has @@ -84,17 +84,17 @@ data_view_ = data_; watcher_.Watch(producer_.get(), MOJO_HANDLE_SIGNAL_WRITABLE, MOJO_WATCH_CONDITION_SATISFIED, - base::Bind(&DataPipeStringWriter::OnProducerHandleReady, + base::Bind(&StringDataPipeProducer::OnProducerHandleReady, base::Unretained(this))); } } -void DataPipeStringWriter::InvokeCallback(MojoResult result) { +void StringDataPipeProducer::InvokeCallback(MojoResult result) { // May delete |this|. std::move(callback_).Run(result); } -void DataPipeStringWriter::OnProducerHandleReady( +void StringDataPipeProducer::OnProducerHandleReady( MojoResult ready_result, const HandleSignalsState& state) { bool failed = false;
diff --git a/mojo/public/cpp/system/data_pipe_string_writer.h b/mojo/public/cpp/system/string_data_pipe_producer.h similarity index 69% rename from mojo/public/cpp/system/data_pipe_string_writer.h rename to mojo/public/cpp/system/string_data_pipe_producer.h index f3b8e44..f35d8ff 100644 --- a/mojo/public/cpp/system/data_pipe_string_writer.h +++ b/mojo/public/cpp/system/string_data_pipe_producer.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_STRING_WRITER_H_ -#define MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_STRING_WRITER_H_ +#ifndef MOJO_PUBLIC_CPP_SYSTEM_STRING_DATA_PIPE_PRODUCER_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_STRING_DATA_PIPE_PRODUCER_H_ #include <string> @@ -22,30 +22,31 @@ // takes care of waiting for pipe capacity as needed, and can notify callers // asynchronously when the operation is complete. // -// Note that the DataPipeStringWriter must be kept alive until notified of +// Note that the StringDataPipeProducer must be kept alive until notified of // completion to ensure that all of the string's data is written to the pipe. // Premature destruction may result in partial or total truncation of data made // available to the consumer. -class MOJO_CPP_SYSTEM_EXPORT DataPipeStringWriter { +class MOJO_CPP_SYSTEM_EXPORT StringDataPipeProducer { public: using CompletionCallback = base::OnceCallback<void(MojoResult result)>; - // Constructs a new DataPipeStringWriter which will write data to |producer|. - explicit DataPipeStringWriter(ScopedDataPipeProducerHandle producer); - ~DataPipeStringWriter(); + // Constructs a new StringDataPipeProducer which will write data to + // |producer|. + explicit StringDataPipeProducer(ScopedDataPipeProducerHandle producer); + ~StringDataPipeProducer(); // Attempts to eventually write all of |data|. Invokes |callback| // asynchronously when done. Note that |callback| IS allowed to delete this - // DataPipeStringWriter. + // StringDataPipeProducer. // // If the write is successful |result| will be |MOJO_RESULT_OK|. Otherwise // (e.g. if the producer detects the consumer is closed and the pipe has no // remaining capacity) |result| will be |MOJO_RESULT_ABORTED|. // - // Note that if the DataPipeStringWriter is destroyed before |callback| can be - // invoked, |callback| is *never* invoked, and the write will be permanently - // interrupted (and the producer handle closed) after making potentially only - // partial progress. + // Note that if the StringDataPipeProducer is destroyed before |callback| can + // be invoked, |callback| is *never* invoked, and the write will be + // permanently interrupted (and the producer handle closed) after making + // potentially only partial progress. // // Multiple writes may be performed in sequence (each one after the last // completes), but Write() must not be called before the |callback| for the @@ -62,11 +63,11 @@ base::StringPiece data_view_; CompletionCallback callback_; SimpleWatcher watcher_; - base::WeakPtrFactory<DataPipeStringWriter> weak_factory_; + base::WeakPtrFactory<StringDataPipeProducer> weak_factory_; - DISALLOW_COPY_AND_ASSIGN(DataPipeStringWriter); + DISALLOW_COPY_AND_ASSIGN(StringDataPipeProducer); }; } // namespace mojo -#endif // MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_STRING_WRITER_H_ +#endif // MOJO_PUBLIC_CPP_SYSTEM_STRING_DATA_PIPE_PRODUCER_H_
diff --git a/mojo/public/cpp/system/tests/BUILD.gn b/mojo/public/cpp/system/tests/BUILD.gn index 9c74b5e..f580601d 100644 --- a/mojo/public/cpp/system/tests/BUILD.gn +++ b/mojo/public/cpp/system/tests/BUILD.gn
@@ -7,10 +7,11 @@ sources = [ "core_unittest.cc", - "data_pipe_string_writer_unittest.cc", + "file_data_pipe_producer_unittest.cc", "handle_signal_tracker_unittest.cc", "handle_signals_state_unittest.cc", "simple_watcher_unittest.cc", + "string_data_pipe_producer_unittest.cc", "wait_set_unittest.cc", "wait_unittest.cc", ]
diff --git a/mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc b/mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc new file mode 100644 index 0000000..2ca9ba0 --- /dev/null +++ b/mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc
@@ -0,0 +1,214 @@ +// Copyright 2017 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 <algorithm> +#include <string> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/test/scoped_task_environment.h" +#include "mojo/public/cpp/system/data_pipe.h" +#include "mojo/public/cpp/system/file_data_pipe_producer.h" +#include "mojo/public/cpp/system/simple_watcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +// Test helper. Reads a consumer handle, accumulating data into a string. Reads +// until encountering an error (e.g. peer closure), at which point it invokes an +// async callback. +class DataPipeReader { + public: + explicit DataPipeReader(ScopedDataPipeConsumerHandle consumer_handle, + size_t read_size, + base::OnceClosure on_read_done) + : consumer_handle_(std::move(consumer_handle)), + read_size_(read_size), + on_read_done_(std::move(on_read_done)), + watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) { + watcher_.Watch( + consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE, + MOJO_WATCH_CONDITION_SATISFIED, + base::Bind(&DataPipeReader::OnDataAvailable, base::Unretained(this))); + } + ~DataPipeReader() = default; + + const std::string& data() const { return data_; } + + private: + void OnDataAvailable(MojoResult result, const HandleSignalsState& state) { + if (result == MOJO_RESULT_OK) { + uint32_t size = static_cast<uint32_t>(read_size_); + std::vector<char> buffer(size, 0); + MojoResult read_result; + do { + read_result = consumer_handle_->ReadData(buffer.data(), &size, + MOJO_READ_DATA_FLAG_NONE); + if (read_result == MOJO_RESULT_OK) { + std::copy(buffer.begin(), buffer.begin() + size, + std::back_inserter(data_)); + } + } while (read_result == MOJO_RESULT_OK); + + if (read_result == MOJO_RESULT_SHOULD_WAIT) + return; + } + + if (result != MOJO_RESULT_CANCELLED) + watcher_.Cancel(); + + std::move(on_read_done_).Run(); + } + + ScopedDataPipeConsumerHandle consumer_handle_; + const size_t read_size_; + base::OnceClosure on_read_done_; + SimpleWatcher watcher_; + std::string data_; + + DISALLOW_COPY_AND_ASSIGN(DataPipeReader); +}; + +class FileDataPipeProducerTest : public testing::Test { + public: + FileDataPipeProducerTest() { CHECK(temp_dir_.CreateUniqueTempDir()); } + + ~FileDataPipeProducerTest() override = default; + + protected: + base::FilePath CreateTempFileWithContents(const std::string& contents) { + base::FilePath temp_file_path = temp_dir_.GetPath().AppendASCII( + base::StringPrintf("tmp%d", tmp_file_id_++)); + base::File temp_file(temp_file_path, + base::File::FLAG_CREATE | base::File::FLAG_WRITE); + int bytes_written = temp_file.WriteAtCurrentPos( + contents.data(), static_cast<int>(contents.size())); + CHECK_EQ(static_cast<int>(contents.size()), bytes_written); + return temp_file_path; + } + + static void WriteFromFileThenCloseWriter( + std::unique_ptr<FileDataPipeProducer> producer, + base::File file) { + FileDataPipeProducer* raw_producer = producer.get(); + raw_producer->WriteFromFile( + std::move(file), + base::BindOnce([](std::unique_ptr<FileDataPipeProducer> producer, + MojoResult result) {}, + std::move(producer))); + } + + static void WriteFromPathThenCloseWriter( + std::unique_ptr<FileDataPipeProducer> producer, + const base::FilePath& path) { + FileDataPipeProducer* raw_producer = producer.get(); + raw_producer->WriteFromPath( + path, base::BindOnce([](std::unique_ptr<FileDataPipeProducer> producer, + MojoResult result) {}, + std::move(producer))); + } + + private: + base::test::ScopedTaskEnvironment task_environment_; + base::ScopedTempDir temp_dir_; + int tmp_file_id_ = 0; + + DISALLOW_COPY_AND_ASSIGN(FileDataPipeProducerTest); +}; + +TEST_F(FileDataPipeProducerTest, WriteFromFile) { + const std::string kTestStringFragment = "Hello, world!"; + constexpr size_t kNumRepetitions = 1000; + std::string test_string; + for (size_t i = 0; i < kNumRepetitions; ++i) + test_string += kTestStringFragment; + + base::FilePath path = CreateTempFileWithContents(test_string); + + base::RunLoop loop; + DataPipe pipe(16); + DataPipeReader reader(std::move(pipe.consumer_handle), 16, + loop.QuitClosure()); + + base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); + WriteFromFileThenCloseWriter( + base::MakeUnique<FileDataPipeProducer>(std::move(pipe.producer_handle)), + std::move(file)); + loop.Run(); + + EXPECT_EQ(test_string, reader.data()); +} + +TEST_F(FileDataPipeProducerTest, WriteFromPath) { + const std::string kTestStringFragment = "Hello, world!"; + constexpr size_t kNumRepetitions = 1000; + std::string test_string; + for (size_t i = 0; i < kNumRepetitions; ++i) + test_string += kTestStringFragment; + + base::FilePath path = CreateTempFileWithContents(test_string); + + base::RunLoop loop; + DataPipe pipe(16); + DataPipeReader reader(std::move(pipe.consumer_handle), 16, + loop.QuitClosure()); + + WriteFromPathThenCloseWriter( + base::MakeUnique<FileDataPipeProducer>(std::move(pipe.producer_handle)), + path); + loop.Run(); + + EXPECT_EQ(test_string, reader.data()); +} + +TEST_F(FileDataPipeProducerTest, TinyFile) { + const std::string kTestString = "."; + base::FilePath path = CreateTempFileWithContents(kTestString); + base::RunLoop loop; + DataPipe pipe(16); + DataPipeReader reader(std::move(pipe.consumer_handle), 16, + loop.QuitClosure()); + WriteFromPathThenCloseWriter( + base::MakeUnique<FileDataPipeProducer>(std::move(pipe.producer_handle)), + path); + loop.Run(); + + EXPECT_EQ(kTestString, reader.data()); +} + +TEST_F(FileDataPipeProducerTest, HugeFile) { + constexpr size_t kHugeFileSize = 100 * 1024 * 1024; + constexpr uint32_t kDataPipeSize = 512 * 1024; + + std::string test_string(kHugeFileSize, 'a'); + for (size_t i = 0; i + 3 < test_string.size(); i += 4) { + test_string[i + 1] = 'b'; + test_string[i + 2] = 'c'; + test_string[i + 3] = 'd'; + } + base::FilePath path = CreateTempFileWithContents(test_string); + + base::RunLoop loop; + DataPipe pipe(kDataPipeSize); + DataPipeReader reader(std::move(pipe.consumer_handle), kDataPipeSize, + loop.QuitClosure()); + + WriteFromPathThenCloseWriter( + base::MakeUnique<FileDataPipeProducer>(std::move(pipe.producer_handle)), + path); + loop.Run(); + + EXPECT_EQ(test_string, reader.data()); +} + +} // namespace +} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/data_pipe_string_writer_unittest.cc b/mojo/public/cpp/system/tests/string_data_pipe_producer_unittest.cc similarity index 70% rename from mojo/public/cpp/system/tests/data_pipe_string_writer_unittest.cc rename to mojo/public/cpp/system/tests/string_data_pipe_producer_unittest.cc index 4bed947..61f5574 100644 --- a/mojo/public/cpp/system/tests/data_pipe_string_writer_unittest.cc +++ b/mojo/public/cpp/system/tests/string_data_pipe_producer_unittest.cc
@@ -14,11 +14,12 @@ #include "base/strings/string_piece.h" #include "base/test/scoped_task_environment.h" #include "mojo/public/cpp/system/data_pipe.h" -#include "mojo/public/cpp/system/data_pipe_string_writer.h" #include "mojo/public/cpp/system/simple_watcher.h" +#include "mojo/public/cpp/system/string_data_pipe_producer.h" #include "testing/gtest/include/gtest/gtest.h" namespace mojo { +namespace { // Test helper. Reads a consumer handle, accumulating data into a string. Reads // until encountering an error (e.g. peer closure), at which point it invokes an @@ -72,102 +73,102 @@ DISALLOW_COPY_AND_ASSIGN(DataPipeReader); }; -class DataPipeStringWriterTest : public testing::Test { +class StringDataPipeProducerTest : public testing::Test { public: - DataPipeStringWriterTest() = default; - ~DataPipeStringWriterTest() override = default; + StringDataPipeProducerTest() = default; + ~StringDataPipeProducerTest() override = default; protected: - static void WriteStringThenCloseWriter( - std::unique_ptr<DataPipeStringWriter> writer, + static void WriteStringThenCloseProducer( + std::unique_ptr<StringDataPipeProducer> producer, const base::StringPiece& str) { - DataPipeStringWriter* raw_writer = writer.get(); - raw_writer->Write( - str, base::BindOnce([](std::unique_ptr<DataPipeStringWriter> writer, + StringDataPipeProducer* raw_producer = producer.get(); + raw_producer->Write( + str, base::BindOnce([](std::unique_ptr<StringDataPipeProducer> producer, MojoResult result) {}, - std::move(writer))); + std::move(producer))); } - static void WriteStringsThenCloseWriter( - std::unique_ptr<DataPipeStringWriter> writer, + static void WriteStringsThenCloseProducer( + std::unique_ptr<StringDataPipeProducer> producer, std::list<base::StringPiece> strings) { - DataPipeStringWriter* raw_writer = writer.get(); + StringDataPipeProducer* raw_producer = producer.get(); base::StringPiece str = strings.front(); strings.pop_front(); - raw_writer->Write( + raw_producer->Write( str, base::BindOnce( - [](std::unique_ptr<DataPipeStringWriter> writer, + [](std::unique_ptr<StringDataPipeProducer> producer, std::list<base::StringPiece> strings, MojoResult result) { if (!strings.empty()) - WriteStringsThenCloseWriter(std::move(writer), - std::move(strings)); + WriteStringsThenCloseProducer(std::move(producer), + std::move(strings)); }, - std::move(writer), std::move(strings))); + std::move(producer), std::move(strings))); } private: base::test::ScopedTaskEnvironment task_environment_; - DISALLOW_COPY_AND_ASSIGN(DataPipeStringWriterTest); + DISALLOW_COPY_AND_ASSIGN(StringDataPipeProducerTest); }; -TEST_F(DataPipeStringWriterTest, EqualCapacity) { +TEST_F(StringDataPipeProducerTest, EqualCapacity) { const std::string kTestString = "Hello, world!"; base::RunLoop loop; mojo::DataPipe pipe(static_cast<uint32_t>(kTestString.size())); DataPipeReader reader(std::move(pipe.consumer_handle), loop.QuitClosure()); - WriteStringThenCloseWriter( - base::MakeUnique<DataPipeStringWriter>(std::move(pipe.producer_handle)), + WriteStringThenCloseProducer( + base::MakeUnique<StringDataPipeProducer>(std::move(pipe.producer_handle)), kTestString); loop.Run(); EXPECT_EQ(kTestString, reader.data()); } -TEST_F(DataPipeStringWriterTest, UnderCapacity) { +TEST_F(StringDataPipeProducerTest, UnderCapacity) { const std::string kTestString = "Hello, world!"; base::RunLoop loop; mojo::DataPipe pipe(static_cast<uint32_t>(kTestString.size() * 2)); DataPipeReader reader(std::move(pipe.consumer_handle), loop.QuitClosure()); - WriteStringThenCloseWriter( - base::MakeUnique<DataPipeStringWriter>(std::move(pipe.producer_handle)), + WriteStringThenCloseProducer( + base::MakeUnique<StringDataPipeProducer>(std::move(pipe.producer_handle)), kTestString); loop.Run(); EXPECT_EQ(kTestString, reader.data()); } -TEST_F(DataPipeStringWriterTest, OverCapacity) { +TEST_F(StringDataPipeProducerTest, OverCapacity) { const std::string kTestString = "Hello, world!"; base::RunLoop loop; mojo::DataPipe pipe(static_cast<uint32_t>(kTestString.size() / 2)); DataPipeReader reader(std::move(pipe.consumer_handle), loop.QuitClosure()); - WriteStringThenCloseWriter( - base::MakeUnique<DataPipeStringWriter>(std::move(pipe.producer_handle)), + WriteStringThenCloseProducer( + base::MakeUnique<StringDataPipeProducer>(std::move(pipe.producer_handle)), kTestString); loop.Run(); EXPECT_EQ(kTestString, reader.data()); } -TEST_F(DataPipeStringWriterTest, TinyPipe) { +TEST_F(StringDataPipeProducerTest, TinyPipe) { const std::string kTestString = "Hello, world!"; base::RunLoop loop; mojo::DataPipe pipe(1); DataPipeReader reader(std::move(pipe.consumer_handle), loop.QuitClosure()); - WriteStringThenCloseWriter( - base::MakeUnique<DataPipeStringWriter>(std::move(pipe.producer_handle)), + WriteStringThenCloseProducer( + base::MakeUnique<StringDataPipeProducer>(std::move(pipe.producer_handle)), kTestString); loop.Run(); EXPECT_EQ(kTestString, reader.data()); } -TEST_F(DataPipeStringWriterTest, MultipleWrites) { +TEST_F(StringDataPipeProducerTest, MultipleWrites) { const std::string kTestString1 = "Hello, world!"; const std::string kTestString2 = "There is a lot of data coming your way!"; const std::string kTestString3 = "So many strings!"; @@ -176,8 +177,8 @@ base::RunLoop loop; mojo::DataPipe pipe(4); DataPipeReader reader(std::move(pipe.consumer_handle), loop.QuitClosure()); - WriteStringsThenCloseWriter( - base::MakeUnique<DataPipeStringWriter>(std::move(pipe.producer_handle)), + WriteStringsThenCloseProducer( + base::MakeUnique<StringDataPipeProducer>(std::move(pipe.producer_handle)), {kTestString1, kTestString2, kTestString3, kTestString4}); loop.Run(); @@ -185,4 +186,5 @@ reader.data()); } +} // namespace } // namespace mojo
diff --git a/net/BUILD.gn b/net/BUILD.gn index dfdfb84..19c2dc5 100644 --- a/net/BUILD.gn +++ b/net/BUILD.gn
@@ -4632,6 +4632,7 @@ "base/mime_util_unittest.cc", "base/mock_network_change_notifier.cc", "base/mock_network_change_notifier.h", + "base/net_string_util_unittest.cc", "base/network_activity_monitor_unittest.cc", "base/network_change_notifier_unittest.cc", "base/network_change_notifier_win_unittest.cc",
diff --git a/net/DEPS b/net/DEPS index 77c57f4..81a0949 100644 --- a/net/DEPS +++ b/net/DEPS
@@ -35,6 +35,7 @@ # Consolidated string functions that depend on icu. "net_string_util_icu\.cc": [ + "+base/i18n/case_conversion.h", "+base/i18n/i18n_constants.h", "+base/i18n/icu_string_conversions.h", "+third_party/icu/source/common/unicode/ucnv.h"
diff --git a/net/android/java/src/org/chromium/net/NetStringUtil.java b/net/android/java/src/org/chromium/net/NetStringUtil.java index 980b185..edbf4e0 100644 --- a/net/android/java/src/org/chromium/net/NetStringUtil.java +++ b/net/android/java/src/org/chromium/net/NetStringUtil.java
@@ -12,6 +12,7 @@ import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.text.Normalizer; +import java.util.Locale; /** * Utility functions for converting strings between formats when not built with @@ -85,4 +86,19 @@ return null; } } + + /** + * Convert a string to uppercase. + * @param str String to convert. + * @return: String converted to uppercase using default locale, + * null on failure. + */ + @CalledByNative + private static String toUpperCase(String str) { + try { + return str.toUpperCase(Locale.getDefault()); + } catch (Exception e) { + return null; + } + } }
diff --git a/net/base/net_string_util.h b/net/base/net_string_util.h index 8e959a0..fb9c0e15 100644 --- a/net/base/net_string_util.h +++ b/net/base/net_string_util.h
@@ -8,6 +8,7 @@ #include <string> #include "base/strings/string16.h" +#include "net/base/net_export.h" // String conversion functions. By default, they're implemented with ICU, but // when building with USE_ICU_ALTERNATIVES, they use platform functions instead. @@ -37,6 +38,11 @@ const char* charset, base::string16* output); +// Converts |str| to uppercase using the default locale, and writes it to +// |output|. On failure returns false and |output| is cleared. +NET_EXPORT_PRIVATE bool ToUpper(const base::string16& str, + base::string16* output); + } // namespace net #endif // NET_BASE_NET_STRING_UTIL_H__
diff --git a/net/base/net_string_util_icu.cc b/net/base/net_string_util_icu.cc index 525800c..7a3e96b54 100644 --- a/net/base/net_string_util_icu.cc +++ b/net/base/net_string_util_icu.cc
@@ -4,6 +4,7 @@ #include "net/base/net_string_util.h" +#include "base/i18n/case_conversion.h" #include "base/i18n/i18n_constants.h" #include "base/i18n/icu_string_conversions.h" #include "base/strings/string_util.h" @@ -59,4 +60,9 @@ output); } +bool ToUpper(const base::string16& str, base::string16* output) { + *output = base::i18n::ToUpper(str); + return true; +} + } // namespace net
diff --git a/net/base/net_string_util_icu_alternatives_android.cc b/net/base/net_string_util_icu_alternatives_android.cc index 76d9326..736f41fd 100644 --- a/net/base/net_string_util_icu_alternatives_android.cc +++ b/net/base/net_string_util_icu_alternatives_android.cc
@@ -113,4 +113,19 @@ return true; } +bool ToUpper(const base::string16& str, base::string16* output) { + output->clear(); + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> java_new_str( + env, env->NewString(str.data(), str.length())); + if (java_new_str.is_null()) + return false; + ScopedJavaLocalRef<jstring> java_result = + android::Java_NetStringUtil_toUpperCase(env, java_new_str); + if (java_result.is_null()) + return false; + *output = base::android::ConvertJavaStringToUTF16(java_result); + return true; +} + } // namespace net
diff --git a/net/base/net_string_util_icu_alternatives_ios.mm b/net/base/net_string_util_icu_alternatives_ios.mm index 1f6c0c0..0938434 100644 --- a/net/base/net_string_util_icu_alternatives_ios.mm +++ b/net/base/net_string_util_icu_alternatives_ios.mm
@@ -73,4 +73,13 @@ return false; } +bool ToUpper(const base::string16& str, base::string16* output) { + base::ScopedCFTypeRef<CFStringRef> cfstring(base::SysUTF16ToCFStringRef(str)); + base::ScopedCFTypeRef<CFMutableStringRef> mutable_cfstring( + CFStringCreateMutableCopy(kCFAllocatorDefault, 0, cfstring.get())); + CFStringUppercase(mutable_cfstring.get(), NULL); + *output = base::SysCFStringRefToUTF16(mutable_cfstring.get()); + return true; +} + } // namespace net
diff --git a/net/base/net_string_util_unittest.cc b/net/base/net_string_util_unittest.cc new file mode 100644 index 0000000..c37ef9e --- /dev/null +++ b/net/base/net_string_util_unittest.cc
@@ -0,0 +1,45 @@ +// Copyright 2017 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 "net/base/net_string_util.h" + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(NetStringUtilTest, ToUpperEmpty) { + base::string16 in; + base::string16 out; + base::string16 expected; + ASSERT_TRUE(ToUpper(in, &out)); + ASSERT_EQ(expected, out); +} + +TEST(NetStringUtilTest, ToUpperSingleChar) { + base::string16 in(base::WideToUTF16(L"a")); + base::string16 out; + base::string16 expected(base::WideToUTF16(L"A")); + ASSERT_TRUE(ToUpper(in, &out)); + ASSERT_EQ(expected, out); +} + +TEST(NetStringUtilTest, ToUpperSimple) { + base::string16 in(base::WideToUTF16(L"hello world")); + base::string16 out; + base::string16 expected(base::WideToUTF16(L"HELLO WORLD")); + ASSERT_TRUE(ToUpper(in, &out)); + ASSERT_EQ(expected, out); +} + +TEST(NetStringUtilTest, ToUpperAlreadyUpper) { + base::string16 in(base::WideToUTF16(L"HELLO WORLD")); + base::string16 out; + base::string16 expected(base::WideToUTF16(L"HELLO WORLD")); + ASSERT_TRUE(ToUpper(in, &out)); + ASSERT_EQ(expected, out); +} + +} // namespace net
diff --git a/net/dns/dns_test_util.cc b/net/dns/dns_test_util.cc index 2de2c69b..923a887 100644 --- a/net/dns/dns_test_util.cc +++ b/net/dns/dns_test_util.cc
@@ -178,6 +178,10 @@ return std::unique_ptr<DnsTransaction>(transaction); } + void AddEDNSOption(const OptRecordRdata::Opt& opt) override { + NOTREACHED() << "Not implemented"; + } + void CompleteDelayedTransactions() { DelayedTransactionList old_delayed_transactions; old_delayed_transactions.swap(delayed_transactions_);
diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc index 11ea820..899493b 100644 --- a/net/dns/dns_transaction.cc +++ b/net/dns/dns_transaction.cc
@@ -48,11 +48,6 @@ namespace { -// Provide a common macro to simplify code and readability. We must use a -// macro as the underlying HISTOGRAM macro creates static variables. -#define DNS_HISTOGRAM(name, time) UMA_HISTOGRAM_CUSTOM_TIMES(name, time, \ - base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromHours(1), 100) - // Count labels in the fully-qualified name in DNS format. int CountLabels(const std::string& name) { size_t count = 0; @@ -216,11 +211,11 @@ return ERR_DNS_MALFORMED_RESPONSE; if (rv == OK) { DCHECK_EQ(STATE_NONE, next_state_); - DNS_HISTOGRAM("AsyncDNS.UDPAttemptSuccess", - base::TimeTicks::Now() - start_time_); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.UDPAttemptSuccess", + base::TimeTicks::Now() - start_time_); } else if (rv != ERR_IO_PENDING) { - DNS_HISTOGRAM("AsyncDNS.UDPAttemptFail", - base::TimeTicks::Now() - start_time_); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.UDPAttemptFail", + base::TimeTicks::Now() - start_time_); } return rv; } @@ -389,11 +384,11 @@ set_result(rv); if (rv == OK) { DCHECK_EQ(STATE_NONE, next_state_); - DNS_HISTOGRAM("AsyncDNS.TCPAttemptSuccess", - base::TimeTicks::Now() - start_time_); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.TCPAttemptSuccess", + base::TimeTicks::Now() - start_time_); } else if (rv != ERR_IO_PENDING) { - DNS_HISTOGRAM("AsyncDNS.TCPAttemptFail", - base::TimeTicks::Now() - start_time_); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.TCPAttemptFail", + base::TimeTicks::Now() - start_time_); } return rv; } @@ -559,10 +554,12 @@ const std::string& hostname, uint16_t qtype, const DnsTransactionFactory::CallbackType& callback, - const NetLogWithSource& net_log) + const NetLogWithSource& net_log, + const OptRecordRdata* opt_rdata) : session_(session), hostname_(hostname), qtype_(qtype), + opt_rdata_(opt_rdata), callback_(callback), net_log_(net_log), qnames_initial_size_(0), @@ -708,7 +705,7 @@ uint16_t id = session_->NextQueryId(); std::unique_ptr<DnsQuery> query; if (attempts_.empty()) { - query.reset(new DnsQuery(id, qnames_.front(), qtype_)); + query.reset(new DnsQuery(id, qnames_.front(), qtype_, opt_rdata_)); } else { query = attempts_[0]->GetQuery()->CloneWithNewId(id); } @@ -944,6 +941,7 @@ scoped_refptr<DnsSession> session_; std::string hostname_; uint16_t qtype_; + const OptRecordRdata* opt_rdata_; // Cleared in DoCallback. DnsTransactionFactory::CallbackType callback_; @@ -985,11 +983,19 @@ const CallbackType& callback, const NetLogWithSource& net_log) override { return std::unique_ptr<DnsTransaction>(new DnsTransactionImpl( - session_.get(), hostname, qtype, callback, net_log)); + session_.get(), hostname, qtype, callback, net_log, opt_rdata_.get())); + } + + void AddEDNSOption(const OptRecordRdata::Opt& opt) override { + if (opt_rdata_ == nullptr) + opt_rdata_ = std::make_unique<OptRecordRdata>(); + + opt_rdata_->AddOpt(opt); } private: scoped_refptr<DnsSession> session_; + std::unique_ptr<OptRecordRdata> opt_rdata_; }; } // namespace
diff --git a/net/dns/dns_transaction.h b/net/dns/dns_transaction.h index cd56fc4..5f6cd0a5c 100644 --- a/net/dns/dns_transaction.h +++ b/net/dns/dns_transaction.h
@@ -13,6 +13,7 @@ #include "base/callback_forward.h" #include "base/compiler_specific.h" #include "net/base/net_export.h" +#include "net/dns/record_rdata.h" namespace net { @@ -68,6 +69,10 @@ const CallbackType& callback, const NetLogWithSource& net_log) WARN_UNUSED_RESULT = 0; + // The given EDNS0 option will be included in all DNS queries performed by + // transactions from this factory. + virtual void AddEDNSOption(const OptRecordRdata::Opt& opt) = 0; + // Creates a DnsTransactionFactory which creates DnsTransactionImpl using the // |session|. static std::unique_ptr<DnsTransactionFactory> CreateFactory(
diff --git a/net/dns/dns_transaction_unittest.cc b/net/dns/dns_transaction_unittest.cc index 0dc00f8b..c06e362 100644 --- a/net/dns/dns_transaction_unittest.cc +++ b/net/dns/dns_transaction_unittest.cc
@@ -54,8 +54,9 @@ const char* dotted_name, uint16_t qtype, IoMode mode, - bool use_tcp) - : query_(new DnsQuery(id, DomainFromDot(dotted_name), qtype)), + bool use_tcp, + const OptRecordRdata* opt_rdata = nullptr) + : query_(new DnsQuery(id, DomainFromDot(dotted_name), qtype, opt_rdata)), use_tcp_(use_tcp) { if (use_tcp_) { std::unique_ptr<uint16_t> length(new uint16_t); @@ -375,10 +376,11 @@ const uint8_t* response_data, size_t response_length, IoMode mode, - bool use_tcp) { + bool use_tcp, + const OptRecordRdata* opt_rdata = nullptr) { CHECK(socket_factory_.get()); std::unique_ptr<DnsSocketData> data( - new DnsSocketData(id, dotted_name, qtype, mode, use_tcp)); + new DnsSocketData(id, dotted_name, qtype, mode, use_tcp, opt_rdata)); data->AddResponseData(response_data, response_length, mode); AddSocketData(std::move(data)); } @@ -387,18 +389,20 @@ const char* dotted_name, uint16_t qtype, const uint8_t* data, - size_t data_length) { - AddQueryAndResponse(id, dotted_name, qtype, data, data_length, ASYNC, - false); + size_t data_length, + const OptRecordRdata* opt_rdata = nullptr) { + AddQueryAndResponse(id, dotted_name, qtype, data, data_length, ASYNC, false, + opt_rdata); } void AddSyncQueryAndResponse(uint16_t id, const char* dotted_name, uint16_t qtype, const uint8_t* data, - size_t data_length) { + size_t data_length, + const OptRecordRdata* opt_rdata = nullptr) { AddQueryAndResponse(id, dotted_name, qtype, data, data_length, SYNCHRONOUS, - false); + false, opt_rdata); } // Add expected query of |dotted_name| and |qtype| and no response. @@ -491,6 +495,43 @@ EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } +TEST_F(DnsTransactionTest, LookupWithEDNSOption) { + OptRecordRdata expected_opt_rdata; + + const OptRecordRdata::Opt ednsOpt(123, "\xbe\xef"); + transaction_factory_->AddEDNSOption(ednsOpt); + expected_opt_rdata.AddOpt(ednsOpt); + + AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, + kT0ResponseDatagram, arraysize(kT0ResponseDatagram), + &expected_opt_rdata); + + TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); +} + +TEST_F(DnsTransactionTest, LookupWithMultipleEDNSOptions) { + OptRecordRdata expected_opt_rdata; + + for (const auto& ednsOpt : { + // Two options with the same code, to check that both are included. + OptRecordRdata::Opt(1, "\xde\xad"), + OptRecordRdata::Opt(1, "\xbe\xef"), + // Try a different code and different length of data. + OptRecordRdata::Opt(2, "\xff"), + }) { + transaction_factory_->AddEDNSOption(ednsOpt); + expected_opt_rdata.AddOpt(ednsOpt); + } + + AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, + kT0ResponseDatagram, arraysize(kT0ResponseDatagram), + &expected_opt_rdata); + + TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); + EXPECT_TRUE(helper0.Run(transaction_factory_.get())); +} + // Concurrent lookup tests assume that DnsTransaction::Start immediately // consumes a socket from ClientSocketFactory. TEST_F(DnsTransactionTest, ConcurrentLookup) {
diff --git a/net/dns/host_resolver_impl.cc b/net/dns/host_resolver_impl.cc index 38a7cdb..070f031 100644 --- a/net/dns/host_resolver_impl.cc +++ b/net/dns/host_resolver_impl.cc
@@ -231,23 +231,30 @@ kSuffix, kSuffixLenTrimmed); } -// Provide a common macro to simplify code and readability. We must use a -// macro as the underlying HISTOGRAM macro creates static variables. -#define DNS_HISTOGRAM(name, time) UMA_HISTOGRAM_CUSTOM_TIMES(name, time, \ - base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromHours(1), 100) - // A macro to simplify code and readability. -#define DNS_HISTOGRAM_BY_PRIORITY(basename, priority, time) \ - do { \ - switch (priority) { \ - case HIGHEST: DNS_HISTOGRAM(basename "_HIGHEST", time); break; \ - case MEDIUM: DNS_HISTOGRAM(basename "_MEDIUM", time); break; \ - case LOW: DNS_HISTOGRAM(basename "_LOW", time); break; \ - case LOWEST: DNS_HISTOGRAM(basename "_LOWEST", time); break; \ - case IDLE: DNS_HISTOGRAM(basename "_IDLE", time); break; \ - case THROTTLED: DNS_HISTOGRAM(basename "_THROTTLED", time); break; \ - } \ - DNS_HISTOGRAM(basename, time); \ +#define DNS_HISTOGRAM_BY_PRIORITY(basename, priority, time) \ + do { \ + switch (priority) { \ + case HIGHEST: \ + UMA_HISTOGRAM_LONG_TIMES_100(basename "_HIGHEST", time); \ + break; \ + case MEDIUM: \ + UMA_HISTOGRAM_LONG_TIMES_100(basename "_MEDIUM", time); \ + break; \ + case LOW: \ + UMA_HISTOGRAM_LONG_TIMES_100(basename "_LOW", time); \ + break; \ + case LOWEST: \ + UMA_HISTOGRAM_LONG_TIMES_100(basename "_LOWEST", time); \ + break; \ + case IDLE: \ + UMA_HISTOGRAM_LONG_TIMES_100(basename "_IDLE", time); \ + break; \ + case THROTTLED: \ + UMA_HISTOGRAM_LONG_TIMES_100(basename "_THROTTLED", time); \ + break; \ + } \ + UMA_HISTOGRAM_LONG_TIMES_100(basename, time); \ } while (0) // Record time from Request creation until a valid DNS response. @@ -256,15 +263,15 @@ base::TimeDelta duration) { if (had_dns_config) { if (speculative) { - DNS_HISTOGRAM("AsyncDNS.TotalTime_speculative", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.TotalTime_speculative", duration); } else { - DNS_HISTOGRAM("AsyncDNS.TotalTime", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.TotalTime", duration); } } else { if (speculative) { - DNS_HISTOGRAM("DNS.TotalTime_speculative", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.TotalTime_speculative", duration); } else { - DNS_HISTOGRAM("DNS.TotalTime", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.TotalTime", duration); } } } @@ -874,44 +881,48 @@ if (error == OK) { if (had_non_speculative_request_) { category = RESOLVE_SUCCESS; - DNS_HISTOGRAM("DNS.ResolveSuccess", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.ResolveSuccess", duration); } else { category = RESOLVE_SPECULATIVE_SUCCESS; - DNS_HISTOGRAM("DNS.ResolveSpeculativeSuccess", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.ResolveSpeculativeSuccess", duration); } // Log DNS lookups based on |address_family|. This will help us determine // if IPv4 or IPv4/6 lookups are faster or slower. switch (key_.address_family) { case ADDRESS_FAMILY_IPV4: - DNS_HISTOGRAM("DNS.ResolveSuccess_FAMILY_IPV4", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.ResolveSuccess_FAMILY_IPV4", + duration); break; case ADDRESS_FAMILY_IPV6: - DNS_HISTOGRAM("DNS.ResolveSuccess_FAMILY_IPV6", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.ResolveSuccess_FAMILY_IPV6", + duration); break; case ADDRESS_FAMILY_UNSPECIFIED: - DNS_HISTOGRAM("DNS.ResolveSuccess_FAMILY_UNSPEC", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.ResolveSuccess_FAMILY_UNSPEC", + duration); break; } } else { if (had_non_speculative_request_) { category = RESOLVE_FAIL; - DNS_HISTOGRAM("DNS.ResolveFail", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.ResolveFail", duration); } else { category = RESOLVE_SPECULATIVE_FAIL; - DNS_HISTOGRAM("DNS.ResolveSpeculativeFail", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.ResolveSpeculativeFail", duration); } // Log DNS lookups based on |address_family|. This will help us determine // if IPv4 or IPv4/6 lookups are faster or slower. switch (key_.address_family) { case ADDRESS_FAMILY_IPV4: - DNS_HISTOGRAM("DNS.ResolveFail_FAMILY_IPV4", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.ResolveFail_FAMILY_IPV4", duration); break; case ADDRESS_FAMILY_IPV6: - DNS_HISTOGRAM("DNS.ResolveFail_FAMILY_IPV6", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.ResolveFail_FAMILY_IPV6", duration); break; case ADDRESS_FAMILY_UNSPECIFIED: - DNS_HISTOGRAM("DNS.ResolveFail_FAMILY_UNSPEC", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.ResolveFail_FAMILY_UNSPEC", + duration); break; } UMA_HISTOGRAM_CUSTOM_ENUMERATION(kOSErrorsForGetAddrinfoHistogramName, @@ -953,8 +964,9 @@ // If first attempt didn't finish before retry attempt, then calculate stats // on how much time is saved by having spawned an extra attempt. if (!first_attempt_to_complete && is_first_attempt && !was_canceled()) { - DNS_HISTOGRAM("DNS.AttemptTimeSavedByRetry", - base::TimeTicks::Now() - retry_attempt_finished_time_); + UMA_HISTOGRAM_LONG_TIMES_100( + "DNS.AttemptTimeSavedByRetry", + base::TimeTicks::Now() - retry_attempt_finished_time_); } if (was_canceled() || !first_attempt_to_complete) { @@ -970,9 +982,9 @@ base::TimeDelta duration = base::TimeTicks::Now() - start_time; if (error == OK) - DNS_HISTOGRAM("DNS.AttemptSuccessDuration", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.AttemptSuccessDuration", duration); else - DNS_HISTOGRAM("DNS.AttemptFailDuration", duration); + UMA_HISTOGRAM_LONG_TIMES_100("DNS.AttemptFailDuration", duration); } // Set on the task runner thread, read on the worker thread. @@ -1111,18 +1123,19 @@ DCHECK(transaction); base::TimeDelta duration = base::TimeTicks::Now() - start_time; if (net_error != OK) { - DNS_HISTOGRAM("AsyncDNS.TransactionFailure", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.TransactionFailure", duration); OnFailure(net_error, DnsResponse::DNS_PARSE_OK); return; } - DNS_HISTOGRAM("AsyncDNS.TransactionSuccess", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.TransactionSuccess", duration); switch (transaction->GetType()) { case dns_protocol::kTypeA: - DNS_HISTOGRAM("AsyncDNS.TransactionSuccess_A", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.TransactionSuccess_A", duration); break; case dns_protocol::kTypeAAAA: - DNS_HISTOGRAM("AsyncDNS.TransactionSuccess_AAAA", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.TransactionSuccess_AAAA", + duration); break; } @@ -1188,14 +1201,14 @@ bool success, const AddressList& addr_list) { if (!success) { - DNS_HISTOGRAM("AsyncDNS.SortFailure", - base::TimeTicks::Now() - start_time); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.SortFailure", + base::TimeTicks::Now() - start_time); OnFailure(ERR_DNS_SORT_ERROR, DnsResponse::DNS_PARSE_OK); return; } - DNS_HISTOGRAM("AsyncDNS.SortSuccess", - base::TimeTicks::Now() - start_time); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.SortSuccess", + base::TimeTicks::Now() - start_time); // AddressSorter prunes unusable destinations. if (addr_list.empty()) { @@ -1575,7 +1588,7 @@ if (dns_task_error_ != OK) { base::TimeDelta duration = base::TimeTicks::Now() - start_time; if (net_error == OK) { - DNS_HISTOGRAM("AsyncDNS.FallbackSuccess", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.FallbackSuccess", duration); if ((dns_task_error_ == ERR_NAME_NOT_RESOLVED) && ResemblesNetBIOSName(key_.hostname)) { UmaAsyncDnsResolveStatus(RESOLVE_STATUS_SUSPECT_NETBIOS); @@ -1586,7 +1599,7 @@ std::abs(dns_task_error_)); resolver_->OnDnsTaskResolve(dns_task_error_); } else { - DNS_HISTOGRAM("AsyncDNS.FallbackFail", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.FallbackFail", duration); UmaAsyncDnsResolveStatus(RESOLVE_STATUS_FAIL); } } @@ -1625,7 +1638,7 @@ void OnDnsTaskFailure(const base::WeakPtr<DnsTask>& dns_task, base::TimeDelta duration, int net_error) { - DNS_HISTOGRAM("AsyncDNS.ResolveFail", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.ResolveFail", duration); if (!dns_task) return; @@ -1660,17 +1673,20 @@ OnDnsTaskFailure(dns_task_->AsWeakPtr(), duration, net_error); return; } - DNS_HISTOGRAM("AsyncDNS.ResolveSuccess", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.ResolveSuccess", duration); // Log DNS lookups based on |address_family|. switch (key_.address_family) { case ADDRESS_FAMILY_IPV4: - DNS_HISTOGRAM("AsyncDNS.ResolveSuccess_FAMILY_IPV4", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.ResolveSuccess_FAMILY_IPV4", + duration); break; case ADDRESS_FAMILY_IPV6: - DNS_HISTOGRAM("AsyncDNS.ResolveSuccess_FAMILY_IPV6", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.ResolveSuccess_FAMILY_IPV6", + duration); break; case ADDRESS_FAMILY_UNSPECIFIED: - DNS_HISTOGRAM("AsyncDNS.ResolveSuccess_FAMILY_UNSPEC", duration); + UMA_HISTOGRAM_LONG_TIMES_100("AsyncDNS.ResolveSuccess_FAMILY_UNSPEC", + duration); break; }
diff --git a/services/ui/main.cc b/services/ui/main.cc index 4592379e..27c9ab5 100644 --- a/services/ui/main.cc +++ b/services/ui/main.cc
@@ -8,7 +8,9 @@ #include "services/ui/service.h" MojoResult ServiceMain(MojoHandle service_request_handle) { - service_manager::ServiceRunner runner(new ui::Service()); + ui::Service* ui_service = new ui::Service; + ui_service->set_running_standalone(true); + service_manager::ServiceRunner runner(ui_service); runner.set_message_loop_type(base::MessageLoop::TYPE_UI); return runner.Run(service_request_handle); }
diff --git a/services/ui/service.cc b/services/ui/service.cc index 5ff864e..9e5d510 100644 --- a/services/ui/service.cc +++ b/services/ui/service.cc
@@ -186,7 +186,8 @@ return false; } - ui::RegisterPathProvider(); + if (running_standalone_) + ui::RegisterPathProvider(); // Initialize resource bundle with 1x and 2x cursor bitmaps. ui::ResourceBundle::InitSharedInstanceWithPakFileRegion(
diff --git a/services/ui/service.h b/services/ui/service.h index 968dfc57..d32960b 100644 --- a/services/ui/service.h +++ b/services/ui/service.h
@@ -93,6 +93,9 @@ explicit Service(const InProcessConfig* config = nullptr); ~Service() override; + // Call if the ui::Service is being run as a standalone process. + void set_running_standalone(bool value) { running_standalone_ = value; } + private: // Holds InterfaceRequests received before the first WindowTreeHost Display // has been established. @@ -227,6 +230,8 @@ bool in_destructor_ = false; + bool running_standalone_ = false; + DISALLOW_COPY_AND_ASSIGN(Service); };
diff --git a/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter b/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter index 69d5a28..84fa2bf 100644 --- a/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter +++ b/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter
@@ -1,23 +1,15 @@ # http://crbug.com/715640 --BackgroundSyncBrowserTest* --ServiceWorkerBlackBoxBrowserTest.Registration --ServiceWorkerBrowserTest.CrossOriginFetchWithSaveData --ServiceWorkerBrowserTest.CrossSiteTransfer -ServiceWorkerBrowserTest.FetchPageWithSaveData --ServiceWorkerBrowserTest.FetchPageWithSaveDataPassThroughOnFetch -ServiceWorkerBrowserTest.ImportsBustMemcache -ServiceWorkerBrowserTest.Reload -ServiceWorkerBrowserTest.ResponseFromHTTPSServiceWorkerIsMarkedAsSecure -ServiceWorkerBrowserTest.ResponseFromHTTPServiceWorkerIsNotMarkedAsSecure --ServiceWorkerDisableWebSecurityTest.UnregisterNoCrash --ServiceWorkerDisableWebSecurityTest.UpdateNoCrash -ServiceWorkerNavigationPreloadTest.CanceledByInterceptor -ServiceWorkerNavigationPreloadTest.GetResponseText -ServiceWorkerNavigationPreloadTest.InvalidRedirect_InvalidLocation -ServiceWorkerNavigationPreloadTest.InvalidRedirect_MultiLocation -ServiceWorkerNavigationPreloadTest.NetworkError -ServiceWorkerNavigationPreloadTest.NetworkFallback --ServiceWorkerNavigationPreloadTest.NotEnabled -ServiceWorkerNavigationPreloadTest.PreloadHeadersCustom -ServiceWorkerNavigationPreloadTest.PreloadHeadersSimple -ServiceWorkerNavigationPreloadTest.RedirectAndRespondWithNavigationPreload @@ -25,13 +17,11 @@ -ServiceWorkerNavigationPreloadTest.RespondWithNavigationPreloadWithMimeSniffing -ServiceWorkerNavigationPreloadTest.SetHeaderValue -ServiceWorkerV8CacheStrategiesAggressiveTest.V8CacheOnCacheStorage --ServiceWorkerV8CacheStrategiesNoneTest.V8CacheOnCacheStorage -ServiceWorkerV8CacheStrategiesNormalTest.V8CacheOnCacheStorage -ServiceWorkerV8CacheStrategiesTest.V8CacheOnCacheStorage -ServiceWorkerVersionBrowserTest.ReadResourceFailure -ServiceWorkerVersionBrowserTest.ReadResourceFailure_WaitingWorker -ServiceWorkerVersionBrowserTest.ServiceWorkerScriptHeader --ServiceWorkerVersionBrowserTest.StartNotFound # ServiceWorker restart needs to read installed scripts. # https://crbug.com/756312
diff --git a/testing/scripts/run_multiple_telemetry_benchmarks_as_googletest.py b/testing/scripts/run_multiple_telemetry_benchmarks_as_googletest.py index 8a6b562..151f85d 100755 --- a/testing/scripts/run_multiple_telemetry_benchmarks_as_googletest.py +++ b/testing/scripts/run_multiple_telemetry_benchmarks_as_googletest.py
@@ -100,7 +100,9 @@ # This is not really a "script test" so does not need to manually add # any additional compile targets. def main_compile_targets(args): - json.dump([], args.output) + # Force compilation of the new isolate. Will change to telemetry_perf_tests + # once we switch the main isolate over to running this file. + json.dump(['telemetry_perf_tests_new'], args.output) if __name__ == '__main__':
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index c09dae2..1f58705 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -1330,7 +1330,7 @@ { "name": "Enabled", "params": { - "engagement_threshold_for_flash": "64" + "engagement_threshold_for_flash": "101" }, "enable_features": [ "PreferHtmlOverPlugins"
diff --git a/third_party/WebKit/LayoutTests/PRESUBMIT.py b/third_party/WebKit/LayoutTests/PRESUBMIT.py index 89ddada2..5b53b1a 100644 --- a/third_party/WebKit/LayoutTests/PRESUBMIT.py +++ b/third_party/WebKit/LayoutTests/PRESUBMIT.py
@@ -72,10 +72,68 @@ return results +def _CheckTestExpectations(input_api, output_api): + local_paths = [f.LocalPath() for f in input_api.AffectedFiles()] + if any('LayoutTests' in path for path in local_paths): + lint_path = input_api.os_path.join(input_api.PresubmitLocalPath(), + '..', 'Tools', 'Scripts', 'lint-test-expectations') + _, errs = input_api.subprocess.Popen( + [input_api.python_executable, lint_path], + stdout=input_api.subprocess.PIPE, + stderr=input_api.subprocess.PIPE).communicate() + if not errs: + return [output_api.PresubmitError( + "lint-test-expectations failed " + "to produce output; check by hand. ")] + if errs.strip() != 'Lint succeeded.': + return [output_api.PresubmitError(errs)] + return [] + + +def _CheckForJSTest(input_api, output_api): + """'js-test.js' is the past, 'testharness.js' is our glorious future""" + jstest_re = input_api.re.compile(r'resources/js-test.js') + + def source_file_filter(path): + return input_api.FilterSourceFile(path, + white_list=[r'third_party/WebKit/LayoutTests/.*\.(html|js|php|pl|svg)$']) + + errors = input_api.canned_checks._FindNewViolationsOfRule( + lambda _, x: not jstest_re.search(x), input_api, source_file_filter) + errors = [' * %s' % violation for violation in errors] + if errors: + return [output_api.PresubmitPromptOrNotify( + '"resources/js-test.js" is deprecated; please write new layout ' + 'tests using the assertions in "resources/testharness.js" ' + 'instead, as these can be more easily upstreamed to Web Platform ' + 'Tests for cross-vendor compatibility testing. If you\'re not ' + 'already familiar with this framework, a tutorial is available at ' + 'https://darobin.github.io/test-harness-tutorial/docs/using-testharness.html' + '\n\n%s' % '\n'.join(errors))] + return [] + + +def _CheckForInvalidPreferenceError(input_api, output_api): + pattern = input_api.re.compile('Invalid name for preference: (.+)') + results = [] + + for f in input_api.AffectedFiles(): + if not f.LocalPath().endswith('-expected.txt'): + continue + for line_num, line in f.ChangedContents(): + error = pattern.search(line) + if error: + results.append(output_api.PresubmitError('Found an invalid preference %s in expected result %s:%s' % (error.group(1), f, line_num))) + return results + + def CheckChangeOnUpload(input_api, output_api): results = [] results.extend(_CheckTestharnessResults(input_api, output_api)) results.extend(_CheckFilesUsingEventSender(input_api, output_api)) + results.extend(_CheckTestExpectations(input_api, output_api)) + results.extend(_CheckForJSTest(input_api, output_api)) + results.extend(_CheckForInvalidPreferenceError(input_api, output_api)) return results @@ -83,4 +141,5 @@ results = [] results.extend(_CheckTestharnessResults(input_api, output_api)) results.extend(_CheckFilesUsingEventSender(input_api, output_api)) + results.extend(_CheckTestExpectations(input_api, output_api)) return results
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json index 4efbabf5..7a4b02e 100644 --- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json +++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
@@ -152198,6 +152198,14 @@ } ] ], + "html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html": [ + [ + "/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html", + { + "timeout": "long" + } + ] + ], "html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html": [ [ "/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html", @@ -152206,6 +152214,22 @@ } ] ], + "html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html": [ + [ + "/html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html", + { + "timeout": "long" + } + ] + ], + "html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html": [ + [ + "/html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html", + { + "timeout": "long" + } + ] + ], "html/semantics/embedded-content/the-img-element/decode/image-decode.html": [ [ "/html/semantics/embedded-content/the-img-element/decode/image-decode.html", @@ -266414,15 +266438,27 @@ "testharness" ], "html/semantics/embedded-content/the-img-element/decode/image-decode-iframe.html": [ - "2632f69b005ea382ed91bfb71a7e0e2ee931a0c8", + "7fb6e548ca0d039496b35dadabd044274d0960be", + "testharness" + ], + "html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html": [ + "361ae415a6a41c7442c24f24898b51aef0e2270a", "testharness" ], "html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html": [ - "64dde3f7b1f9ed02e4433858f075123f18c092a4", + "34e5a2beebed1c95ab978258e3d93d9cefcd4d70", + "testharness" + ], + "html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html": [ + "0af39535cb047736d88948de962f876b1addbdbb", + "testharness" + ], + "html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html": [ + "9405efb65176096957438cbdcc59109b488d80e6", "testharness" ], "html/semantics/embedded-content/the-img-element/decode/image-decode.html": [ - "02d38d593fb42587c3ef261c349f7c42db345005", + "5368b62bf6c950e8d57b16b36148e5695ce16fd8", "testharness" ], "html/semantics/embedded-content/the-img-element/delay-load-event-detached.html": [
diff --git a/third_party/WebKit/LayoutTests/reporting-observer/reporting-api.html b/third_party/WebKit/LayoutTests/reporting-observer/reporting-api.html new file mode 100644 index 0000000..647ad935 --- /dev/null +++ b/third_party/WebKit/LayoutTests/reporting-observer/reporting-api.html
@@ -0,0 +1,51 @@ +<!DOCTYPE html> +<script src="../resources/testharness.js"></script> +<script src="../resources/testharnessreport.js"></script> +<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script> +<script src="file:///gen/third_party/WebKit/public/platform/reporting.mojom.js"></script> +<script> +// Mock implementation of ReportingServiceProxy. +// |promise| property is always a promise for the next report to be queued. +class MockReportingServiceProxy { + constructor() { + this.bindingSet = new mojo.BindingSet(blink.mojom.ReportingServiceProxy); + this.resetPromise(); + } + + bind(handle) { + this.bindingSet.addBinding(this, handle); + } + + resetPromise() { + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + }); + } + + // Interface implementation. + async queueDeprecationReport(url, message, sourceFile, lineNumber) { + this.resolve([url, message, sourceFile, lineNumber]); + this.resetPromise(); + } +} + +// Make an instance and have it receive the request. +var proxy = new MockReportingServiceProxy(); +var interceptor = new MojoInterfaceInterceptor(blink.mojom.ReportingServiceProxy.name); +interceptor.oninterfacerequest = e => proxy.bind(e.handle); +interceptor.start(); + +promise_test(async () => { + let promise = proxy.promise; + + // Use a deprecated feature. + window.webkitStorageInfo; + + // Ensure the report is generated and routed to the mojo interface. + let [url, message, sourceFile, lineNumber] = await promise; + assert_true(url.url.endsWith("reporting-observer/reporting-api.html")); + assert_equals(typeof message, "string"); + assert_true(sourceFile.endsWith("reporting-observer/reporting-api.html")); + assert_equals(typeof lineNumber, "number"); +}, "Deprecation report"); +</script>
diff --git a/third_party/WebKit/PRESUBMIT.py b/third_party/WebKit/PRESUBMIT.py index 5cb3f74..03e199a 100644 --- a/third_party/WebKit/PRESUBMIT.py +++ b/third_party/WebKit/PRESUBMIT.py
@@ -38,36 +38,6 @@ return results -def _CheckWatchlist(input_api, output_api): - """Check that the WATCHLIST file parses correctly.""" - errors = [] - for f in input_api.AffectedFiles(): - if f.LocalPath() != 'WATCHLISTS': - continue - import StringIO - import logging - import watchlists - - log_buffer = StringIO.StringIO() - log_handler = logging.StreamHandler(log_buffer) - log_handler.setFormatter( - logging.Formatter('%(levelname)s: %(message)s')) - logger = logging.getLogger() - logger.addHandler(log_handler) - - wl = watchlists.Watchlists(input_api.change.RepositoryRoot()) - - logger.removeHandler(log_handler) - log_handler.flush() - log_buffer.flush() - - if log_buffer.getvalue(): - errors.append(output_api.PresubmitError( - 'Cannot parse WATCHLISTS file, please resolve.', - log_buffer.getvalue().splitlines())) - return errors - - def _CommonChecks(input_api, output_api): """Checks common to both upload and commit.""" # We should figure out what license checks we actually want to use. @@ -78,29 +48,9 @@ input_api, output_api, excluded_paths=_EXCLUDED_PATHS, maxlen=800, license_header=license_header)) results.extend(_CheckForNonBlinkVariantMojomIncludes(input_api, output_api)) - results.extend(_CheckTestExpectations(input_api, output_api)) - results.extend(_CheckWatchlist(input_api, output_api)) return results -def _CheckTestExpectations(input_api, output_api): - local_paths = [f.LocalPath() for f in input_api.AffectedFiles()] - if any('LayoutTests' in path for path in local_paths): - lint_path = input_api.os_path.join(input_api.PresubmitLocalPath(), - 'Tools', 'Scripts', 'lint-test-expectations') - _, errs = input_api.subprocess.Popen( - [input_api.python_executable, lint_path], - stdout=input_api.subprocess.PIPE, - stderr=input_api.subprocess.PIPE).communicate() - if not errs: - return [output_api.PresubmitError( - "lint-test-expectations failed " - "to produce output; check by hand. ")] - if errs.strip() != 'Lint succeeded.': - return [output_api.PresubmitError(errs)] - return [] - - def _CheckStyle(input_api, output_api): # Files that follow Chromium's coding style do not include capital letters. re_chromium_style_file = re.compile(r'\b[a-z_]+\.(cc|h)$') @@ -148,51 +98,6 @@ return [] -def _CheckForJSTest(input_api, output_api): - """'js-test.js' is the past, 'testharness.js' is our glorious future""" - jstest_re = input_api.re.compile(r'resources/js-test.js') - - def source_file_filter(path): - return input_api.FilterSourceFile(path, - white_list=[r'third_party/WebKit/LayoutTests/.*\.(html|js|php|pl|svg)$']) - - errors = input_api.canned_checks._FindNewViolationsOfRule( - lambda _, x: not jstest_re.search(x), input_api, source_file_filter) - errors = [' * %s' % violation for violation in errors] - if errors: - return [output_api.PresubmitPromptOrNotify( - '"resources/js-test.js" is deprecated; please write new layout ' - 'tests using the assertions in "resources/testharness.js" ' - 'instead, as these can be more easily upstreamed to Web Platform ' - 'Tests for cross-vendor compatibility testing. If you\'re not ' - 'already familiar with this framework, a tutorial is available at ' - 'https://darobin.github.io/test-harness-tutorial/docs/using-testharness.html' - '\n\n%s' % '\n'.join(errors))] - return [] - -def _CheckForFailInFile(input_api, f): - pattern = input_api.re.compile('^FAIL') - errors = [] - for line_num, line in f.ChangedContents(): - if pattern.match(line): - errors.append(' %s:%d %s' % (f.LocalPath(), line_num, line)) - return errors - - -def _CheckForInvalidPreferenceError(input_api, output_api): - pattern = input_api.re.compile('Invalid name for preference: (.+)') - results = [] - - for f in input_api.AffectedFiles(): - if not f.LocalPath().endswith('-expected.txt'): - continue - for line_num, line in f.ChangedContents(): - error = pattern.search(line) - if error: - results.append(output_api.PresubmitError('Found an invalid preference %s in expected result %s:%s' % (error.group(1), f, line_num))) - return results - - def _CheckForForbiddenNamespace(input_api, output_api): """Checks that Blink uses Chromium namespaces only in permitted code.""" # This list is not exhaustive, but covers likely ones. @@ -245,8 +150,6 @@ results.extend(_CommonChecks(input_api, output_api)) results.extend(_CheckStyle(input_api, output_api)) results.extend(_CheckForPrintfDebugging(input_api, output_api)) - results.extend(_CheckForJSTest(input_api, output_api)) - results.extend(_CheckForInvalidPreferenceError(input_api, output_api)) results.extend(_CheckForForbiddenNamespace(input_api, output_api)) return results
diff --git a/third_party/WebKit/Source/core/frame/Deprecation.cpp b/third_party/WebKit/Source/core/frame/Deprecation.cpp index 1c30a2e..6d0298d 100644 --- a/third_party/WebKit/Source/core/frame/Deprecation.cpp +++ b/third_party/WebKit/Source/core/frame/Deprecation.cpp
@@ -9,6 +9,7 @@ #include "core/frame/DeprecationReport.h" #include "core/frame/FrameConsole.h" #include "core/frame/LocalFrame.h" +#include "core/frame/LocalFrameClient.h" #include "core/frame/Report.h" #include "core/frame/ReportingContext.h" #include "core/inspector/ConsoleMessage.h" @@ -16,6 +17,8 @@ #include "core/workers/WorkerOrWorkletGlobalScope.h" #include "platform/runtime_enabled_features.h" #include "public/platform/WebFeaturePolicyFeature.h" +#include "public/platform/reporting.mojom-blink.h" +#include "services/service_manager/public/cpp/interface_provider.h" namespace { @@ -271,14 +274,22 @@ return; Document* document = frame->GetDocument(); - ReportingContext* reporting_context = ReportingContext::From(document); - if (!reporting_context->ObserverExists()) - return; - // Send a deprecation report to any ReportingObservers. - ReportBody* body = new DeprecationReport(message, SourceLocation::Capture()); + // Construct the deprecation report. + DeprecationReport* body = + new DeprecationReport(message, SourceLocation::Capture()); Report* report = new Report("deprecation", document->Url().GetString(), body); - reporting_context->QueueReport(report); + + // Send the deprecation report to any ReportingObservers. + ReportingContext* reporting_context = ReportingContext::From(document); + if (reporting_context->ObserverExists()) + reporting_context->QueueReport(report); + + // Send the deprecation report to the Reporting API. + mojom::blink::ReportingServiceProxyPtr service; + frame->Client()->GetInterfaceProvider()->GetInterface(&service); + service->QueueDeprecationReport(document->Url(), body->message(), + body->sourceFile(), body->lineNumber()); } String Deprecation::DeprecationMessage(WebFeature feature) {
diff --git a/third_party/WebKit/Source/core/frame/DeprecationReport.h b/third_party/WebKit/Source/core/frame/DeprecationReport.h index ee069e8..b51085ce 100644 --- a/third_party/WebKit/Source/core/frame/DeprecationReport.h +++ b/third_party/WebKit/Source/core/frame/DeprecationReport.h
@@ -21,8 +21,10 @@ ~DeprecationReport() override {} String message() const { return message_; } - String sourceFile() const { return location_->Url(); } long lineNumber() const { return location_->LineNumber(); } + String sourceFile() const { + return location_->Url().IsNull() ? "" : location_->Url(); + } DEFINE_INLINE_VIRTUAL_TRACE() { ReportBody::Trace(visitor); }
diff --git a/third_party/WebKit/Source/core/paint/compositing/CompositedLayerMapping.cpp b/third_party/WebKit/Source/core/paint/compositing/CompositedLayerMapping.cpp index af81ff14..5ae665d 100644 --- a/third_party/WebKit/Source/core/paint/compositing/CompositedLayerMapping.cpp +++ b/third_party/WebKit/Source/core/paint/compositing/CompositedLayerMapping.cpp
@@ -1663,6 +1663,7 @@ foreground_layer_->SetNeedsDisplay(); } foreground_layer_->SetOffsetFromLayoutObject(foreground_offset); + foreground_layer_->SetShouldHitTest(true); // NOTE: there is some more configuring going on in // updateScrollingLayerGeometry().
diff --git a/third_party/WebKit/Source/core/paint/compositing/CompositedLayerMappingTest.cpp b/third_party/WebKit/Source/core/paint/compositing/CompositedLayerMappingTest.cpp index ac50b10..f4d456d 100644 --- a/third_party/WebKit/Source/core/paint/compositing/CompositedLayerMappingTest.cpp +++ b/third_party/WebKit/Source/core/paint/compositing/CompositedLayerMappingTest.cpp
@@ -918,6 +918,9 @@ static_cast<GraphicsLayerPaintingPhase>( kGraphicsLayerPaintForeground | kGraphicsLayerPaintOverflowContents), mapping->ForegroundLayer()->PaintingPhase()); + // Regression test for crbug.com/767908: a foreground layer should also + // participates hit testing. + EXPECT_TRUE(mapping->ForegroundLayer()->GetShouldHitTestForTesting()); Element* negative_composited_child = GetDocument().getElementById("negative-composited-child");
diff --git a/third_party/WebKit/Source/platform/graphics/GraphicsLayer.h b/third_party/WebKit/Source/platform/graphics/GraphicsLayer.h index 6009278a..10c1ad5 100644 --- a/third_party/WebKit/Source/platform/graphics/GraphicsLayer.h +++ b/third_party/WebKit/Source/platform/graphics/GraphicsLayer.h
@@ -183,6 +183,7 @@ void SetIsRootForIsolatedGroup(bool); void SetShouldHitTest(bool); + bool GetShouldHitTestForTesting() { return should_hit_test_; } void SetFilters(CompositorFilterOperations); void SetBackdropFilters(CompositorFilterOperations);
diff --git a/third_party/blink/tools/move_blink_source.py b/third_party/blink/tools/move_blink_source.py index 3a07e44..f8af13d 100755 --- a/third_party/blink/tools/move_blink_source.py +++ b/third_party/blink/tools/move_blink_source.py
@@ -5,21 +5,8 @@ """Tool to move Blink source from third_party/WebKit to third_party/blink. -How to use: -1. third_party/blink/tools/move_blink_source.py update --run - (It would take a few minutes to complete this command.) -2. git cl format -3. git commit -a -4. Land the commit - -5. third_party/blink/tools/move_blink_source.py move --git - (It would take an hour to complete this command.) -6. third_party/WebKit/Tools/Scripts/run-bindings-test --reset-results -7. git commit -a -8. Land the commit -9. Pray for successful build! - -TODO(tkent): More automation. +See https://docs.google.com/document/d/1l3aPv1Wx__SpRkdOhvJz8ciEGigNT3wFKv78XiuW0Tw/edit?usp=sharing#heading=h.o225wrxp242h +for the details. """ import argparse @@ -157,7 +144,7 @@ ('third_party/WebKit/Source', 'third_party/blink/renderer')]), ] for file_path, replacement_list in file_replacement_list: - self._update_single_file_content(file_path, replacement_list) + self._update_single_file_content(file_path, replacement_list, should_write=self._options.run) def move(self): _log.info('Planning renaming ...') @@ -179,6 +166,10 @@ self._fs.move(self._fs.join(self._repo_root, src_from_repo), self._fs.join(self._repo_root, dest_from_repo)) _log.info('[%d/%d] Moved %s', i + 1, len(file_pairs), src) + self._update_single_file_content( + 'build/get_landmines.py', + [('\ndef main', ' print \'The Great Blink mv for source files (crbug.com/768828)\'\n\ndef main')]) + def _create_basename_maps(self, file_pairs): basename_map = {} @@ -408,7 +399,7 @@ return re.sub(r'#include\s+"(\w+\.h)"', partial(self._replace_basename_only_include, subdir, source_path), content) - def _update_single_file_content(self, file_path, replace_list): + def _update_single_file_content(self, file_path, replace_list, should_write=True): full_path = self._fs.join(self._repo_root, file_path) original_content = self._fs.read_text_file(full_path) content = original_content @@ -421,7 +412,7 @@ else: raise TypeError('A tuple or a function is expected.') if content != original_content: - if self._options.run: + if should_write: self._fs.write_text_file(full_path, content) _log.info('Updated %s', file_path) else:
diff --git a/third_party/closure_compiler/externs/networking_private.js b/third_party/closure_compiler/externs/networking_private.js index f6415091..5dd6aa6 100644 --- a/third_party/closure_compiler/externs/networking_private.js +++ b/third_party/closure_compiler/externs/networking_private.js
@@ -678,6 +678,7 @@ * PaymentPortal: (!chrome.networkingPrivate.PaymentPortal|undefined), * PRLVersion: (number|undefined), * RoamingState: (string|undefined), + * Scanning: (boolean|undefined), * ServingOperator: (!chrome.networkingPrivate.CellularProviderProperties|undefined), * SIMLockStatus: (!chrome.networkingPrivate.SIMLockStatus|undefined), * SIMPresent: (boolean|undefined), @@ -716,6 +717,7 @@ * PaymentPortal: (!chrome.networkingPrivate.PaymentPortal|undefined), * PRLVersion: (number|undefined), * RoamingState: (string|undefined), + * Scanning: (boolean|undefined), * ServingOperator: (!chrome.networkingPrivate.CellularProviderProperties|undefined), * SIMLockStatus: (!chrome.networkingPrivate.SIMLockStatus|undefined), * SIMPresent: (boolean|undefined),
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 5a09e2d70..50206b4 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -5757,6 +5757,8 @@ <int value="8" label="FAILURE_SESSION_RATE_PARSE"/> <int value="9" label="FAILURE_AVAILABILITY_PARSE"/> <int value="10" label="FAILURE_UNKNOWN_KEY"/> + <int value="11" label="FAILURE_SESSION_RATE_IMPACT_PARSE"/> + <int value="12" label="FAILURE_SESSION_RATE_IMPACT_UNKNOWN_FEATURE"/> </enum> <enum name="ConnectionDiagnosticsIssue">
diff --git a/tools/perf/page_sets/data/key_desktop_move_cases.json b/tools/perf/page_sets/data/key_desktop_move_cases.json index 40eafb5..ad5c0328 100644 --- a/tools/perf/page_sets/data/key_desktop_move_cases.json +++ b/tools/perf/page_sets/data/key_desktop_move_cases.json
@@ -1,10 +1,10 @@ { "archives": { "Maps": { - "DEFAULT": "key_desktop_move_cases_000.wpr" + "DEFAULT": "key_desktop_move_cases_000.wprgo" }, "https://mail.google.com/mail/": { - "DEFAULT": "key_desktop_move_cases_002.wpr" + "DEFAULT": "key_desktop_move_cases_002.wprgo" } }, "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
diff --git a/tools/perf/page_sets/data/key_desktop_move_cases_000.wpr.sha1 b/tools/perf/page_sets/data/key_desktop_move_cases_000.wpr.sha1 deleted file mode 100644 index 79b4cb92..0000000 --- a/tools/perf/page_sets/data/key_desktop_move_cases_000.wpr.sha1 +++ /dev/null
@@ -1 +0,0 @@ -7982d4d8661f8e7bf33e4c4a68e41dd3b8dc7661 \ No newline at end of file
diff --git a/tools/perf/page_sets/data/key_desktop_move_cases_000.wprgo.sha1 b/tools/perf/page_sets/data/key_desktop_move_cases_000.wprgo.sha1 new file mode 100644 index 0000000..6223e1f --- /dev/null +++ b/tools/perf/page_sets/data/key_desktop_move_cases_000.wprgo.sha1
@@ -0,0 +1 @@ +26fe310f8a67bbbd91848cfbd260442285c91468 \ No newline at end of file
diff --git a/tools/perf/page_sets/data/key_desktop_move_cases_002.wpr.sha1 b/tools/perf/page_sets/data/key_desktop_move_cases_002.wpr.sha1 deleted file mode 100644 index 21eb8d3..0000000 --- a/tools/perf/page_sets/data/key_desktop_move_cases_002.wpr.sha1 +++ /dev/null
@@ -1 +0,0 @@ -2f03166ecdd325bacaf5ce2ab411d5f533d1b67a \ No newline at end of file
diff --git a/tools/perf/page_sets/data/key_desktop_move_cases_002.wprgo.sha1 b/tools/perf/page_sets/data/key_desktop_move_cases_002.wprgo.sha1 new file mode 100644 index 0000000..3fa537cb --- /dev/null +++ b/tools/perf/page_sets/data/key_desktop_move_cases_002.wprgo.sha1
@@ -0,0 +1 @@ +ef581f08a71089465e81b6fe4bc72e6ddde33766 \ No newline at end of file
diff --git a/ui/accessibility/ax_tree.cc b/ui/accessibility/ax_tree.cc index e7e9405..81584ab 100644 --- a/ui/accessibility/ax_tree.cc +++ b/ui/accessibility/ax_tree.cc
@@ -166,7 +166,8 @@ } gfx::RectF AXTree::RelativeToTreeBounds(const AXNode* node, - gfx::RectF bounds) const { + gfx::RectF bounds, + bool* offscreen) const { // If |bounds| is uninitialized, which is not the same as empty, // start with the node bounds. if (bounds.width() == 0 && bounds.height() == 0) { @@ -175,12 +176,21 @@ // If the node bounds is empty (either width or height is zero), // try to compute good bounds from the children. if (bounds.IsEmpty()) { + bool all_children_offscreen = true; for (size_t i = 0; i < node->children().size(); i++) { ui::AXNode* child = node->children()[i]; - bounds.Union(GetTreeBounds(child)); + bool temp_offscreen = false; + bounds.Union(GetTreeBounds(child, &temp_offscreen)); + if (!temp_offscreen) + // At least one child is on screen. + all_children_offscreen = false; } - if (bounds.width() > 0 && bounds.height() > 0) + if (bounds.width() > 0 && bounds.height() > 0) { + // If all the children are offscreen, the node itself is offscreen. + if (offscreen != nullptr && all_children_offscreen) + *offscreen = true; return bounds; + } } } else { bounds.Offset(node->data().location.x(), node->data().location.y()); @@ -228,6 +238,8 @@ if (!clipped.IsEmpty()) { // We can simply clip it to the container. bounds = clipped; + // No need to update |offscreen| if it is set, because it should be + // false by default. } else { // Totally offscreen. Find the nearest edge or corner. // Make the minimum dimension 1 instead of 0. @@ -245,6 +257,8 @@ bounds.set_y(0); bounds.set_height(1); } + if (offscreen != nullptr) + *offscreen |= true; } } @@ -254,8 +268,8 @@ return bounds; } -gfx::RectF AXTree::GetTreeBounds(const AXNode* node) const { - return RelativeToTreeBounds(node, gfx::RectF()); +gfx::RectF AXTree::GetTreeBounds(const AXNode* node, bool* offscreen) const { + return RelativeToTreeBounds(node, gfx::RectF(), offscreen); } std::set<int32_t> AXTree::GetReverseRelations(AXIntAttribute attr,
diff --git a/ui/accessibility/ax_tree.h b/ui/accessibility/ax_tree.h index 2a1f0f91..dda8f28 100644 --- a/ui/accessibility/ax_tree.h +++ b/ui/accessibility/ax_tree.h
@@ -181,11 +181,20 @@ // Convert any rectangle from the local coordinate space of one node in // the tree, to bounds in the coordinate space of the tree. + // If set, updates |offscreen| boolean to be true if the node is offscreen + // relative to its rootWebArea. Callers should initialize |offscreen| + // to false: this method may get called multiple times in a row and + // |offscreen| will be propagated. gfx::RectF RelativeToTreeBounds(const AXNode* node, - gfx::RectF node_bounds) const; + gfx::RectF node_bounds, + bool* offscreen = nullptr) const; // Get the bounds of a node in the coordinate space of the tree. - gfx::RectF GetTreeBounds(const AXNode* node) const; + // If set, updates |offscreen| boolean to be true if the node is offscreen + // relative to its rootWebArea. Callers should initialize |offscreen| + // to false: this method may get called multiple times in a row and + // |offscreen| will be propagated. + gfx::RectF GetTreeBounds(const AXNode* node, bool* offscreen = nullptr) const; // Given a node ID attribute (one where IsNodeIdIntAttribute is true), // and a destination node ID, return a set of all source node IDs that
diff --git a/ui/accessibility/ax_tree_unittest.cc b/ui/accessibility/ax_tree_unittest.cc index 3430d85..8bf0d1f 100644 --- a/ui/accessibility/ax_tree_unittest.cc +++ b/ui/accessibility/ax_tree_unittest.cc
@@ -44,6 +44,13 @@ bounds.y(), bounds.width(), bounds.height()); } +bool IsNodeOffscreen(const AXTree& tree, int32_t id) { + AXNode* node = tree.GetFromId(id); + bool result = false; + tree.GetTreeBounds(node, &result); + return result; +} + class FakeAXTreeDelegate : public AXTreeDelegate { public: FakeAXTreeDelegate() @@ -827,6 +834,30 @@ EXPECT_EQ("(100, 10) size (500 x 40)", GetBoundsAsString(tree, 2)); } +// If a node doesn't specify its location but at least one child does have +// a location, it will be offscreen if all of its children are offscreen. +TEST(AXTreeTest, EmptyNodeOffscreenWhenAllChildrenOffscreen) { + AXTreeUpdate tree_update; + tree_update.root_id = 1; + tree_update.nodes.resize(4); + tree_update.nodes[0].id = 1; + tree_update.nodes[0].location = gfx::RectF(0, 0, 800, 600); + tree_update.nodes[0].role = AX_ROLE_ROOT_WEB_AREA; + tree_update.nodes[0].child_ids.push_back(2); + tree_update.nodes[1].id = 2; + tree_update.nodes[1].location = gfx::RectF(); // Deliberately empty. + tree_update.nodes[1].child_ids.push_back(3); + tree_update.nodes[1].child_ids.push_back(4); + // Both children are offscreen + tree_update.nodes[2].id = 3; + tree_update.nodes[2].location = gfx::RectF(900, 10, 400, 20); + tree_update.nodes[3].id = 4; + tree_update.nodes[3].location = gfx::RectF(1000, 30, 400, 20); + + AXTree tree(tree_update); + EXPECT_TRUE(IsNodeOffscreen(tree, 2)); +} + // Test that getting the bounds of a node works when there's a transform. TEST(AXTreeTest, GetBoundsWithTransform) { AXTreeUpdate tree_update; @@ -947,6 +978,37 @@ EXPECT_EQ("(50, 599) size (150 x 1)", GetBoundsAsString(tree, 5)); } +TEST(AXTreeTest, GetBoundsUpdatesOffscreen) { + AXTreeUpdate tree_update; + tree_update.root_id = 1; + tree_update.nodes.resize(5); + tree_update.nodes[0].id = 1; + tree_update.nodes[0].location = gfx::RectF(0, 0, 800, 600); + tree_update.nodes[0].role = AX_ROLE_ROOT_WEB_AREA; + tree_update.nodes[0].child_ids.push_back(2); + tree_update.nodes[0].child_ids.push_back(3); + tree_update.nodes[0].child_ids.push_back(4); + tree_update.nodes[0].child_ids.push_back(5); + // Fully onscreen + tree_update.nodes[1].id = 2; + tree_update.nodes[1].location = gfx::RectF(10, 10, 150, 150); + // Cropped in the bottom right + tree_update.nodes[2].id = 3; + tree_update.nodes[2].location = gfx::RectF(700, 500, 150, 150); + // Offscreen on the top + tree_update.nodes[3].id = 4; + tree_update.nodes[3].location = gfx::RectF(50, -200, 150, 150); + // Offscreen on the bottom + tree_update.nodes[4].id = 5; + tree_update.nodes[4].location = gfx::RectF(50, 700, 150, 150); + + AXTree tree(tree_update); + EXPECT_FALSE(IsNodeOffscreen(tree, 2)); + EXPECT_FALSE(IsNodeOffscreen(tree, 3)); + EXPECT_TRUE(IsNodeOffscreen(tree, 4)); + EXPECT_TRUE(IsNodeOffscreen(tree, 5)); +} + TEST(AXTreeTest, IntReverseRelations) { AXTreeUpdate initial_state; initial_state.root_id = 1;
diff --git a/ui/views/mus/aura_init.cc b/ui/views/mus/aura_init.cc index 765ef0e..146c6f244 100644 --- a/ui/views/mus/aura_init.cc +++ b/ui/views/mus/aura_init.cc
@@ -76,10 +76,11 @@ const std::string& resource_file, const std::string& resource_file_200, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, - Mode mode) { + Mode mode, + bool register_path_provider) { std::unique_ptr<AuraInit> aura_init = base::WrapUnique(new AuraInit()); if (!aura_init->Init(connector, identity, resource_file, resource_file_200, - io_task_runner, mode)) { + io_task_runner, mode, register_path_provider)) { aura_init.reset(); } return aura_init; @@ -90,7 +91,8 @@ const std::string& resource_file, const std::string& resource_file_200, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, - Mode mode) { + Mode mode, + bool register_path_provider) { env_ = aura::Env::CreateInstance( (mode == Mode::AURA_MUS || mode == Mode::AURA_MUS_WINDOW_MANAGER) ? aura::Env::Mode::MUS @@ -101,8 +103,10 @@ base::WrapUnique(new MusClient(connector, identity, io_task_runner)); } ui::MaterialDesignController::Initialize(); - if (!InitializeResources(connector, resource_file, resource_file_200)) + if (!InitializeResources(connector, resource_file, resource_file_200, + register_path_provider)) { return false; + } // Initialize the skia font code to go ask fontconfig underneath. #if defined(OS_LINUX) @@ -124,7 +128,8 @@ bool AuraInit::InitializeResources(service_manager::Connector* connector, const std::string& resource_file, - const std::string& resource_file_200) { + const std::string& resource_file_200, + bool register_path_provider) { // Resources may have already been initialized (e.g. when 'chrome --mash' is // used to launch the current app). if (ui::ResourceBundle::HasSharedInstance()) @@ -146,7 +151,8 @@ // Calling services will shutdown ServiceContext as appropriate. if (!loader.OpenFiles(std::move(directory), resource_paths)) return false; - ui::RegisterPathProvider(); + if (register_path_provider) + ui::RegisterPathProvider(); base::File pak_file = loader.TakeFile(resource_file); base::File pak_file_2 = pak_file.Duplicate(); ui::ResourceBundle::InitSharedInstanceWithPakFileRegion(
diff --git a/ui/views/mus/aura_init.h b/ui/views/mus/aura_init.h index 2ba6fd2..68fa0fb 100644 --- a/ui/views/mus/aura_init.h +++ b/ui/views/mus/aura_init.h
@@ -65,7 +65,8 @@ const std::string& resource_file, const std::string& resource_file_200 = std::string(), scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = nullptr, - Mode mode = Mode::UI); + Mode mode = Mode::UI, + bool register_path_provider = true); // Only valid if Mode::AURA_MUS was passed to constructor. MusClient* mus_client() { return mus_client_.get(); } @@ -76,17 +77,18 @@ // Returns true if AuraInit was able to successfully complete initialization. // If this returns false, then Aura is in an unusable state, and calling // services should shutdown. - bool Init( - service_manager::Connector* connector, - const service_manager::Identity& identity, - const std::string& resource_file, - const std::string& resource_file_200 = std::string(), - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = nullptr, - Mode mode = Mode::UI); + bool Init(service_manager::Connector* connector, + const service_manager::Identity& identity, + const std::string& resource_file, + const std::string& resource_file_200, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + Mode mode, + bool register_path_provider); bool InitializeResources(service_manager::Connector* connector, const std::string& resource_file, - const std::string& resource_file_200); + const std::string& resource_file_200, + bool register_path_provider); #if defined(OS_LINUX) sk_sp<font_service::FontLoader> font_loader_;
diff --git a/ui/webui/resources/cr_elements/chromeos/network/cr_onc_types.js b/ui/webui/resources/cr_elements/chromeos/network/cr_onc_types.js index 4c517967..d93e0d6 100644 --- a/ui/webui/resources/cr_elements/chromeos/network/cr_onc_types.js +++ b/ui/webui/resources/cr_elements/chromeos/network/cr_onc_types.js
@@ -120,6 +120,14 @@ CrOnc.ProxySettingsType = chrome.networkingPrivate.ProxySettingsType; /** @enum {string} */ +CrOnc.VPNType = { + L2TP_IPSEC: 'L2TP-IPsec', + OPEN_VPN: 'OpenVPN', + THIRD_PARTY_VPN: 'ThirdPartyVPN', + ARCVPN: 'ARCVPN', +}; + +/** @enum {string} */ CrOnc.Type = chrome.networkingPrivate.NetworkType; /** @enum {string} */
diff --git a/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html b/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html index 8babeb5d..2fa42fb 100644 --- a/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html +++ b/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html
@@ -48,6 +48,7 @@ #labelWrapper { flex: 1; flex-basis: 0.000000001px; + text-align: start; } #outer {